使用SWIG Python动态绑定C++对象


SWIG(Simplified Wrapper and Interface Generator)是一个为C/C++库提供脚本调用支持的工具,支持Lua, Perl, Python, Go等多种脚本语言。如果不了解可以参考Interfacing C/C++ and Python with SWIG。本文主要关注在SWIG Python中如何实现绑定已有C++实例,想象一下,调试时如果可以不用重新编译C++程序,使用脚本动态调用C++,该是有多方便。SWIG文档 31.3.5 Pointers小节中明确提到

However, the inverse operation is not possible, i.e., you can't build a Swig pointer object from a raw integer value

官方是不提供支持的,但是仔细看一下SWIG代码会发现并不难实现。

首先,我们来看一下SWIG是如何对C++类进行封装的,定义一个简单的测试类:

  
  
  
#ifndef FOO_H__ #define FOO_H__ class Foo { public : Foo(); int GetNum(); void SetNum( int num); private : int m_num; }; #endif // FOO_H__

Foo.cpp

  
  
  
#include " Foo.h " Foo::Foo() : m_num( - 1 ) { } int Foo::GetNum() { return m_num; } void Foo::SetNum( int num ) { m_num = num; }

接口文件:

  
  
  
% module foo % { #include " Foo.h " % } class Foo { public : Foo(); int GetNum(); void SetNum( int num); private : int m_num; };

调用命令:swig -c++ –python Foo.i生成wrapper文件,然后使用distutils编译成_foo.so:

  
  
  
from distutils.core import setup, Extension setup(name = " foo " , version = " 1.0 " , ext_modules = [Extension( " _foo " , [ " Foo_wrap.cxx " , " Foo.cpp " ], extra_compile_args = [ ' -g ' ])])

测试一下:

  
  
  
>>> from foo import * >>> f = Foo() >>> f < foo.Foo; proxy of < Swig Object of type ' Foo * ' at 0x56db40 > > >>> f.SetNum( 17 ) >>> f.GetNum() 17

Ok, 工作正常。SWIG为Foo生成了两个封装文件foo.py和Foo_wrap.cxx,Foo_wrap.cxx使用Python C-API导出一些普通函数,foo.py用Python定义了Foo类,使得我们可以OO式使用封装。看一下SWIG生成的两个文件:

foo.py

  
  
  
class Foo(_object): def __init__ (self, * args): this = _foo.new_Foo( * args) try : self.this.append(this) except : self.this = this def GetNum( * args): return _foo.Foo_GetNum( * args) def SetNum( * args): return _foo.Foo_SetNum( * args) ...

Foo_wrap.cxx

  
  
  
PyObject * _wrap_new_Foo(PyObject * self, PyObject * args) { Foo * result = (Foo * ) new Foo(); resultobj = SWIG_NewPointerObj(SWIG_as_voidptr(result), SWIGTYPE_p_Foo, SWIG_POINTER_NEW | 0 ); return resultobj; } PyObject * _wrap_Foo_GetNum(PyObject * self, PyObject * args) { PyObject * resultobj = 0 ; Foo * arg1 = (Foo * ) 0 ; int result; void * argp1 = 0 ; int res1 = 0 ; PyObject * obj0 = 0 ; if ( ! PyArg_ParseTuple(args,( char * ) " O:Foo_GetNum " , & obj0)) SWIG_fail; res1 = SWIG_ConvertPtr(obj0, & argp1,SWIGTYPE_p_Foo, 0 | 0 ); if ( ! SWIG_IsOK(res1)) { SWIG_exception_fail(SWIG_ArgError(res1), " in method ' " " Foo_GetNum " " ', expected argument " " 1 "" of type ' " " Foo * "" ' " ); } arg1 = reinterpret_cast < Foo * > (argp1); result = ( int )(arg1) -> GetNum(); resultobj = SWIG_From_int(static_cast < int > (result)); return resultobj; }

可以看到SWIG首先把C++类成员函数导出成普通函数,其第一个参数是self,即foo.py中的类Foo实例(为了区别,Python封装类记为py.Foo,C++类记为C++.Foo)。py.Foo的__init__函数调用_wrap_new_Foo构造C++实例,并使用SWIG_NewPointerObj将指针封装为一个PySwigObject,返回。py.Foo将其保存在self.this中。调用py.Foo.GetNum()时,使用SWIG_ConvertPtr再将py.Foo转换成C++.Foo指针,然后调用C++.Foo.GetNum()。

通过以上分析,发现_wrap_new_Foo函数中调用的SWIG_NewPointerObj可以使用C++指针构造一个PySwigObject对象,我们是不是可以手动调用该函数为已有C++实例构造一个PySwigObject呢,如果可以,将结果赋给一个py.Foo.this,理论上该py.Foo就绑定到了该C++实例。话不多说,尝试一下:

添加一个函数创建C++.Foo,并把指针打印出来:

  
  
  
Foo * CreateFoo() { Foo * f = new Foo(); f -> SetNum( 7 ); cout.unsetf(std::ios_base::basefield); cout << f << endl; return f; }

使用Python C-API建一个模块,命名为hook,如下:

  
  
  
#include < Python.h > #include < cstdio > #include < iostream > using namespace std; // 从Foo_wrap.cpp中复制出来的定义 typedef void * ( * swig_converter_func)( void * , int * ); typedef struct swig_type_info * ( * swig_dycast_func)( void ** ); struct swig_type_info; extern " C " swig_type_info * swig_types[ 3 ]; extern " C " PyObject * SWIG_Python_NewPointerObj(PyObject * self, void * ptr, swig_type_info * type, int flags); #define SWIGTYPE_p_Foo swig_types[0] static PyObject * AttachFooObject(PyObject * self, PyObject * args) { const char * addr_str; if ( ! PyArg_ParseTuple(args, " s " , & addr_str)) return NULL; unsigned long addr; sscanf(addr_str, " %lx " , & addr); return SWIG_Python_NewPointerObj(NULL, reinterpret_cast < void *> (addr), SWIGTYPE_p_Foo, 0 ); } static PyMethodDef HookMethods[] = { { " Attach " , AttachFooObject, METH_VARARGS, " Attach C++ object " }, { NULL, NULL, 0 , NULL } }; PyMODINIT_FUNC inithook( void ) { PyObject * m; m = Py_InitModule( " hook " , HookMethods); if (m == NULL) return ; }

distuitls脚本:

  
  
  
from distutils.core import setup, Extension setup(name = " hook " , version = " 1.0 " , ext_modules = [Extension( " hook " , [ " hook.cpp " ], extra_compile_args = [ ' -g ' ], extra_link_args = [ ' _foo.so ' ])])

先看一下效果:

  
  
  
>>> from foo import * >>> CreateFoo() 0x60aba0 < foo.Foo; proxy of < Swig Object of type ' Foo * ' at 0x2a987b1b70 > > >>> import hook >>> def empty_init(self): ... pass ... >>> Foo.__init__ = empty_init // 禁止Foo.__init__再创建C++.Foo >>> f = Foo() >>> f. this = hook.Attach( ' 0x60aba0 ' ) >>> f.GetNum() 7

可以看到,f已经成功绑定到CreateFoo()创建的C++实例上。

但是,hook.cpp中从Foo_wrap.cpp复制出来的那段符号定义中,实际上swig_type_info是没有导出的,可以nm –D _foo.so | grep swig_type_info查看一下,上面我是直接手动修改了SWIG生成的Foo_wrap.cpp文件:

image

还好SWIG是开源的,我们可以给SWIG打patch,在SWIG中找到输出这段代码的部分:

swig/Source/Swig/typesys.c (line 2148 ~ line 2154)

  
  
  
Printf(f_forward, " static swig_type_info *swig_types[%d];\n " , i + 1 ); Printf(f_forward, " static swig_module_info swig_module = {swig_types, %d, 0, 0, 0, 0};\n " , i); Printf(f_forward, " #define SWIG_TypeQuery(name) SWIG_TypeQueryModule(&swig_module, &swig_module, name)\n " ); Printf(f_forward, " #define SWIG_MangledTypeQuery(name) SWIG_MangledTypeQueryModule(&swig_module, &swig_module, name)\n " ); Printf(f_forward, " \n/* -------- TYPES TABLE (END) -------- */\n\n " );

我们手动修改一下:

  
  
  
Printf(f_forward, " extern " C " swig_type_info *swig_types[%d];\n " , i + 1 ); Printf(f_forward, " swig_type_info *swig_types[%d];\n " , i + 1 ); Printf(f_forward, " static swig_module_info swig_module = {swig_types, %d, 0, 0, 0, 0};\n " , i); Printf(f_forward, " #define SWIG_TypeQuery(name) SWIG_TypeQueryModule(&swig_module, &swig_module, name)\n " ); Printf(f_forward, " #define SWIG_MangledTypeQuery(name) SWIG_MangledTypeQueryModule(&swig_module, &swig_module, name)\n " ); Printf(f_forward, " \n/* -------- TYPES TABLE (END) -------- */\n\n " );

diff一下,制作一个patch就可以让SWIG支持了。

总结:

本文只是提供了实现思路,具体应用还需要考虑很多问题,例如swig_type_info的查询等。本文中所用的SWIG是2.0.10版本,有些版本中SWIG_Python_NewPointerObject是没有导出的,不过也可以用打patch的方法解决。


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM