使用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