小编典典

Python Swig-从ctypes指针创建swig包装的实例

python

我有用swig包装的类的C 代码。我无法修改代码或包装。在python中,我具有使用ctypes的指向所述C
类实例的指针。如何围绕该指针创建一个Swig包装器?

我知道swig对象拥有一个’this’属性,该属性在内部指向包装的对象,但是我找不到一种将其设置为我手头的指针的方法。

谢谢您的帮助!


阅读 296

收藏
2021-01-20

共1个答案

小编典典

可以
执行此操作,但是这需要大量工作,并且解决使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的指针:

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。稍后,我们将介绍无法解决问题的解决方法。

因此,我们的目标是获取“仅ctypes”函数的输出,并通过更多的ctypes调用传递它,SwigPyObject_New以创建可以与SWIG模块的this属性交换的东西。

为了进行调用,我们通常会调用SWIG_TypeQuery以查找swig_type_info要使用的正确方法。但是,实际上这是一个宏,它可以扩展以传递一些始终是静态的静态变量。因此,我们将使用以下功能:

SWIGRUNTIME swig_type_info *
SWIG_Python_TypeQuery(const char *type)

(具有相同的SWIGRUNTIME条件)。

至此,我们已经足够可以交换代理对象的此属性,并且只要能够构造供体就可以完成。(尽管那会泄漏)。我们可以通过两种方法使它更好:

  1. __init__里面的猴子补丁test.Foo可以工作。如果您确实希望%nodefaultctor在SWIG界面中进行重新编译,那么这是最好的选择:

    def patched_init(self, ptr):
    self.this = ptr
    

    test.Foo.init = patched_init

  2. 创建一个仅具有的新类,然后在修改属性之前将__init__其设置为this属性__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时需要进行一些实质性更改,但似乎在这里并不适用),才能使此答案有效。

2021-01-20