C,C++中调用python脚本(2)-高级应用

还是先说一下背景吧,之前有写过C,C++代码中调用python脚本,但也仅是停留在浅尝辄止的地步,这次由于在fuload中要实现调用python的脚本,所以继续深入了解了一下。
提前打好招呼,这篇文章有点长,但是信息量也比较大,如果感兴趣希望能耐心读下去。
另外,文章中的代码都可以直接到fuload项目下看到:
http://code.google.com/p/fuload/source/browse/#svn/trunk/src/slave/py_module
先来看一下so的cpp文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
#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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/usr/bin/python
# -*- coding: utf-8 -*-
'''
#=============================================================================
#  Author:          dantezhu - http://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("http://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如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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在编译安装的时候,没有使用:

1
./configure --enable-shared

那么就会造成在/usr/local/lib/目录下只有libpython2.7.a而没有libpython2.7.so,这个时候需要给makefile加一个参数:

1
-export-dynamic

即makefile变成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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编译安装的时候就加上

1
./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。
-------------------------------------我是分割线-----------------------------------
无论我们是否已经用

1
./configure --enable-shared

编译了python,为保险起见,都用如下makefile编译so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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:

1
2
3
4
5
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的调用有问题,改成如下方式后正常:

1
2
3
4
5
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):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
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;
}




原创文章,版权所有。转载请注明:转载自Vimer的程序世界 [ http://www.vimer.cn ]

本文链接地址: http://www.vimer.cn/?p=1874

10 个评论 在 “C,C++中调用python脚本(2)-高级应用”

  1. iCyOMiK 说:

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

    [回复]

  2. ptrjeffrey 说:

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

    [回复]

  3. ptrjeffrey 说:

    为什么评论不上

    [回复]

  4. ptrjeffrey 说:

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

    [回复]

  5. flyliying 说:

    可以尝试boost.python

    [回复]

    Dante 回复:

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

    [回复]

  6. aaqqxx 说:

    如果调用的模块里面使用了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;
    }

    一直段错误,怎么好解决呢?

    [回复]

    aaqqxx 回复:

    #include
    #include

    [回复]

    Dante 回复:

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

    [回复]

    madper 回复:

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

    [回复]

我要评论

*

*