最后更新于 .

还是先说一下背景吧,之前有写过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;
}

 

Pingbacks

Pingbacks已打开。

Trackbacks

引用地址

评论

  1. iCyOMiK

    iCyOMiK on #

    这次的比上次长很多,先Mark一下,回头再看~谢谢。

    Reply

  2. ptrjeffrey

    ptrjeffrey on #

    赞一个
    我的情况和楼主一样,在可执行程序里面一点问题没有,不过编译成so以后就出现
    ImportError: /usr/local/lib/python2.5/lib-dynload/time.so: undefined symbol: pyExec_IOError
    想了很多办法都不行,看到了楼主的文章,试试着改了一下,结果果然OK!
    欢迎交流

    Reply

  3. ptrjeffrey

    ptrjeffrey on #

    为什么评论不上

    Reply

  4. ptrjeffrey

    ptrjeffrey on #

    赞一个
    楼主帮我解决了大问题
    我在so中去调用python的东东的时候也遇到import time 就有问题,出现
    ImportError: /usr/local/lib/python2.5/lib-dynload/_socket.so: undefined symbol: PyExc_IOError
    用了楼主的办法问题解决.哈哈

    Reply

  5. flyliying

    flyliying on #

    可以尝试boost.python

    Reply

    1. Dante

      Dante on #

      呃,为什么不用python原生的,非要去搞别人封装过的呢。。

      Reply

  6. aaqqxx

    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

    1. aaqqxx

      aaqqxx on #

      #include
      #include

      Reply

    2. Dante

      Dante on #

      呃,这个我也没法直接给你解答,对照一下文章的代码和编译参数,如果还是不行的话,只能google看一下有没相同问题的了。。好像之前是有同学报段错误的。

      Reply

    3. madper

      madper on #

      先说一下, 我什么都不会, 然后, 段错误可以考虑用valgrind来跑一下, 起码可以定位是哪里出现的段错误. 然后再考虑怎么解决...

      Reply

  7. 7asswd

    7asswd on #

    赞。看了这个文章,解决了我的BUG.

    Reply

发表评论