Python swig - 从 ctypes 指针创建 swig 包装的实例 [英] Python swig - create swig wrapped instance from ctypes pointer

查看:47
本文介绍了Python swig - 从 ctypes 指针创建 swig 包装的实例的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have C++ code with a class wrapped with swig. I cannot modify the code or the wrapping. In python, I have, using ctypes, a pointer to an instance of said C++ class. How can I create a swig wrapper around this pointer?

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

I know that swig objects hold a 'this' attribute which internally points to the wrapped object, but I couldn't find a way to set it to the pointer that I have on hand.

感谢您的帮助!

推荐答案

可以做到这一点,但它需要做很多工作,而且解决使两者中的任何一个的潜在问题都会简单得多ctypes 或 SWIG 接口比强制互换性更完整和可用.(还值得注意的是,从 SWIG 创建一个 ctypes 对象比做你想做的事情更容易,即从 ctypes 创建一个 SWIG 对象.

You can do this, but it's rather a lot of work and it would be much simpler to fix the underlying problem of making either the ctypes or the SWIG interface complete and usable than force interchangeability. (It's also worth noting that it's easier to create a ctypes object from a SWIG one than it is to do what you're trying to do, which is to create a SWIG object from a ctypes one).

为了说明这一点,我创建了以下用 SWIG 包装的头文件:

To illustrate this I've created the following header file that we'll wrap with SWIG:

struct Foo {
  double d;
  char msg[20];
};

然后我用下面的接口包装了它:

Then I wrapped it with the following interface:

%module test

%{
#include "test.h"
%}

// Prove I'm not cheating here - we can't even instantiate this class
%nodefaultctor;

%include "test.h"

我还添加了一个测试函数供我们从 ctypes 调用,它没有被 SWIG 包裹:

I also added a test function for us to call from ctypes, that isn't SWIG wrapped:

#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;
}

您对 SWIG 包装类的这个 this 属性的猜测是一个很好的起点,但这并不像更改它那么简单 - 您插入的对象类型必须与 SWIG 期望的匹配.它不仅仅是一个表示为 int 的指针:

Your guess about this this attribute of SWIG wrapped classes is a good starting point, but it's not as simple as just changing that - the type of the object you slot in has to match what SWIG expects. It's more than just a pointer represented as an int:

repr(test.Foo().this) # You'll need to drop the nodefaultctor directive to see this
"<Swig Object of type 'Foo *' at 0x7f621197d1e0>"

如果我们检查 SWIG 生成的源代码,我们可以看到有一个函数接受一个指针、一些类型信息并为我们创建这些对象:

If we inspect the source code that SWIG generates we can see that there is a function which takes a pointer, some type information and creates these objects for us:

SWIGRUNTIME PyObject *
SwigPyObject_New(void *ptr, swig_type_info *ty, int own);

让我们暂时忽略 SWIGRUNTIME 默认定义为 static 的事实,为了让我们开始试验这个运行时,我们可以将它重新定义为 extern.稍后我们将研究无法做到这一点的解决方法.

Let's ignore the fact that SWIGRUNTIME is defined as static by default for now, to get us started experimenting with this runtime we can redefine it to extern. Later we'll look at workarounds for when we can't do that.

所以我们的目标是获取ctypes only"函数的输出并通过更多的 ctypes 调用将其传递给 SwigPyObject_New 以创建一些我们可以用我们的 SWIG 的 this 属性交换的东西模块.

So our goal is to take the output of the "ctypes only" function and pass it, via more ctypes calls into SwigPyObject_New to create something we can swap around for with the this attribute of our SWIG module.

为了调用它,我们通常会调用 SWIG_TypeQuery 来查找要使用的正确 swig_type_info.然而,这实际上是一个宏,它扩展为传入一些始终为静态的静态变量.所以我们将使用这个函数:

In order to call that we'd normally call SWIG_TypeQuery to lookup the correct swig_type_info to use. However this is actually a macro, which expands to pass in some static variables that are always static. So instead we'll be using this function:

SWIGRUNTIME swig_type_info *
SWIG_Python_TypeQuery(const char *type)

(具有相同的 SWIGRUNTIME 条件).

(with the same SWIGRUNTIME proviso).

在这一点上,我们有足够的东西可以交换代理对象的 this 属性,如果我们能够构造捐赠者,就完成了.(虽然那会泄漏).有两种方法可以让这个更好:

At this point we've got enough that we could swap the this attribute of a surrogate object and be done if we were able to construct donors. (Although that would leak). There are two ways we can make this nicer:

  1. Monkey patch __init__test.Foo 内工作.如果您真的不想重新编译 SWIG 接口中的 %nodefaultctor,这是最好的:

  1. Monkey patch __init__ inside test.Foo to work. This is best if you really have %nodefaultctor inside the SWIG interface you don't want to recompile:

def patched_init(self, ptr):
    self.this = ptr
test.Foo.__init__ = patched_init

  • 创建一个只有 __init__ 的新类,它在修改 __class__ 属性之前设置 this 属性并使用它代替:

  • Create a new class that only has an __init__ which sets the this attribute before modifying the __class__ attribute and use that instead:

    class FooCtypesSwigInterop(object):
        def __init__(self, ptr):
            self.this = ptr
            self.__class__ = test.Foo
    

    当您不想破坏 test.Foo 现有的 __init__ 实现时,此选项最有意义.

    this option makes most sense when you don't want to break test.Foo's existing __init__ implementation.

    话虽如此,我们现在可以通过以下方式实现最初的目标:

    With that said we can now achieve our initial goal with something like this:

    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)
    

    这一切都可以编译并使用:

    This all compiles and works with:

    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 接口中.

    To work around the problem of having SWIGRUNTIME defined as static you'll need to do one more step. Either use debug symbols or reverse engineer the SWIG binary module you've got but can't modify to find the addresses of the two functions we need that aren't exported relative to an exported symbol. You can then use those to construct ctypes function pointers instead of looking them up by name. Of course it would be easier to buy/find/re-write the SWIG module, or add the missing features to the ctypes interface probably.

    (最后值得注意的是,如果 SWIG 使用 -builtin 运行,它似乎不适用于这里,但需要进行一些实质性的更改才能使此答案起作用).

    (Finally it's worth noting that although it doesn't seem to apply here if SWIG runs with -builtin some substantial changes will need to be made for this answer to work).

    这篇关于Python swig - 从 ctypes 指针创建 swig 包装的实例的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

  • 查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆