Bilinual
Bilinual

Reputation: 13

Converting a structure containg void* pointers to its Ctype equivalent

I am trying to write a python wrapper for the following functions and structures in a dynamic library, but I cannot get it right.

typedef struct
{
    const char* name;
    void* value;
} XY_Param ;


XY_DLL_API int XY_SetParam(XY_Param parameter);
XY_DLL_API int XY_GetParam(XY_Param* parameter);

On the python side, I defined the corresponding structure and function below.

class Parameter(Structure):
    _fields_ = [
    ("name", c_char_p),
    ("value", c_void_p)
    ]


def setDoubleParameter(self, pname, pvalue):
    param = Parameter()
    param.name = c_char_p(pname.encode())
    param.value = c_void_p(c_double(pvalue))
    ret = self.se.XY_SetParam(ptmtr)

That results in the following error message:

param.value = c_void_p(c_double(pvalue))
TypeError: cannot be converted to pointer

Upvotes: 1

Views: 719

Answers (1)

Mark Tolonen
Mark Tolonen

Reputation: 177800

c_double(pvalue) is an value equivalent to C double. A pointer is needed to cast to a c_void_p (C void*), so the address must be taken. A downside to this design, however, is the object has to exist for the lifetime the pointer is valid, which may not be convenient.

In the example below, I create the ctypes object and store it as an instance variable in the class instance, then cast its address to a c_void_p to pass to C. As long as the class instance exists, the pointer will be valid:

test.c

#define XY_DLL_API __declspec(dllexport)

typedef struct
{
    const char* name;
    void* value;
} XY_Param ;

XY_Param global;

XY_DLL_API void XY_SetParam(XY_Param parameter) {
    global = parameter;
}

XY_DLL_API void XY_GetParam(XY_Param* parameter) {
    *parameter = global;
}

test.py

from ctypes import *

class Parameter(Structure):
    _fields_ = [("name", c_char_p),
                ("value", c_void_p)]

dll = CDLL('./test')
dll.XY_SetParam.argtypes = Parameter,
dll.XY_SetParam.restype = None
dll.XY_GetParam.argtypes = POINTER(Parameter),
dll.XY_GetParam.restype = None

class Stash:
    def set_double(self, name, value):
        param = Parameter()
        param.name = name.encode()
        self.obj = c_double(value) # save the object to be stashed as instance variable
        param.value = cast(byref(self.obj),c_void_p) # take address and cast to void*
        dll.XY_SetParam(param)

    def get_double(self):
        param = Parameter()
        dll.XY_GetParam(byref(param))
        data = cast(param.value,POINTER(c_double)).contents # cast back to double* and dereference
        return param.name.decode(),data.value

stash = Stash()
stash.set_double('double',1.234)
print(stash.get_double())

# demo the object being freed
del stash.obj
print(stash.get_double())  # could crash, could print garbage
('double', 1.234)             # retrieved correctly
('\x12', 1.465443742036e-311) # garbage after deletion

Upvotes: 1

Related Questions