项目里需要使用脚本,我是框架的维护人员,就需要把脚本系统加入到框架里。
因为对PYTHON比较熟悉,所以最后选择了PYTHON作为脚本系统的主引擎。
下面是在这个过程中遇到的种种问题以及最后的解决方法。
1- 编译出错,提示找不到 __imp_Py_XXXX的引用
这个问题真的很恶心,WIN版的PYTHON27.DLL用的是单线DLL的CRT,而项目如果用的是多线LIB的CRT,或者其他不兼容的CRT,就会出这个问题。
果断下载PYTHON2.7.2的源代码,编译之。然而,编译过程也非常的恶心,工程属性修改成MTd和MT仍旧不行。
后看到编译时有调用cl的命令行,找了半天在make_buildinfo这个项目里发现是使用代码生成的编译参数,里面写死的是MDd,修改之再编译,问题解决。
2- Py_Initialize 退出,提示无法找到site模块。
这个问题原本想暴力的修改源代码里的nosite标记改之,后思考可能这个模块是有用的,所以把运行过PYTHON.EXE的PYTHON运行路径里的LIB里的被编译成PYC的所有PY按路径复制到EXE的路径下,问题解决。
3- 不想用BOOST.PYTHON,又想不用写PyCFunction的形式的C模块方法
这个问题经过分析,得到结论是,从任意形式的用户函数生成一个PyObject*(*PYFUNC)(PyObject*,PyObject*)形式的函数封装。并且对于一个独立地址的用户函数,需要一个独立地址的函数封装。
首先,我开始解决如何从一个固定函数得到另一个固定地址函数。一开始我想到用函数地址作为模版参数,这样每个独立地址就能生成一个单独的模版类,然后模版类里的静态函数自然就是独立地址的。后我使用了static的本地函数测试,未果,提示什么non-extra的错误。后进群询问,得到同样答案,并且编译成功的结果。仔细观察后发现,他用的是非static修饰的函数。我去掉static修饰,竟然成了。
接下来,我开始解决如何从PyObject*args解析出用户函数的每个参数的问题。这个问题有几个部分,第一部分是如何从函数里析出每个参数,这部分我用了模版的某种特化,具体名字我不懂,就是如下面这种形式:
template <typename TR, typename T1 = void, typename T2 = void>
struct st_func {};
template <typename TR, typename T1>
struct st_func<TR, T1, void> {};
这样就可以析出返回值TR和各个参数的类型。为了支持尽可能多的参数,我写了个程序生成了从0个参数到40个参数的模版变体。
使用固定地址,因为函数定义需要TR,T1,所以函数指针无法直接在第一个模版参数传递,并且是固定参数,无法放在可选参数后面,所以我选择了先在第一个参数用void*传递函数指针,然后在st_func内部用TR,T1...这些拼出一个函数指针,把首位置的指针强转成原函数形式,再进行调用。
因为PYTHON的解析函数需要每个参数的类型标识符,大部分类型是一个字符表示,这部分用了enum来为每个类型生成一个const且static的字符标识。比如
template <> struct type_char<int> { enum{ typechar = 'i' };};
然后最终PyArg_ParseTuple 需要的是一个字符串,那么我在代码里就 这样来生成这个字符串:
char szTypes[] = { type_char<T1>::typechar, .., 0 }; 最后一个部分是解决TR是void时的函数返回值问题。后来我用了struct内部的特化函数来解决。最终形式如下:
template <void* FP, typename TR, typename T1>
struct st_cppfunc_to_py<FP, TR, T1, void, void>
{
typedef TR (*TFP)(T1);
static PyObject * func( PyObject * self, PyObject * args ) { return _func<TR>( self, args ); }
template<typename ITR>
static PyObject * _func( PyObject * self, PyObject * args ) {
char szType[] = { type_char<T1>::typechar, 0 };
T1 v1;
if( PyArg_ParseTuple( args, szType, &v1 ) ) {
TR ret = ((TFP)FP)( v1 );
char szRetType[2] = { type_char<TR>::typechar, 0 };
return Py_BuildValue( szRetType, ret );
}
Py_RETURN_NONE;
}
template<>
static PyObject * _func<void>( PyObject * self, PyObject * args ) {
char szType[] = { type_char<T1>::typechar, 0 };
T1 v1;
if( PyArg_ParseTuple( args, szType, &v1 ) ) {
((TFP)FP)( v1 );
}
Py_RETURN_NONE;
}
};
外面包一个 template <void*FP, typename TR, typename T1> PyCFunction __cppfunc2py( TR(*RFP)(T1) ) { return st_cppfunc_to_py<FP, TR, T1>::func; }
就可以很方便的生成嵌入PY的函数了。
4- 包装的scriptvalue怎么获得PyObject*的类型呢
用Py_TYPE(ob)就可以获取到object的typeobject,它的tp_name就是它的名字,常用类型 int, long, float, str 都可以分辨出来。
5- PYTHON的调试版总是报GC异常
这个问题我是尝试着来解决的,总结了以下几点:
a. 模块的DICT是不用PY_XDECREF来释放的
b. 返回值需要一个PY_XDECREF释放。
c. Py_BuildValue 返回值需要一个PY_XDECREF。
d. 用户模块不需要 PY_XDECREF。
e. PY文件生成的模块需要一个 PY_XDECREF。
f. Set Object到Tuple去调完PY的函数,PY_XDECREF(TUPLE)时,需要注意的是,Set进去的Object都会被调用一次Py_XDECREF,所以一个好的办法是在Set进Tuple时,就INC一下他们的REF。
6- 为何一直调不到py文件里的函数
这个问题困扰了我几分钟,模块的method获取不到,让我一度以为是脚本写的问题。
后来我打印出来脚本的搜索路径(print sys.path),是一大堆的路径,我放进去的路径排在最后。于是我想,是不是有的路径下有重名的PY文件,就给文件改了个名字,结果就OK了。
这个问题,我后来想可以通过调整搜索优先级来解决,不过目前还是这样解决比较好,因为调整方法目前还不得而知。