首页 > 代码库 > 在python中扩展c语言模块

在python中扩展c语言模块

    有一个以前写的c语言代码,我想把它用在python程序中。我先是看了《python基础教程》一书中的方法,书中说可以用swig加python内置distutils模块的方法来实现。我照着书上的步骤试了试,结果在导入模块的时候总是提示“ImportError: dynamic module does not define init function (initprintf)”。起初我以为是so文件没有放对位置。但是我试着在目录中建立了一个简单的python模块,然后再导入,发现没有问题,看来python是可以直接导入当前目录下的模块的。接着我去网上找其他方法,发现网上少有人用swig,大多数都是自己写wrap文件实现。但是也有一个用swig的,其中的编译步骤跟书上的有差异。我就照着那个试了试,这次出了个新错误,“ImportError: ./example.so: undefined symbol: fact”。看来之前照书上来那个错误应该是因为so文件没有被放入指定位置造成的。接着我又开始想办法解决这个新错误,折腾了一会考虑到是不是没编译好,因此我就照着另一个网站的方法手动编译,问题终于得到了解决。解决方法来自ibm技术社区,看来还是这个网站靠谱。
    就在刚刚我又重新试了试书上的方法。我发现swig是没有问题的,这个工具的作用就是把c源文件转换成用于python的接口源文件。之后我把之前生成的so文件改名,前边加上_,然后移入python的模块文件夹中,接着问题得到解决。看来swig的机制就是,生成的so文件必须以固定命名规则存入指定文件夹里,否则就会出问题。看来swig还是不够人性化啊。
    接着我又开始试验用自己写wrap源文件配合distutils实现c扩展。但是还是会出现“ImportError: ./example.so: undefined symbol: fact”。这时我回想起自己用网上的编译教程时,有一步需要把原始的c文件也编译进去,我这才恍然大悟,distutils压根就没有编译过原始c文件,相关的函数怎么可能实现啊!于是我研究setup.py发现里面填源文件的地方是个list,应该可以填入多个源文件,所以我就把原始源文件也填了进去,果然问题得到了解决。
    综上,在python中扩展c语言模块可以总结为以下几步:
  1. 首先,编写包含py接口的c源文件,称为wrap.c。这一步可以使用swig带过。 例如:swig -python foo.i
  2. 接着,对wrap.c进行编译,生成XX.so。这一步可以使用 distutils带过。如果要用 distutils的话,则需要首先编写一个py脚本。示例:setup(name = ‘example‘, version = ‘1.0‘, ext_modules = [Extension(‘example‘, [‘wrap.c‘,‘example.c‘])])  注意:句子中的list不仅不要包含wrap源文件,还要包含函数的原始源文件
  3. 上一步的so文件便是最终可以使用的库文件,把该文件放入py的lib中或者当前目录,即可导入c语言扩展模块进行使用。如果之前用过swig,还要把相应的so文件移入模块文件夹中并在文件名前加上_。示例: cp printf.so /usr/lib64/python2.7/site-packages/_printf.so
    经过了之前的学习,我已经能够顺利的使用写好的wrap文件生成c扩展库了。今天我决定把自己的那些函数也都编译成扩展库。虽然我可以直接轻松的使用swig实现,但是swig要求必须把写好的库文件放入指定目录,否则就出问题,然而我想的是把所有需要的库都放入一个本地目录来方便程序的移植,swig显然跟我的想法相悖。因此我决定自己编写wrap文件。

    wrap文件中有几个关键函数必须掌握。首先是:PyObject* wrap_fact(PyObject* self, PyObject* args)。这个函数的作用是分析python传进来的参数并执行响应的c函数。PyArg_ParseTuple(args, "is", &n,&c)函数可以用来分析python传进来的(元组)参数。其中的字符串参数是一个格式化字符串,里面is分别表示整形和字符串。后边需要加上存储参数的c变量地址,这里需要注意的是,这些参数必须是用来存储参数的变量的地址,即使要存储的是一个字符串也一样,这点·跟scanf还是有一定差异的。分析完参数之后,就可以直接利用得到的这些参数调用相应的c函数。之后可以利用return Py_BuildValue("i", result)向python返回值。示意中语句可以将result变量的值以整形返回。总的来说,这个函数的实际作用就是取得python调用相应函数时所传递的参数,然后利用这些参数执行这个函数中的c代码段,然后将c代码段中的指定值返回给python。
    然是是一个指针数组:static PyMethodDef exampleMethods[]。这个数组用来存储c函数映射表。每个c函数都需要有一个这样的{"fact", wrap_fact, METH_VARARGS, "Caculate N!"}数组,其中的元素依次代表python中函数的名字,接口函数地址,参数类型,函数描述。需要扩展多少个函数就要有多少个这样的数组。
    最后是模块初始化函数:void initexample()。注意这个函数的名字是有讲究的,必须是init模块名字。里边需要包含语句:m = Py_InitModule("example", exampleMethods);其中的参数分别代表模块名字、函数映射表数组。
    示例:
#include <Python.h>
extern int sendsyslog(const char *addr,int port,int type);
extern int sendtoac2(int type);
extern int sendtoac1(int type);
extern int rarecv(int wtime,const char *if_name2);
PyObject* sendcmsg_syslog(PyObject* self, PyObject* args)
{
  int port,type, result;
  char *addr;
  if (! PyArg_ParseTuple(args, "sii", &addr,&port,&type))
    return NULL;
  result = sendsyslog(addr,port,type);
  return Py_BuildValue("i", result);
}
PyObject* sendcmsg_sendac1(PyObject* self, PyObject* args)
{
  int type, result;
  if (! PyArg_ParseTuple(args, "i", &type))
    return NULL;
  result = sendtoac1(type);
  return Py_BuildValue("i", result);
}

static PyMethodDef sendcmsgMethods[] =
{
  {"syslog", sendcmsg_syslog, METH_VARARGS, "send syslog msg!"},
  {"sendac1", sendcmsg_sendac1, METH_VARARGS ,"send a q msg to client1"},
  {"sendac2", sendcmsg_sendac2, METH_VARARGS ,"send a q msg to client2"},
  {"rarecv", sendcmsg_rarecv, METH_VARARGS ,"detect ra msg"},
  {NULL, NULL}
};
void initsendcmsg()
{
  PyObject* m;
  m = Py_InitModule("sendcmsg", sendcmsgMethods);

}