Search code examples
pythonarraysstructurectypes

Python ctypes pointer to array of structs


Ive got a similar question to ctypes and array of structs but im not looking to return the structure. Rather, my structure is passed into the function call as a pointer and then i want the values pointed at by the pointer. see below:

from .h:

typedef struct NIComplexNumberF32_struct {
   ViReal32 real;
   ViReal32 imaginary;
} NIComplexNumberF32;

ViStatus _VI_FUNC niRFSA_FetchIQSingleRecordComplexF32(
   ViSession vi,
   ViConstString channelList,
   ViInt64 recordNumber,
   ViInt64 numberOfSamples,
   ViReal64 timeout,
   NIComplexNumberF32* data,
   niRFSA_wfmInfo* wfmInfo);

i have made several attempts, but this was my latest:

import ctypes

class NIComplexNumberF32_struct_data(ctypes.Structure):
    _fields_ = [("real", ctypes.c_float),
                ("imaginary",  ctypes.c_float)]

class niRFSA_wfmInfo_struct_data(ctypes.Structure):
    _fields_ = [("absoluteInitialX", ctypes.c_double),
                ("relativeInitialX",  ctypes.c_double),
                ("xIncrement",  ctypes.c_double),
                ("actualSamples",  ctypes.c_double),
                ("offset",  ctypes.c_double),
                ("gain",  ctypes.c_double),
                ("reserved1",  ctypes.c_double),
                ("reserved2",  ctypes.c_double)                
                ]


# ViStatus _VI_FUNC niRFSA_FetchIQSingleRecordComplexF32(
#    ViSession vi,
#    ViConstString channelList,
#    ViInt64 recordNumber,
#    ViInt64 numberOfSamples,
#    ViReal64 timeout,
#    NIComplexNumberF32* data,
#    niRFSA_wfmInfo* wfmInfo);

#create instances
data = ctypes.POINTER(NIComplexNumberF32_struct_data)()
wfmInfo = ctypes.POINTER(niRFSA_wfmInfo_struct_data)()

dll_path = r"C:\Program Files\IVI Foundation\IVI\bin\NiRFSA_64.dll" 
dll = ctypes.cdll.LoadLibrary(dll_path)
dll.niRFSA_FetchIQSingleRecordComplexF32.argtypes =(ViSession,ViString,ViReal64,ViReal64,ViReal64,ctypes.POINTER(NIComplexNumberF32_struct_data),ctypes.POINTER(niRFSA_wfmInfo_struct_data) )
dll.niRFSA_FetchIQSingleRecordComplexF32(handle,bytes('','ascii'),0,1000,1,data,wfmInfo)

Traceback (most recent call last):
RuntimeError: (-1074134952) IVI: (Hex 0xBFFA0058) Null pointer passed for parameter or attribute.

What i want are the real/imag data values from the array of data structure.

I have got to be missing something dumb. Hoping someone can point me in the right direction.

thanks all!!


Solution

  • This:

    #create instances
    data = ctypes.POINTER(NIComplexNumberF32_struct_data)()
    wfmInfo = ctypes.POINTER(niRFSA_wfmInfo_struct_data)()
    

    Is equivalent to the following C code:

    struct NIComplexNumberF32* = NULL;
    struct niRFSA_wfmInfo* = NULL;
    

    It only creates NULL pointers and causes the RuntimeError you see. Instead, create instances of each structure and pass them by address using ctypes.byref().

    Here's a working example with a simplfied API since it wasn't provided: test.c

    struct NIComplexNumberF32 {
        float real;
        float imaginary;
    };
    
    struct niRFSA_wfmInfo {
        double absoluteInitialX;
        double relativeInitialX;
        double xIncrement;
        double actualSamples;
        double offset;
        double gain;
        double reserved1;
        double reserved2;
    };
    
    __declspec(dllexport)  // I'm using Windows, so needs this for function export from DLL
    void niRFSA_FetchIQSingleRecordComplexF32(struct NIComplexNumberF32* p1, struct niRFSA_wfmInfo* p2) {
        p1->real = 1.5f;
        p1->imaginary = 2.75f;
        p2->absoluteInitialX = 1.5;
        p2->relativeInitialX = 2.5;
        p2->xIncrement = 3.5;
        p2->actualSamples = 4.5;
        p2->offset = 5.5;
        p2->gain = 6.5;
        p2->reserved1 = 0.0;
        p2->reserved2 = 0.0;
    }
    

    test.py

    import ctypes as ct
    
    class NIComplexNumberF32(ct.Structure):
        _fields_ = (("real", ct.c_float),
                    ("imaginary",  ct.c_float))
    
        # helper to print fields
        def __repr__(self):
            return f'({self.real}{self.imaginary:+}j)'
    
    class niRFSA_wfmInfo(ct.Structure):
        _fields_ = (("absoluteInitialX", ct.c_double),
                    ("relativeInitialX",  ct.c_double),
                    ("xIncrement",  ct.c_double),
                    ("actualSamples",  ct.c_double),
                    ("offset",  ct.c_double),
                    ("gain",  ct.c_double),
                    ("reserved1",  ct.c_double),
                    ("reserved2",  ct.c_double))
    
    dll = ct.CDLL('./test')
    dll.niRFSA_FetchIQSingleRecordComplexF32.argtypes = ct.POINTER(NIComplexNumberF32), ct.POINTER(niRFSA_wfmInfo)
    dll.niRFSA_FetchIQSingleRecordComplexF32.restype = None
    
    data = NIComplexNumberF32(5.125, 7.375)  # If you want to initialize the fields to something besides default
    print(data)  # Uses __str__ or _repr__ function for display if defined (in that order)
    wfmInfo = niRFSA_wfmInfo()  # Init fields using defaults (0.0 for double type)
    
    # This function doesn't use the input values.
    # It returns data in both structures.
    dll.niRFSA_FetchIQSingleRecordComplexF32(ct.byref(data), ct.byref(wfmInfo))
    
    print(data)  # Uses defined representation function (__repr__)
    
    # access attributes directly...
    print(wfmInfo.absoluteInitialX)
    print(wfmInfo.relativeInitialX)
    print(wfmInfo.gain)
    

    Output:

    (5.125+7.375j)
    (1.5+2.75j)
    1.5
    2.5
    6.5