用Boost.Python + CMake + wxPython构建跨语言GUI程序<二>
- Function
接下来是函数部分,在根目录下边新添加一个子项目,连同其子目录,并且在根目录的CMakeLists.txt里边加入对应声明:
mkdir Function; touch Function/CMakeLists.txt; touch Function/test.cpp
对应的根目录CMakeLists.txt后边加入:
add_subdirectory(Function)
编辑Function的CMakeLists.txt:
project(Function)
set(lib_target function)
include_directories(${Boost_INCLUDE_DIRS})
add_library(${lib_target} SHARED test.cpp)
set_target_properties(${lib_target} PROPERTIES PREFIX "")
target_link_libraries(${lib_target} ${Boost_LIBRARIES})
接下来就是实际的练习代码,添于test.cpp里。
1> 最常见的自由函数,最基本的C函数:
//dummy function void dummyFunc() { cout << "Dummy function called!" << endl; }
对应的Wrapper为:
BOOST_PYTHON_MODULE(function) { def(fun, dummyFunc) .................. .................. }
这里需要留意的是,对应的MODULE里边的那个名字必须和CMakeLists.txt里边制定的库名字完全一样,否则python导入对应的模块时候会报错误。
编译之,只需要:
cd ../Build; make ls lib/function.so
cd lib
在Python里边测试,可以用了:
$python Python 2.6.2 (r262:71600, Aug 8 2009, 19:23:16) [GCC 4.4.1] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import function >>> function.dummyFunc() Dummy function called! >>>
2> 接下来是函数调用里边,参数和返回值生存期的问题,因为有可能函数返回了一个内部对象,必须要外部来释放,或者返回对象指向参数对象的子对象,或者参数之间存在相互依赖等,这些都必须显示指明,否则就可能被可恶的指针问题说羁绊。
下边是一个很极端的例子:
//////////////////////////////////////////////////////////////////////////////// //calling policy struct InnerType { int i; int j; }; struct RefType { float i; double j; }; struct ComposedType { InnerType contained; RefType* ref; }; //really bad ? InnerType& Func(ComposedType& x, RefType* z) { x.ref = z; return x.contained; }
想正确的把最后这个Func导入到Python里边用,就必须指明其参数之间的依赖关系以及返回值应用里边的曲曲折折,就是z指向的对象必须又x来管理,而返回值则是x的一个子对象,这里用Policy来指定:
class_<ComposedType>("ComposedType") .def_readwrite("contained", &ComposedType::contained) .def_readonly("ref", &ComposedType::ref); class_<RefType>("RefType") .def_readwrite("i", &RefType::i) .def_readwrite("j", &RefType::j); class_<InnerType>("InnerType") .def_readwrite("i", &InnerType::i) .def_readwrite("j", &InnerType::j); def("Func", Func, return_internal_reference<1, with_custodian_and_ward<1, 2> >() );
这种繁琐的情况,还是尽量少用来做公有接口的好吧。
一个测试的例子:
Python 2.6.2 (r262:71600, Aug 8 2009, 19:23:16) [GCC 4.4.1] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import function >>> x=function.ComposedType() >>> x.contained.i=1 >>> x.contained.j=2 >>> z=function.RefType() >>> z.i=1 >>> z.j=3 >>> y=function.Func(x,z) >>> y.i 1 >>> y.j 2 >>> y <function.InnerType object at 0x7fead87ff910> >>>
这里使用了类成员变量的导出来构造其他的辅助参数对象,例子也很直观。后边在Class的例子里边还有更详尽的阐释。
3> 函数重载
重载是个很好的工具,可以用同样的名字来描述不同的实现,下边的是一个成员函数重载的例子(其实和Free funciton的唯一差别就是声明导出的时候,
要在class_<T>对象的那个.后边加def,而一般函数只要直接Def即可):
//////////////////////////////////////////////////////////////////////////////// //Overloadding struct X { bool f(int a) { return true; } bool f(int a, double b) { return true; } bool f(int a, double b, char c) { return true; } int f(int a, int b, int c) { return a + b + c; }; };
声明的时候,则要费时一点:
//helpers bool (X::*fx1)(int) = &X::f; bool (X::*fx2)(int, double) = &X::f; bool (X::*fx3)(int, double, char)= &X::f; int (X::*fx4)(int, int, int) = &X::f; class_<X>("X") .def("f", fx1) .def("f", fx2) .def("f", fx3) .def("f", fx4) ;
上边用了几个辅助函数来指向同一个函数,然后将他们都导出到同一个python对象的同一个成员函数下边即可。
Python里边调用的例子:
>>> import function >>> obj=function.X() >>> help(obj.f) Help on method f: f(...) method of function.X instance f( (X)arg1, (int)arg2) -> bool : C++ signature : bool f(X {lvalue},int) f( (X)arg1, (int)arg2, (float)arg3) -> bool : C++ signature : bool f(X {lvalue},int,double) f( (X)arg1, (int)arg2, (float)arg3, (str)arg4) -> bool : C++ signature : bool f(X {lvalue},int,double,char) f( (X)arg1, (int)arg2, (int)arg3, (int)arg4) -> int : C++ signature : int f(X {lvalue},int,int,int)
这里help给出的提示已经包含了几个重载的参数,其中第一个参数就是C++的this指针或者python的self。
>>> obj.f(1,1.1) True >>> obj.f(1,1, 3) 5 >>> function.X.f(obj, 1, 1.2, "msg") True
上边是两种不同的参数调用X的重载成员函数版本。
4> 函数参数的默认值
这个是c++允许函数重载的另一种方式,简单的方法是用一个简单的wrapper来写一些helper,如下:
//////////////////////////////////////////////////////////////////////////////// //Default args int fn(int, double = 3.14, char const* = "hello") { return 1; } //wrapper int fn1(int x) {return fn(x);} int fn2(int x, double y) { return fn(x,y);}
导出的方式和上边类似:
//default args def("f", fn); def("f", fn1); def("f", fn2);
当然boost.python提供了一个宏来完成上边的封装,因此用下边的方式则更简单:
BOOST_PYTHON_FUNCTION_OVERLOADS(fn_overloads, fn, 1, 3);
def("fn", fn, fn_overloads());
上边的宏第一个参数指定了重载函数的名字,第二个是原来的函数名,最后两个参数这表示最少和最多能够接受多少个可变参数。
调用示例:
>>> func = function.f >>> help(func) Help on built-in function f: f(...) f( (int)arg1, (float)arg2, (str)arg3) -> int : C++ signature : int f(int,double,char const*) f( (int)arg1) -> int : C++ signature : int f(int) f( (int)arg1, (float)arg2) -> int : C++ signature : int f(int,double) >>> fn = function.fn >>> help(fn) Help on built-in function fn: fn(...) fn( (int)arg1 [, (float)arg2 [, (str)arg3]]) -> int : C++ signature : int fn(int [,double [,char const*]]) >>> func(1,2.2, "str") 1 >>> fn(1) 1 >>> fn(1, 2.2) 1
5> 类的成员函数参数的默认值
有些时候我们需要和类的成员函数打交道,所以对应的也有一个宏来完成那些繁杂的wrapper:
//////////////////////////////////////////////////////////////////////////////// //Mem_fun struct Y { Y(int i, int j, int k=0, int p=1.2){} void mem_fn(int i, int j = 0, double k=1.2, const std::string& str="msg"){} }; BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(mem_fn_overloads, mem_fn, 1, 4);
宏的作用和上边的那个用于普通函数的差不多。
这里边有一个构造函数默认值的问题,参考下边的optional模板:
//Mem_fn and optional init class_<Y>("Y", init<int, int, optional<int, double> >()) .def("mem_fn", &Y::mem_fn, mem_fn_overloads());