通过SWIG将C转换为Python:无法使void **参数保持其值

8

我有一个类似这样的C接口(简化版):

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

它的使用方法如下:

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

你会注意到Operation()分配了缓冲区p,GetFieldValue查询p,Cleanup释放p。我无法控制C接口--那段代码在其他地方被广泛使用。
我想通过SWIG从Python调用此代码,但是我找不到如何传递指向指针的好例子--并检索其值。
我认为正确的方法是使用类型映射,因此我定义了一个接口,该接口将在C端自动取消引用p:
%typemap(in) void** {
   $1 = (void**)&($input);
}

然而,我无法使以下 Python 代码正常工作:
import test
p = None
theAnswer = 0.0f
if test.Operation(p):
   theAnswer = test.GetFieldValue(p)
   test.Cleanup(p)

在调用test.Operation()之后,p始终保持其初始值None。
如果有任何帮助来找出在SWIG中正确执行此操作的方法,将不胜感激。否则,我很可能只会编写一个C++包装器,以防止Python处理指针。然后使用SWIG包装该包装器。有人制止我吧!
编辑:
感谢Jorenko,现在我有了以下SWIG接口:
% 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);
    }
}

使用这个SWIG接口的Python代码如下:
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) 

奇怪的是,在Python代码中,test.GetFieldValue(p)返回无意义的内容,但是test.gfv(p)返回正确的值。我已经在void*的typemap中插入了调试代码,并且两个都具有相同的p值!有什么想法吗?

更新:我决定使用ctypes。简单得多。


  1. 在你的Python代码中,p 同时是 'void**' 和 'void*'。
  2. None 是不可变的,因此无论如何 p 引用的对象都不会改变(尽管你可以使用 =p 绑定到另一个对象)。
- jfs
2个回答

7

我同意theller的观点,你应该使用ctypes。它总是比考虑类型映射更容易。

但是,如果你一定要使用swig,你需要为void **创建一个类型映射,返回新分配的void *

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

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

然后你的Python代码看起来像这样:
import test
success, p = test.Operation()
theAnswer = 0.0f
if success:
   theAnswer = test.GetFieldValue(p)
   test.Cleanup(p)

编辑:

我希望swig能够自动处理一个简单的按值传递的void*参数,但为了保险起见,这里提供了用于包装GetFieldValue()Cleanup()中的void*的swig代码:

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

感谢提供swig。test.Operation()现在可以工作了,但我在从Python代码中调用test.GetFieldValue(p)时遇到了麻烦。我需要为void*添加typemap吗? - Jason Sundram
有点奇怪。我在我的答案中添加了一个类型映射,以防万一,但我还没有测试过它... - Jorenko
谢谢 - 这有点奇怪。没有类型映射,GetFieldValue 声称 p 是空的。有了类型映射,就没有抱怨了,但是 theAnswer 返回了一个垃圾值。 - Jason Sundram
请记住,类型映射是真正的 C 代码片段,会在包装函数调用之前/之后插入。如果您愿意,可以在那里放置调试打印等,以尝试解决问题。 - Jorenko
谢谢--这是接口(+你的东西): %模块测试 %{ extern float GetFieldValue(void *p); }% %inline%{float gfv(void *p){return GetFieldValue(p);}}%奇怪的是,test.GetFieldValue(p)返回无意义的值,但test.gfv(p)返回正确的值。但两者都具有相同的p值! - Jason Sundram
显示剩余2条评论

4

你愿意使用ctypes吗?以下是一份示例代码,应该可以正常工作(尽管未经测试):

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)

谢谢 - 如果可以的话,我更愿意使用SWIG,但如果不行的话,我可能会考虑ctypes。 - Jason Sundram

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接