zoukankan      html  css  js  c++  java
  • 使用C语言扩展Python

    开发环境:Ubuntu9.10,python2.6,gcc4.4.1

    1,ubuntu下的python运行包和开发包是分开的,因此需要在新利得里面安装python-all-dev,从而可以在代码中引用python的头文件和库。

    2.下面是一个最简单的可以供python调用的c扩展模块,假设c程序文件名为foo.c:

    复制代码
    代码
    #include <Python.h>

    static PyObject* foo_bar(PyObject* self, PyObject* args) {
        Py_RETURN_NONE;
    }

    static PyMethodDef foo_methods[] = {
        {
    "bar",(PyCFunction)foo_bar,METH_NOARGS,NULL},
        {NULL,NULL,
    0,NULL}
    };

    PyMODINIT_FUNC initfoo() {
        Py_InitModule3(
    "foo", foo_methods, "My first extension module.");
    } 
    复制代码

         我们可以将上述模块分成3个部分:1)c模块想对外暴露的接口函数。2)提供给外部的python程序使用的一个c模块函数名称映射表。3)c模块的初始化函数。模块的第一行将Python.h引入到模块中,这个文件将使得你的模块可以hook进python的解释器,从而可以为外部的python程序所使用。

    c模块中的函数签名一般有下列三种形式:

    PyObject* MyFunction(PyObject* self, PyObject* args);
    PyObject
    * MyFunctionWithKeywords(PyObject* self, PyObject* args, PyObject* kw);
    PyObject
    * MyFunctionWithNoArgs(PyObject* self);

          一般我们使用的是第一种方式,函数的参数将会一个元组(tuple)的形式传进来,因此我们在c模块的函数中需要对其进行解析。Python中不能象c语言一样声明一个void类型的函数,如果你不想函数返回一个值的话,那就返回一个NONE,在这里我们可以通过Python头文件中的一个宏Py_RETURN_NONE来实现。

    C模块中的函数名称其实对外部来说是不可见的,因此可以随便你命名,一般我们可以使用static函数(这在C语言里表示在当前文件以外是不可见的)。本文函数命名方式采用模块名加上函数名,例如foo_bar,这表示在模块foo中会有一个bar函数。然后就是函数映射表了,它是一个PyMethodDef结构体数组,

    struct PyMethodDef {
        
    char* ml_name;
        PyCFunction ml_meth;
        
    int ml_flags;
        
    char* ml_doc;
    };

          第一个成员ml_name是函数名,当我们在外部的Python代码中使用此模块时利用这个名称进行函数调用。ml_meth是函数地址。ml_flags告诉解释器ml_meth将会使用上述三种方法签名的哪一种,一般设置为METH_VARARGS,如果你想允许关键字参数,则可以将其与METH_KEYWORDS进行或运算。若不想接受任何参数,则可以将其设置为METH_NOARGS.最后,ml_doc字段是函数的注释文档信息,最好还是写几句吧,不然会被鄙视的。。。另外,这个表必须以{NULL,NULL,0,NULL}这样一条空记录结尾。

        模块的初始化函数是在模块被加载时被Python解释器所调用的,如果你的模块名为foo,则要求命名为initfoo.Py_InitModule3函数一般用来定义一个模块。

    3,现在我们来将foo.c文件编译为一个扩展模块,使用下述命令进行编译: 

    gcc -shared -/usr/include/python2.6 foo.c -o foo.so

    注意shared object的名称必须和传给Py_InitModule3函数的字符串一致,另一种可选的方式是加上module后缀,因此上述foo模块可以命名为foo.so或foomodule.so。

    4,上面的编译方式可以完成任务,但更好的生成扩展模块的方法是使用distutils。首先写一个setup.py脚本:

    from distutils.core import setup, Extension
    setup(name 
    = 'foo', version = '1.0', ext_modules = [Extension('foo', ['foo.c'])])

    然后执行下述命令进行build: 

    python ./setup.py build

    这会在当前目录下生成一个build子目录,其中包含了中间生成的foo.o以及最后生成出来的foo.so。当然,最简单的方法是使用下述命令进行模块的生成和安装:

    python ./setup.py install

    注:由于需要获得dist-packages的写权限,最好先切换到root用户,如果直接使用su切换出现下面的错误:

    su: Authentication failure

    则为root用户设置一个新密码:

    sudo passwd root

    再用新密码切换到root用户。查看build时的详细情况,我们可以发现这么一句:

    copying build/lib.linux-i686-2.6/foo.so -> /usr/local/lib/python2.6/dist-packages

     这是将生成的模块拷贝到/usr/local/lib/python2.6/dist-packages下了,这样就将我们的foo模块安装到系统中了,我们可以验证如下,在python命令行中,

    import foo
    dir(foo)

     结果如下: 

    ['__doc__','__file__','__name__','__package__','bar']

    呵呵,不错吧,这个foo模块现在已经和其他系统模块一样了,原因就在于dist-packages是在sys.path这个路径中的,
    5,现在我们手上已经有一个生成并安装好的C扩展模块了,剩下的就是在python代码中引入这个新模块,并调用它的方法

    import foo
    foo.bar()

    当然,由于在c模块中的bar函数里,我们目前什么都还没做,所以现在啥都没有,在下一篇中我们实现:1)从python脚本里向C模块中传递参数。2)从C模块中返回值给外部的Python脚本

    夜已经深了,这个python和c/c++,java相结合系列的第一篇就暂时写到这里。。。








    在上一篇中我们已经使用c语言实现了一个最简单的扩展模块,这一篇中将在其基础上进行功能的丰富。
    首先来考虑如何从外部的Python向C模块传递进参数,foo_bar2展示了如何向C模块传递整数,浮点数,字符串三个参数,其中"ids"指明了传入参数的数据类型。PyArg_ParseTuple负责对args进行解析,若解析失败则返回0.
    复制代码
    代码
    #include <Python.h>

    static PyObject* foo_bar(PyObject* self, PyObject* args) {
        Py_RETURN_NONE;
    }

    static PyObject* foo_bar2(PyObject* self, PyObject* args) {
        
    int iNum;
        
    double fNum;
        
    char* str;
        
    if (!PyArg_ParseTuple(args, "ids"&iNum, &fNum, &str)) {
            
    return NULL;
        }
        Py_RETURN_NONE;
    }
    static PyMethodDef foo_methods[] = {
        {
    "bar",(PyCFunction)foo_bar,METH_NOARGS,NULL},
        {
    "bar2", (PyCFunction)foo_bar2,METH_VARARGS,NULL},
        {NULL,NULL,
    0,NULL}
    };

    PyMODINIT_FUNC initfoo() {
        Py_InitModule3(
    "foo", foo_methods, "My first extension module.");
    }
    复制代码

     你还可以指定可选的参数,只需要通过在格式字符串中包含一个"|"字符即可,如下所示:

    复制代码
    代码
    static PyObject* foo_bar2(PyObject* self, PyObject* args) {
        
    int iNum;
        
    double fNum;
        
    char* str;
        
    int iNum2 = 4;
        
    double fNum2 = 5.0;
        
    char *str2 = "hello";
        
    if (!PyArg_ParseTuple(args, "ids|ids"&iNum, &fNum, &str,&iNum2, &fNum2, &str2)) {
            
    return NULL;
        }
        Py_RETURN_NONE;
    }
    复制代码

     你在调用此函数时,前面三个参数是必须要传递的,而后面的则是可选的。

    另一种情况是当你的函数接受关键字参数,那么m_flags可设置为METH_VARARGS|METH_KEYWORDS,相应的使用PyArg_ParseTupleAndKeywords来进行参数解析。

    函数 PyArg_ParseTupleAndKeywords() 声明如下:

    int PyArg_ParseTupleAndKeywords(PyObject* arg, PyObject* kwdict, char* format, char* kwlist[],...);

     参数arg和format定义同 PyArg_ParseTuple() 。参数 kwdict 是关键字字典,用于接受运行时传来的关键字参数。参数 kwlist 是一个NULL结尾的字符串,定义了可以接受的参数名,并从左到右与format中各个变量对应。如果执行成功 PyArg_ParseTupleAndKeywords() 会返回true,否则返回false并抛出异常。

    注:嵌套的tuple在使用关键字参数时无法生效,不在kwlist中的关键字参数会导致 TypeError 异常

     代码

    复制代码
    #include <Python.h>

    static PyObject* foo_bar3(PyObject* self, PyObject* args, PyObject* kw) {
        
    static char* kwlist[] = {"i""d""s",NULL};
        
    int iNum = 0;
        
    double fNum = 2.0f;
        
    char* str = "thing";
        
    if (!PyArg_ParseTupleAndKeywords(args,kw,"i|ds",kwlist,&iNum,&fNum,&str)) {
            printf(
    "ERROR");
            
    return NULL;
        }
        printf(
    "num is: %d,%f,%s ",iNum,fNum,str);
        Py_RETURN_NONE;
    }
    static PyMethodDef foo_methods[] = {
        {
    "bar3", (PyCFunction)foo_bar3, METH_VARARGS|METH_KEYWORDS, NULL},
        {NULL,NULL,
    0,NULL}
    };

    PyMODINIT_FUNC initfoo() {
        Py_InitModule3(
    "foo", foo_methods, "My first extension module.");
    }
    复制代码

     相应的在函数表里记录如下:

    {"foo_bar",(PyCFunction)foo_bar, METH_VARARGS|METH_KEYWORDS,NULL},

    这样你在python代码中调用时可以传递关键字参数,其中只有i表示的整数是必需的,因此下述调用都是合法的:

    import foo
    foo.bar3(
    1)
    foo.bar3(
    1,d=2.0)
    foo.bar33(i
    =1,d=2.0)

    而如果你传递了其他关键参数,则会报TypeError,比如foo.bar3(i=1,dd=3.0,s="fda")

    下面来看第二个问题:上面说的PyArg_ParseTuple和PyArg_ParseTupleAndKeywords这两个函数是将传递进C模块的Python对象转变为C里的数据类型,那么相反的情况如何呢?即如何从C模块返回值到Python程序中。要完成这件事,我们所需要的函数是Py_BuildValue,示例如下:

    复制代码
    代码
    #include <Python.h>

    static PyObject* foo_add_sub(PyObject* self, PyObject* args) {
        
    int num1,num2;
        
    if (!PyArg_ParseTuple(args, "ii"&num1, &num2)) {
            
    return NULL;
        }
        
    return Py_BuildValue("ii", num1 + num2, num1 - num2);
    }

    static PyMethodDef foo_methods[] = {
        {
    "add_sub", (PyCFunction)foo_add_sub, METH_VARARGS, NULL},
        {NULL,NULL,
    0,NULL}
    };

    PyMODINIT_FUNC initfoo() {
        Py_InitModule3(
    "foo", foo_methods, "My first extension module.");
    }
    复制代码

    这样在Python代码中调用如下:

    import foo
    (sum,sub) 
    = foo.add_sub(9,3)

    好了,现在从Python代码传递参数进C模块,以及C模块返回值到Python代码都已经清楚了,下一篇我们将利用这些技术来完成一个实际的C扩展模块 

     

     

    上一篇中我们已经了解如何在Python程序和C模块之间进行值的相互传递,现在我们来进入实作阶段,看看如何将一个C语言开发的开源mp3编解码库LAME包装为一个Python下可以使用的扩展模块。
    首先去http://lame.sourceforge.net/download.php下载LAME的源代码,然后切换到root用户编译源代码,
    ./configure
    make
    make install

    安装完成后你可以在/usr/local/include/lame目录下找到lame.h头文件,我们在后面的demo程序中会include它的,下面就是一个非常简单的lame示例程序lame_test.c:

    复制代码
    代码
    #include <stdio.h>
    #include 
    <stdlib.h>
    #include 
    <lame.h>

    #define INBUFSIZE 4096
    #define MP3BUFSIZE (int) (1.25 * INBUFSIZE) + 7200

    int encode(char* inPath, char* outPath) {
        
    int status = 0;
        lame_global_flags
    * gfp;
        
    int ret_code;
        FILE
    * infp;
        FILE
    * outfp;
        
    short* input_buffer;
        
    int input_samples;
        
    char* mp3_buffer;
        
    int mp3_bytes;
        
        gfp 
    = lame_init();
        
    if (gfp == NULL) {
            printf(
    "lame_init failed ");
            status 
    = -1;
            
    goto exit;
        }
        
        ret_code 
    = lame_init_params(gfp);
        
    if (ret_code < 0) {
            printf(
    "lame_init_params returned %d ",ret_code);
            status 
    = -1;
            
    goto close_lame;
        }

        infp 
    = fopen(inPath, "rb");
        outfp 
    = fopen(outPath, "wb");
        
        input_buffer 
    = (short*)malloc(INBUFSIZE*2);
        mp3_buffer 
    = (char*)malloc(MP3BUFSIZE);
        
        
    do{
            input_samples 
    = fread(input_buffer, 2, INBUFSIZE, infp);
            mp3_bytes 
    = lame_encode_buffer_interleaved(gfp, input_buffer,input_samples/2, mp3_buffer, MP3BUFSIZE);
            
    if (mp3_bytes < 0) {
                printf(
    "lame_encode_buffer_interleaved returned %d ", mp3_bytes);
                status 
    = -1;
                
    goto free_buffers;
            } 
    else if(mp3_bytes > 0) {
                fwrite(mp3_buffer, 
    1, mp3_bytes, outfp);
            }
        }
    while (input_samples == INBUFSIZE);
        
        mp3_bytes 
    = lame_encode_flush(gfp, mp3_buffer, sizeof(mp3_buffer));
        
    if (mp3_bytes > 0) {
            printf(
    "writing %d mp3 bytes ", mp3_bytes);
            fwrite(mp3_buffer, 
    1, mp3_bytes, outfp);
        }
    free_buffers:
        free(mp3_buffer);
        free(input_buffer);
        
        fclose(outfp);
        fclose(infp);
    close_lame:
        lame_close(gfp);
    exit:
        
    return status;
    }

    int main(int argc, char** argv) {
        
    if (argc < 3) {
            printf(
    "usage: lame_test rawinfile mp3outfile ");
        }
        encode(argv[
    1], argv[2]);
        
    return 0;
    }
    复制代码

    编译步骤:

    gcc -I /usr/local/include/lame lame_test.c -lmp3lame -o lame_test

    试验准备:

    首先需要一个test.wav文件,先安装sox来将wav文件转为raw格式的数据:

        sudo apt-get install sox
        sox test
    .wav -t raw test.raw

     

    然后执行lame_test来对其进行mp3编码:

    ./lame_test ./test.raw ./test.mp3 

    好了,现在我们要在这个c程序的基础上将其包装为一个Python扩展模块。下面的pylame.c就是简单地调用lame_test.c中定义的encode方法,然后通过它对外部的python程序提高mp3编码的服务

    复制代码
    代码
    #include <Python.h>
    #include <lame.h>

    int encode(char* ,char*);

    static PyObject * pylame_encode(PyObject* self, PyObject* args) {
        
    int status;
        
    char* inPath;
        
    char* outPath;
        
    if (!PyArg_ParseTuple(args, "ss"&inPath, &outPath)) {        
            
    return NULL;
        }
        status = encode(inPath, outPath);
        
    return Py_BuildValue("i", status);
    }

    static PyMethodDef pylame_methods[] = {
        {"encode", pylame_encode, METH_VARARGS, NULL},
        {NULL, NULL, 0, NULL}
    };

    PyMODINIT_FUNC initpylame() {
        Py_InitModule3("pylame", pylame_methods, "an simple lame module.");
    }
    复制代码

    模块编译步骤:

    gcc -shared -I /usr/include/python2.6 -I /usr/local/include/lame/ pylame.c lame_test.c -lmp3lame -o pylame.so

    ok,现在lame扩展模块已经封装好了,可以到python程序中进行调用了。在pylame.so所在目录下新建一个python文件lame1.py代码如下:

    import pylame

    if __name__ == '__main__':
        inPath = './test.raw'
        outPath = './test.mp3'
        pylame.encode(inPath, outPath)

    编译执行: 

    python ./lame1.py

    你会发现生成了一个test.mp3,打开听听看是否是你想要的歌曲呢,呵呵。。。 

     

     

    作者:洞庭散人

    出处:http://phinecos.cnblogs.com/    

    本博客遵从Creative Commons Attribution 3.0 License,若用于非商业目的,您可以自由转载,但请保留原作者信息和文章链接URL。

  • 相关阅读:
    安恒X计划12月月赛
    IDA 7.0在Mojava更新后打不开的问题
    ev3_basic——HITCON CTF 2018
    护网杯划水
    python开发中容易犯的错误整合
    使用gunicorn部署Flask项目
    记两个国外CTF的弱pwn
    MongoDB和pymongo自用手册
    深入理解python之二——python列表和元组
    深入理解python之一——python3对象的一些特性
  • 原文地址:https://www.cnblogs.com/catkins/p/5270418.html
Copyright © 2011-2022 走看看