还是先说一下背景吧,之前有写过C,C++代码中调用python脚本,但也仅是停留在浅尝辄止的地步,这次由于在fuload中要实现调用python的脚本,所以继续深入了解了一下。 提前打好招呼,这篇文章有点长,但是信息量也比较大,如果感兴趣希望能耐心读下去。 另外,文章中的代码都可以直接到fuload项目下看到: http://code.google.com/p/fuload/source/browse/#svn/trunk/src/slave/py_module
先来看一下so的cpp文件:
#include <iostream>
#include <string>
#include <vector>
#include <set>
#include <map>
#include <python2.7/Python.h>
using namespace std;
#define PYMODULE_NAME "fl_module"
#define PYFUNC_INIT "fuload_handle_init"
#define PYFUNC_PROCESS "fuload_handle_process"
#define PYFUNC_FINI "fuload_handle_fini"
#define PYMODULE_PATH "../py_module/"
PyObject * g_pModule = NULL;
PyObject * g_pInitFunc = NULL;
PyObject * g_pProcessFunc = NULL;
PyObject * g_pFiniFunc = NULL;
string log_python_exception()
{
std::string strErrorMsg;
if (!Py_IsInitialized())
{
strErrorMsg = "Python 运行环境没有初始化!";
return strErrorMsg;
}
if (PyErr_Occurred() != NULL)
{
PyObject *type_obj, *value_obj, *traceback_obj;
PyErr_Fetch(&type_obj, &value_obj, &traceback_obj);
if (value_obj == NULL)
return strErrorMsg;
PyErr_NormalizeException(&type_obj, &value_obj, 0);
if (PyString_Check(PyObject_Str(value_obj)))
{
strErrorMsg = PyString_AsString(PyObject_Str(value_obj));
}
if (traceback_obj != NULL)
{
strErrorMsg += "Traceback:";
PyObject * pModuleName = PyString_FromString("traceback");
PyObject * pTraceModule = PyImport_Import(pModuleName);
Py_XDECREF(pModuleName);
if (pTraceModule != NULL)
{
PyObject * pModuleDict = PyModule_GetDict(pTraceModule);
if (pModuleDict != NULL)
{
PyObject * pFunc = PyDict_GetItemString(pModuleDict,"format_exception");
if (pFunc != NULL)
{
PyObject * errList = PyObject_CallFunctionObjArgs(pFunc,type_obj,value_obj, traceback_obj,NULL);
if (errList != NULL)
{
int listSize = PyList_Size(errList);
for (int i=0;i < listSize;++i)
{
strErrorMsg += PyString_AsString(PyList_GetItem(errList,i));
}
}
}
}
Py_XDECREF(pTraceModule);
}
}
Py_XDECREF(type_obj);
Py_XDECREF(value_obj);
Py_XDECREF(traceback_obj);
}
return strErrorMsg;
}
/**
* @brief 清理python环境
*/
void clear_pyenv()
{
Py_CLEAR(g_pModule);
Py_CLEAR(g_pInitFunc);
Py_CLEAR(g_pProcessFunc);
Py_CLEAR(g_pFiniFunc);
Py_Finalize();//调用Py_Finalize,这个跟Py_Initialize相对应的。
g_pModule = NULL;
g_pInitFunc = NULL;
g_pProcessFunc = NULL;
g_pFiniFunc = NULL;
}
/**
* @brief 第一次进入so时,需要做的初始化工作,只会执行一次。
*
* @return 0 succ
* else fail
*/
extern "C" int fuload_handle_init()
{
Py_Initialize();//使用python之前,要调用Py_Initialize();这个函数进行初始化
if (!Py_IsInitialized())
{
printf("init error\n");
return -1001;
}
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append('./')");
PyRun_SimpleString("sys.path.append('"PYMODULE_PATH"')");
g_pModule =PyImport_ImportModule(PYMODULE_NAME);//这里是要调用的文件名
if (!g_pModule) {
printf("Cant open python file!\n");
printf("%s\n",log_python_exception().c_str());
clear_pyenv();
return -1002;
}
g_pInitFunc = PyObject_GetAttrString(g_pModule, PYFUNC_INIT);//这里是要调用的函数名
g_pProcessFunc = PyObject_GetAttrString(g_pModule, PYFUNC_PROCESS);//这里是要调用的函数名
g_pFiniFunc = PyObject_GetAttrString(g_pModule, PYFUNC_FINI);//这里是要调用的函数名
if (!g_pInitFunc || !g_pProcessFunc || !g_pFiniFunc)
{
printf("func name not find\n");
clear_pyenv();
return -1003;
}
PyObject *objResult = PyObject_CallFunction(g_pInitFunc, NULL);//调用函数
if (!objResult)
{
clear_pyenv();
return -1004;
}
int ret = PyInt_AsLong(objResult);
return ret;
}
/**
* @brief 业务逻辑,每次进入
*
* @param mapParams 将输入数据按照url方式解析之后的key-value结构
*
* @return 0 succ
* else 返回值,会用来做统计
*/
extern "C" int fuload_handle_process(map<string,string>& mapParams)
{
if (!g_pProcessFunc)
{
return -1001;
}
PyObject * t_dict = PyDict_New();
for(map<string, string>::iterator it = mapParams.begin(); it != mapParams.end(); ++it)
{
PyDict_SetItemString(t_dict,it->first.c_str(),Py_BuildValue("s", it->second.c_str()));
}
PyObject *objResult = PyObject_CallFunctionObjArgs(g_pProcessFunc,t_dict,NULL);
if (!objResult)
{
printf("%s\n",log_python_exception().c_str());
return -1002;
}
int ret = PyInt_AsLong(objResult);
return ret;
}
/**
* @brief so结束时的收尾工作,只会调用一次。一般不用编写
*
* @return 0 succ
* else fail
*/
extern "C" int fuload_handle_fini()
{
PyObject *objResult = PyObject_CallFunction(g_pFiniFunc, NULL);//调用函数
if (!objResult)
{
clear_pyenv();
return -1004;
}
int ret = PyInt_AsLong(objResult);
clear_pyenv();
return ret;
}
#ifdef FL_MODULE_MAIN
int main(int argc, const char *argv[])
{
map<string,string> mapParams;
mapParams["first"]="1";
mapParams["second"]="2";
fuload_handle_init();
fuload_handle_process(mapParams);
fuload_handle_fini();
return 0;
}
#endif
然后看一下其调用的fl_module.py:
#!/usr/bin/python
# -*- coding: utf-8 -*-
'''
#=============================================================================
# Author: dantezhu - https://www.vimer.cn
# Email: zny2008@gmail.com
# FileName: fl_module.py
# Description: 给python用的module主文件
# Version: 1.0
# LastChange: 2010-12-13 18:45:10
# History:
#=============================================================================
'''
import urllib
def fuload_handle_init():
print 'init'
return 0
def fuload_handle_process(mapParams):
print urllib.urlopen("https://www.vimer.cn").read()
return 0
def fuload_handle_fini():
print 'fini'
return 0
if __name__ == '__main__':
fuload_handle_process("")
代码逻辑都是比较简单的,就不详细解释了,只列出几个链接,大家有兴趣可以看一下: PyObject的常用函数 将python类型转换成C类型 通过C类型生成python类型 Py_BuildValue的说明 OK,其实最郁闷的并不是在代码的编写上,而是在makefile的编写上。 由于一开始想先通过编译成可执行程序来测试,所以makefile如下:
CXX = g++
TARGET = main
C_FLAGS += -g -Wall -pthread
LIB = -L/usr/local/lib/ -lpython2.7 -ldl -lutil
INC = -I. -I/usr/local/include/python2.7/
all: $(TARGET)
main: main.o
$(CXX) -o $@ $^ $(LIB) $(C_FLAGS)
.cpp.o:
$(CXX) -c -o $*.o $(INC) $(C_FLAGS) $*.cpp
.cc.o:
$(CXX) -c -o $*.o $(INC) $(C_FLAGS) $*.cc
clean:
-rm -f *.o $(TARGET)
但是编译出来的程序在执行的时候会报如下错误:
ImportError: /usr/local/lib/python2.7/lib-dynload/_socket.so: undefined symbol: PyExc_ValueError
后来发现这个问题是有两种解决方案:
1.如果python在编译安装的时候,没有使用:
./configure --enable-shared
那么就会造成在/usr/local/lib/目录下只有libpython2.7.a而没有libpython2.7.so,这个时候需要给makefile加一个参数:
-export-dynamic
即makefile变成:
CXX = g++
TARGET = main
C_FLAGS += -g -Wall -pthread -export-dynamic
LIB = -L/usr/local/lib/ -lpython2.7 -ldl -lutil
INC = -I. -I/usr/local/include/python2.7/
all: $(TARGET)
main: main.o
$(CXX) -o $@ $^ $(LIB) $(C_FLAGS)
.cpp.o:
$(CXX) -c -o $*.o $(INC) $(C_FLAGS) $*.cpp
.cc.o:
$(CXX) -c -o $*.o $(INC) $(C_FLAGS) $*.cc
clean:
-rm -f *.o $(TARGET)
成功~
2.我们也可以在python编译安装的时候就加上
./configure --enable-shared
这样原来的makefile不用做任何更改也是可以用的。 用man看了一下:
-E
--export-dynamic
--no-export-dynamic
When creating a dynamically linked executable, using the -E option or the
--export-dynamic option causes the linker to add all symbols to the dynamic symbol
table. The dynamic symbol table is the set of symbols which are visible from dynamic
objects at run time.
If you do not use either of these options (or use the --no-export-dynamic option to
restore the default behavior), the dynamic symbol table will normally contain only
those symbols which are referenced by some dynamic object mentioned in the link.
If you use "dlopen" to load a dynamic object which needs to refer back to the symbols
defined by the program, rather than some other dynamic object, then you will probably
need to use this option when linking the program itself.
You can also use the dynamic list to control what symbols should be added to the
dynamic symbol table if the output format supports it. See the description of
--dynamic-list.
Note that this option is specific to ELF targeted ports. PE targets support a similar
function to export all symbols from a DLL or EXE; see the description of
--export-all-symbols below.
OK,这样可执行程序就没有问题了,但是关键我们要实现的是编译一个so。
-------------------------------------我是分割线-----------------------------------
无论我们是否已经用
./configure --enable-shared
编译了python,为保险起见,都用如下makefile编译so:
CC = gcc
CXX = g++
CFLAGS = -Wall -pipe -DDEBUG -D_NEW_LIC -g -D_GNU_SOURCE \
-shared -D_REENTRANT -fPIC -pthread
LIB = -L/usr/local/lib/ -lpython2.7 -ldl -lutil -Xlinker -export-dynamic
INC = -I. -I/usr/local/include/python2.7/
OO = main.o
TARGETS = libmodule.so
all: $(TARGETS)
$(TARGETS): $(OO)
$(CXX) $(CFLAGS) $(INC) $(OO) -o $@ $(LIB)
.c.o:
$(CC) $(CFLAGS) -c $(INC) $<
echo $@
.cpp.o:
$(CXX) $(CFLAGS) -c $(INC) $<
echo $@
%:%.c
$(CC) $(CFLAGS) -o $@ $< $(OO)
echo $@
clean:
rm -f *.o
rm -f $(TARGETS)
用如下代码加载so:
SoObj=dlopen((char*)moduleFile.c_str(),RTLD_LAZY);
if(SoObj==NULL)
{
return -1;
}
会发现报如下错误:
/usr/local/lib/python2.7/lib-dynload/_socket.so: undefined symbol: forkptyTraceback:Traceback (most recent call last):
File "../py_module/fl_module.py", line 16, in <module>
import urllib
File "/usr/local/lib/python2.7/urllib.py", line 26, in <module>
import socket
File "/usr/local/lib/python2.7/socket.py", line 47, in <module>
import _socket
ImportError: /usr/local/lib/python2.7/lib-dynload/_socket.so: undefined symbol: forkpty
这里可真是苦了我了,在网上遍寻原因未果,后来终于突发奇想,原来是加载so的调用有问题,改成如下方式后正常:
SoObj=dlopen((char*)moduleFile.c_str(),RTLD_LAZY|RTLD_GLOBAL);
if(SoObj==NULL)
{
return -1;
}
搜到的解释如下:
RTLD_LAZY:在dlopen返回前,对于动态库中存在的未定义的变量(如外部变量extern,也可以是函数)不执行解析,就是不解析这个变量的地址。
RTLD_NOW:与上面不同,他需要在dlopen返回前,解析出每个未定义变量的地址,如果解析不出来,在dlopen会返回NULL,错误为:
: undefined symbol: xxxx.......
RTLD_GLOBAL:它的含义是使得库中的解析的定义变量在随后的随后其它的链接库中变得可以使用。
OK,到此为止,我们的程序总算是调通了,文章很长,但是相信也是值得的. 这里再附赠一个函数,当调用python脚本失败,打印异常信息(参考自:http://www.cppblog.com/why/archive/2010/11/08/132999.html):
string log_python_exception()
{
std::string strErrorMsg;
if (!Py_IsInitialized())
{
strErrorMsg = "Python 运行环境没有初始化!";
return strErrorMsg;
}
if (PyErr_Occurred() != NULL)
{
PyObject *type_obj, *value_obj, *traceback_obj;
PyErr_Fetch(&type_obj, &value_obj, &traceback_obj);
if (value_obj == NULL)
return strErrorMsg;
PyErr_NormalizeException(&type_obj, &value_obj, 0);
if (PyString_Check(PyObject_Str(value_obj)))
{
strErrorMsg = PyString_AsString(PyObject_Str(value_obj));
}
if (traceback_obj != NULL)
{
strErrorMsg += "Traceback:";
PyObject * pModuleName = PyString_FromString("traceback");
PyObject * pTraceModule = PyImport_Import(pModuleName);
Py_XDECREF(pModuleName);
if (pTraceModule != NULL)
{
PyObject * pModuleDict = PyModule_GetDict(pTraceModule);
if (pModuleDict != NULL)
{
PyObject * pFunc = PyDict_GetItemString(pModuleDict,"format_exception");
if (pFunc != NULL)
{
PyObject * errList = PyObject_CallFunctionObjArgs(pFunc,type_obj,value_obj, traceback_obj,NULL);
if (errList != NULL)
{
int listSize = PyList_Size(errList);
for (int i=0;i < listSize;++i)
{
strErrorMsg += PyString_AsString(PyList_GetItem(errList,i));
}
}
}
}
Py_XDECREF(pTraceModule);
}
}
Py_XDECREF(type_obj);
Py_XDECREF(value_obj);
Py_XDECREF(traceback_obj);
}
return strErrorMsg;
}
iCyOMiK on #
这次的比上次长很多,先Mark一下,回头再看~谢谢。
Reply
ptrjeffrey on #
赞一个
我的情况和楼主一样,在可执行程序里面一点问题没有,不过编译成so以后就出现
ImportError: /usr/local/lib/python2.5/lib-dynload/time.so: undefined symbol: pyExec_IOError
想了很多办法都不行,看到了楼主的文章,试试着改了一下,结果果然OK!
欢迎交流
Reply
ptrjeffrey on #
为什么评论不上
Reply
ptrjeffrey on #
赞一个
楼主帮我解决了大问题
我在so中去调用python的东东的时候也遇到import time 就有问题,出现
ImportError: /usr/local/lib/python2.5/lib-dynload/_socket.so: undefined symbol: PyExc_IOError
用了楼主的办法问题解决.哈哈
Reply
flyliying on #
可以尝试boost.python
Reply
Dante on #
呃,为什么不用python原生的,非要去搞别人封装过的呢。。
Reply
aaqqxx on #
如果调用的模块里面使用了matplotlib里面的show().,好像就只能显示一次,能不能很好的解决呢?
#include
#include
void test2()
{
// int b;
Py_Initialize();
PyRun_SimpleString("from matplotlib.pyplot import plotfile\n"
"from pylab import show\n"
"plotfile('/home/huskier/Desktop/data')\n"
"show()\n");
Py_Finalize();
}
int
main(int argc,char *argv[])
{
test2();
test2();
return 0;
}
一直段错误,怎么好解决呢?
Reply
aaqqxx on #
#include
#include
Reply
Dante on #
呃,这个我也没法直接给你解答,对照一下文章的代码和编译参数,如果还是不行的话,只能google看一下有没相同问题的了。。好像之前是有同学报段错误的。
Reply
madper on #
先说一下, 我什么都不会, 然后, 段错误可以考虑用valgrind来跑一下, 起码可以定位是哪里出现的段错误. 然后再考虑怎么解决...
Reply
7asswd on #
赞。看了这个文章,解决了我的BUG.
Reply