我有用swig包装的类的C 代码。我无法修改代码或包装。在python中,我具有使用ctypes的指向所述C 类实例的指针。如何围绕该指针创建一个Swig包装器?
我知道swig对象拥有一个’this’属性,该属性在内部指向包装的对象,但是我找不到一种将其设置为我手头的指针的方法。
谢谢您的帮助!
您 可以 执行此操作,但是这需要大量工作,并且解决使ctypes或SWIG接口完整且可用的根本问题比强制互换性要容易得多。(还值得注意的是,从SWIG创建一个ctypes对象比执行您要尝试的工作(从ctypes一个创建SWIG对象)要容易得多。
为了说明这一点,我创建了下面的头文件,将使用SWIG进行包装:
struct Foo { double d; char msg[20]; };
然后,我用以下接口包装它:
%module test %{ #include "test.h" %} // Prove I'm not cheating here - we can't even instantiate this class %nodefaultctor; %include "test.h"
我还添加了一个测试函数供我们从ctypes调用,该函数未进行SWIG包装:
#include "test.h" // This function returns a Foo, but is only accessible to ctypes extern "C" Foo *fun1() { static Foo f = { 1.234, "Hello" }; return &f; }
您this对SWIG封装类的此属性的猜测是一个很好的起点,但它并不只是更改它那么简单- 您插入的对象的类型必须与SWIG期望的匹配。它不仅仅是一个表示为int的指针:
this
repr(test.Foo().this) # You'll need to drop the nodefaultctor directive to see this "<Swig Object of type 'Foo *' at 0x7f621197d1e0>"
如果检查SWIG生成的源代码,我们可以看到有一个函数带有指针,一些类型信息并为我们创建了这些对象:
SWIGRUNTIME PyObject * SwigPyObject_New(void *ptr, swig_type_info *ty, int own);
让我们忽略目前默认情况下SWIGRUNTIME定义的事实static,为了让我们开始尝试运行时,我们可以将其重新定义为extern。稍后,我们将介绍无法解决问题的解决方法。
SWIGRUNTIME
static
extern
因此,我们的目标是获取“仅ctypes”函数的输出,并通过更多的ctypes调用传递它,SwigPyObject_New以创建可以与SWIG模块的this属性交换的东西。
SwigPyObject_New
为了进行调用,我们通常会调用SWIG_TypeQuery以查找swig_type_info要使用的正确方法。但是,实际上这是一个宏,它可以扩展以传递一些始终是静态的静态变量。因此,我们将使用以下功能:
SWIG_TypeQuery
swig_type_info
SWIGRUNTIME swig_type_info * SWIG_Python_TypeQuery(const char *type)
(具有相同的SWIGRUNTIME条件)。
至此,我们已经足够可以交换代理对象的此属性,并且只要能够构造供体就可以完成。(尽管那会泄漏)。我们可以通过两种方法使它更好:
__init__里面的猴子补丁test.Foo可以工作。如果您确实希望%nodefaultctor在SWIG界面中进行重新编译,那么这是最好的选择:
__init__
test.Foo
%nodefaultctor
def patched_init(self, ptr): self.this = ptr
test.Foo.init = patched_init
创建一个仅具有的新类,然后在修改属性之前将__init__其设置为this属性__class__,而改用该类:
__class__
class FooCtypesSwigInterop(object): def __init__(self, ptr): self.this = ptr self.__class__ = test.Foo
当您不想破坏test.Foo现有的__init__实现时,此选项最有意义。
这样,我们现在可以通过以下方式实现我们的初始目标:
import ctypes import test # This *must* happen after the import of the real SWIG module # 0x4 is RTLD_NOLOAD which ensures that we get the same handle as the Python # import did, even if it was loaded with RTLD_LOCAL as Python is prone to. swig_module = ctypes.PyDLL('./_test.so',ctypes.RTLD_LOCAL|0x4) # Note that we used PyDLL instead of CDLL because we need to retain the GIL # Setup SWIG_Python_TypeQuery to have the right argument/return types # (Using void* as a substitute for swig_type_info*) SWIG_Python_TypeQuery = swig_module.SWIG_Python_TypeQuery SWIG_Python_TypeQuery.argtypes = [ctypes.c_char_p] SWIG_Python_TypeQuery.restype = ctypes.c_void_p # Likewise SwigPyObject_New, using ctypes.py_object though for return SwigPyObject_New = swig_module.SwigPyObject_New SwigPyObject_New.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int] SwigPyObject_New.restype = ctypes.py_object # Actually do the type query for the type our ctypes function returns: SWIGTYPE_p_Foo = SWIG_Python_TypeQuery('Foo*') print(hex(SWIGTYPE_p_Foo)) # Now the ctypes function itself that returns the type we want SWIG managed fun1 = swig_module.fun1 fun1.argtypes = [] fun1.restype = ctypes.c_void_p # Make the actual ctypes call we care about here result = fun1() print(hex(result)) # And then create a SwigPyObject for it from the void* return type # Note that 0 means not owned by SWIG sresult = SwigPyObject_New(result, SWIGTYPE_p_Foo, 0) print(repr(sresult)) # This is how we jimmy it back into the this attribute of a SWIG type class FooCtypesSwigInterop(object): def __init__(self, ptr): self.this = ptr self.__class__ = test.Foo c = FooCtypesSwigInterop(sresult) # Finally a usable SWIG object from the ctypes call print(c.msg)
所有这些都可以编译并使用:
swig3.0 -python -c++ -Wall test.i g++ -shared -o _test.so test_wrap.cxx fun1.cc -Wall -Wextra -fPIC -I/usr/include/python2.7/ -std=c++11 -DSWIGRUNTIME=extern LD_LIBRARY_PATH=. python run.py
并给我们:
0x7fb6eccf29e0 0x7fb6eccf2640 <Swig Object of type 'Foo *' at 0x7fb6ee436720> Hello
要解决SWIGRUNTIME定义为的问题,static您需要再执行一步。请使用调试符号或对已获得的SWIG二进制模块进行反向工程,但无法进行修改以找到我们需要的两个函数的地址,这些地址相对于已导出的符号未导出。然后,您可以使用它们来构造ctypes函数指针,而不用按名称查找它们。当然,购买/查找/重写SWIG模块或将缺少的功能添加到ctypes接口可能会更容易。
(最后,值得注意的是,尽管SWIG运行-builtin时需要进行一些实质性更改,但似乎在这里并不适用),才能使此答案有效。
-builtin