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

发布于 2021-01-29 15:02:15

我有用swig包装的类的C 代码。我无法修改代码或包装。在python中,我具有使用ctypes的指向所述C

类实例的指针。如何围绕该指针创建一个Swig包装器?

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

谢谢您的帮助!

关注者
0
被浏览
55
1 个回答
  • 面试哥
    面试哥 2021-01-29
    为面试而生,有面试问题,就找面试哥。

    可以
    执行此操作,但是这需要大量工作,并且解决使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时需要进行一些实质性更改,但似乎在这里并不适用),才能使此答案有效。



知识点
面圈网VIP题库

面圈网VIP题库全新上线,海量真题题库资源。 90大类考试,超10万份考试真题开放下载啦

去下载看看