C to Python via SWIG: can't get void** paramet

2020-05-27 08:15发布

I have a C interface that looks like this (simplified):

extern bool Operation(void ** ppData);
extern float GetFieldValue(void* pData);
extern void Cleanup(p);

which is used as follows:

void * p = NULL;
float theAnswer = 0.0f;
if (Operation(&p))
{
   theAnswer = GetFieldValue(p);
   Cleanup(p);
}

You'll note that Operation() allocates the buffer p, that GetFieldValue queries p, and that Cleanup frees p. I don't have any control over the C interface -- that code is widely used elsewhere.

I'd like to call this code from Python via SWIG, but I was unable to find any good examples of how to pass a pointer to a pointer -- and retrieve its value.

I think the correct way to do this is by use of typemaps, so I defined an interface that would automatically dereference p for me on the C side:

%typemap(in) void** {
   $1 = (void**)&($input);
}

However, I was unable to get the following python code to work:

import test
p = None
theAnswer = 0.0f
if test.Operation(p):
   theAnswer = test.GetFieldValue(p)
   test.Cleanup(p)

After calling test.Operation(), p always kept its initial value of None.

Any help with figuring out the correct way to do this in SWIG would be much appreciated. Otherwise, I'm likely to just write a C++ wrapper around the C code that stops Python from having to deal with the pointer. And then wrap that wrapper with SWIG. Somebody stop me!

Edit:

Thanks to Jorenko, I now have the following SWIG interface:

% module Test 
%typemap (in,numinputs=0) void** (void *temp)
{
    $1 = &temp;
}

%typemap (argout) void**
{
    PyObject *obj = PyCObject_FromVoidPtr(*$1, Cleanup);
    $result = PyTuple_Pack(2, $result, obj);
}
%{
extern bool Operation(void ** ppData); 
extern float GetFieldValue(void *p); 
extern void Cleanup(void *p);
%} 
%inline 
%{ 
    float gfv(void *p){ return GetFieldValue(p);} 
%} 

%typemap (in) void*
{
    if (PyCObject_Check($input))
    {
        $1 = PyCObject_AsVoidPtr($input);
    }
}

The python code that uses this SWIG interface is as follows:

import test 
success, p = test.Operation()
if success:
   f = test.GetFieldValue(p) # This doesn't work 
   f = test.gvp(p) # This works! 
   test.Cleanup(p) 

Oddly, in the python code, test.GetFieldValue(p) returns gibberish, but test.gfv(p) returns the correct value. I've inserting debugging code into the typemap for void*, and both have the same value of p! The call Any ideas about that?

Update: I've decided to use ctypes. MUCH easier.

2条回答
Fickle 薄情
2楼-- · 2020-05-27 08:54

I agree with theller, you should use ctypes instead. It's always easier than thinking about typemaps.

But, if you're dead set on using swig, what you need to do is make a typemap for void** that RETURNS the newly allocated void*:

%typemap (in,numinputs=0) void** (void *temp)
{
    $1 = &temp;
}

%typemap (argout) void**
{
    PyObject *obj = PyCObject_FromVoidPtr(*$1);
    $result = PyTuple_Pack(2, $result, obj);
}

Then your python looks like:

import test
success, p = test.Operation()
theAnswer = 0.0f
if success:
   theAnswer = test.GetFieldValue(p)
   test.Cleanup(p)

Edit:

I'd expect swig to handle a simple by-value void* arg gracefully on its own, but just in case, here's swig code to wrap the void* for GetFieldValue() and Cleanup():

%typemap (in) void*
{
    $1 = PyCObject_AsVoidPtr($input);
}
查看更多
我想做一个坏孩纸
3楼-- · 2020-05-27 09:06

Would you be willing to use ctypes? Here is sample code that should work (although it is untested):

from ctypes import *

test = cdll("mydll")

test.Operation.restype = c_bool
test.Operation.argtypes = [POINTER(c_void_p)]

test.GetFieldValue.restype = c_float
test.GetFieldValue.argtypes = [c_void_p]

test.Cleanup.restype = None
test.Cleanup.argtypes = [c_void_p]

if __name__ == "__main__":
    p = c_void_p()
    if test.Operation(byref(p)):
        theAnswer = test.GetFieldValue(p)
        test.Cleanup(p)
查看更多
登录 后发表回答