Using Python Ctypes to pass struct pointer to a DL

2020-02-12 10:05发布

问题:

I am attempting to access a function in a DLL file using Python ctypes. The provided function description is below.

Prototype: Picam_ConnectDemoCamera( PicamModel model,
                                    const pichar* serial_number,
                                    PicamCameraID* id )

Description: Virtually connects the software-simulated 'model' with 'serial_number'
and returns the camera id in `_id_`
Notes: `_id_` is optional and can be null

The function references a few variable types defined in the DLL. These variables are described next

PicamModel
Type: enum
Description: The camera model.

PicamCameraID
Type: Struct
- PicamModel model: _model_ is the camera model
- PicamComputerInterface computer_interface: computer_interface is the method of
communication
- pichar sensor_name [PicamStringSize_SensorName]: sensor_name contains the name of
the sensor in the camera
- pichar serial_number [PicamStringSize_SerialNumber]: serial_number contains the
unique serial number of the camera

Note that'pichar' is defined in one of the provided header files as follows:

typedef          char   pichar; /* character native to platform   

This seems straightforward, but isn't working out for me for some reason.

My understanding is as follows: I pass the function three variables:

1) a model, which is really an enum. I'm told that python ctypes doesn't inherently support enums, and so I am passing it an integer, 2, which maps to a particular model type.

2) a pointer to a serial number (I can make this up: an arbitrary string)

3) A pointer to PicamCameraID, a variable type defined within the DLL, based on the struct type

After reading through SO I found a few good starting points, but am still running out of luck. Here's my best shot so far

def pointer(x):
      PointerToType = ctypes.POINTER(type(x))
      ptr = ctypes.cast(ctypes.addressof(x), PointerToType)
      return ptr

class PicamCameraID(ctypes.Structure):
      pass
PicamCameraID._fields_=[("model",ctypes.c_int),
                    ("computer_interface",ctypes.c_int),
                    ("sensor_name",ctypes.c_char_p),
                    ("serial_number",ctypes.c_char_p)]

myid = PicamCameraID()
model = ctypes.c_int(2)
serialnum = ctypes.c_char_p('asdf')
print picam.Picam_ConnectDemoCamera(model, pointer(serialnum), ctypes.byref(myid))

As you can hopefully see, I'm trying to create the variable type that corresponds to the PicamCameraID struct defined in the DLL. My intuition was to use the ctypes.pointer command to reference this when passing the argument to the function, but I found indications to use ctypes.byref instead elsewhere.

The code runs without error (returns 0), however, I get varying results when I try to access the properties of the struct PicamCameraID:

myid.model returns "2" which is great that's what I specified myid.computer_interface returns "1" which is fine

BUT the problem is myid.serial_number returns

Traceback (most recent call last):   File "<pyshell#28>", line 1, in
<module>
   myid.serial_number ValueError: invalid string pointer 0x2820303031207820

and myid.sensor_name returns:

Traceback (most recent call last):   File "<pyshell#29>", line 1, in
<module>
    myid.sensor_name ValueError: invalid string pointer 0x3034333120563245

For some reason these strings aren't being properly populated?

Any ideas would be much appreciated, I am a novice to python ctypes (and c)

Kind regards

Addition June 1 2013

typedef enum PicamStringSize {
    PicamStringSize_SensorName     =  64,
    PicamStringSize_SerialNumber   =  64,
    PicamStringSize_FirmwareName   =  64,
    PicamStringSize_FirmwareDetail = 256 } PicamStringSize; 

typedef struct PicamCameraID {
    PicamModel             model;
    PicamComputerInterface computer_interface;
    pichar                 sensor_name[PicamStringSize_SensorName];
    pichar                 serial_number[PicamStringSize_SerialNumber];
} PicamCameraID;

回答1:

Your definition of PicamCameraID structure is incorrect: sensor_name and serial_number are arrays:

"""
struct PicamCameraID {
  PicamModel             model;
  PicamComputerInterface computer_interface;
  pichar                 sensor_name[PicamStringSize_SensorName];
  pichar                 serial_number[PicamStringSize_SerialNumber]; 
};
"""
import ctypes as c

PicamStringSize_SensorName = PicamStringSize_SerialNumber = 64
PicamModel = PicamComputerInterface = c.c_int
pichar = c.c_char

class PicamCameraID(c.Structure):
    _fields_ = [("model", PicamModel),
                ("computer_interface", PicamComputerInterface),
                ("sensor_name", pichar * PicamStringSize_SensorName),
                ("serial_number", pichar * PicamStringSize_SerialNumber)]

It seems the second argument is just a string, so you don't need to apply pointer() to it. Here's the ctypes prototype for Picam_ConnectDemoCamera() function:

"""
Picam_ConnectDemoCamera(PicamModel model, const pichar* serial_number,
                        PicamCameraID* id)
"""
pichar_p = c.c_char_p # assume '\0'-terminated C strings
Picam_ConnectDemoCamera.argtypes = [PicamModel, pichar_p,
                                    c.POINTER(PicamCameraID)]
Picam_ConnectDemoCamera.restype = c.c_int # assume it returns C int

To call it:

picam_id = PicamCameraID()
rc = Picam_ConnectDemoCamera(2, "serial number here", c.byref(picam_id))
print(rc)
print(picam_id.sensor_name.value) # C string
print(picam_id.sensor_name.raw)   # raw memory


回答2:

Likely, you're not entering the exact structure fields in the exact same order. If you don't, then you usually get random data or segfaults. To access a library with ctypes, you usually need to look inside the header (.h file) to copy the exact structure definition. Alternatively, use cffi with ffi.verify() if you don't want to depend on the precise layout.