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文件:
還好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的方法解決。

