• 使用C/C++编写Python扩展库


    转载:http://kipway.com/kipway_python_ext.html

    本文描述在Windows系统下(Linux下也一样,官方文档本来就没有区分系统)使用C/C++编写Python3扩展库的方法,Python Extending 的官方文档在这里(Extending and Embedding the Python Interpreter )。下面主要讲原理,有描述不清楚的请参考官方文档。

    1.Python扩展库的本质

    Python扩展库的文件后缀名为pyd,实际就是一个标准的C方式导出函数的DLL动态库,使用结构体指针和函数指针来实现对象的传递。制定了Python和扩展库之间函数参数传递规范,数据对象规范,为方便使用,像微软的COM API一样,Python提供了Python/C API来完成参数的解析,对象的构造等。

    2.开发环境

    开发环境:既然是windows系统,当然使用微软的集成开发环境,这里使用VS2015中的VC140。

    运行测试环境:到Python官网下载最新的Python3.6.1,安装后到安装目录提取Python/C API,两个目录,include和libs复制出来,加入到VC工程的附加包含目录和附加库目录即可。下面先以一个具体例子讲解,先不到出对象,只导出函数。

    3.实现一个简单的Python扩展库

    假设这里的例子扩展模块名为libkdc,在VS里建立一个win32 DLL工程,工程名为linkdc,使用MT模式摆脱VC运行库。将Python/C API的include和Libs目录加入到libkdc工程的附加包含目录和附加库目录。在源代码中引入Python.h,为方便使用,再定义一个C方式导出函数宏PyExt_FUNC

    #include <Python.h>
    #ifdef _WIN32
    #   if defined(__cplusplus)
    #       define PyEXT_FUNC extern "C" __declspec(dllexport) PyObject*
    #   else 
    #       define PyEXT_FUNC __declspec(dllexport) PyObject*
    #   endif
    #else
    #   define PyEXT_FUNC extern "C" PyObject*
    #endif

    然后定义一个导出函数供Python使用

    PyEXT_FUNC py_kdc_open(PyObject* self, PyObject* args)
    {
        const char* sip;
        int port;
        const char* suser;
        const char* spass;
        if (!PyArg_ParseTuple(args, "siss", &sip, &port, &suser, &spass))
        {
            Py_INCREF(Py_None);
            return Py_None;
        }
        int  nh = kdc_open(sip, port, suser, spass, 0);//这句就不必关注了,实现open的具体代码    
        return Py_BuildValue("i", nh);
    }

    先认为所有的导出函数都是上面的例子,返回一个PyObject*对象,有两个PyObject*参数(实际也可以有三个,具体请参见官方文档)。self相当于this指针(因为Python使用结构体和函数指针来实现的对象),目前只导出函数,因此不必理会这个参数。 args就是可变参数的参数数组对象(Python中叫做“Tuple”),Python进来的参数要使用API函数PyArg_ParseTuple来解析成C变量,返回值要使用API函数Py_BuildValue把C变量装换为Python对象。Python/C API不仅可以创建简单的值对象,还可以创建Python内置的Tuple,List,Dict等集合类对象。细节参考官方文档,也就是Python/C API提供了常用的Pyhton对象和C之间的转换函数。

    如果是普通的动态库,上面定义的导出函数py_kdc_open已经可以被其他程序使用了,但是Python解释程序还不知道有这个函数,因此需要实现一个Python约定的函数PyInit_xxx(),其中的xxx就是模块名libkdc,当Python中import libkdc时,就会首先调用这个约定的导出函数,获取其他导出函数的定义,相当于微软COM组件IUnknown接口的QueryInterface方法,但是这里Python来的更简单更直接,Python来源于Linux,Linux下大部分的软件和工具是为程序员服务的,简单直接明了,windows下的软件和工具是直接为最终用户服务的,程序员用起来晦涩难懂。下面给出PyInit_libkdc的实现

    PyMODINIT_FUNC PyInit_libkdc(void)
    {
        static PyMethodDef pyMethods[] =
        {
            { "open", py_kdc_open, METH_VARARGS, "open the channel" },//先只看这一行就行了
            { "status", py_kdc_status, METH_VARARGS, "get the channel status" },
            { "close", py_kdc_close, METH_VARARGS, "open the channel" },        
            { "select", py_kdc_select, METH_VARARGS, "query records" },
            { NULL, NULL,0,NULL }
        };
        static struct PyModuleDef cModPyDem =
        {
            PyModuleDef_HEAD_INIT,
            "libkdc",  // name of module 
            NULL,      // module documentation, may be NULL 
            -1,        // size of per-interpreter state of the module, or -1 if the module keeps state in global variables. 
            pyMethods  // Methods
        };
        return PyModule_Create(&cModPyDem);
    }

    上面约定函数目的就是告诉Python这个libkdc扩展库里面有哪些方法可以调用(目前没有导出对象,只有方法,具体用法参见官方文档),好了,编译成动态库libkdc.dll,改名字为libkdc.pyd就可以当作Python扩展库使用了。

    4.实现自定义Python对象

    基本数据类型对象和Python内置的集合类对象一般够用了,如果要实现自定义对象返回给Python使用,可参见官方文档Defining New Types一章,其基本原理就是你需要扩展定义一个基于PyObject的结构体,增加你自己的成员变量,填写构造函数和析构函数和其他基础性的函数指针,填写自定义方法的函数指针。定义一个static的自定义结构体全局变量(相当于COM中的类厂),然后在PyInit_XXX()中使用Py_INCREF增加引用计数(和COM组件一样,Python也是通过引用计数来决定销毁对象的时机),使用PyModule_AddObject将自定义的对象加入到模块中。 这时候上面例子导出函数的self参数就有用了,相当于C++中的this指针,确定操作的是哪个对象,因为在Python扩展库中,对象的数据和方法是分开的。官方文档给出的例子如下:

    #include <Python.h>
    typedef struct {
        PyObject_HEAD
        /* Type-specific fields go here. */
    } noddy_NoddyObject;
    static PyTypeObject noddy_NoddyType = {
        PyVarObject_HEAD_INIT(NULL, 0)
        "noddy.Noddy",             /* tp_name */
        sizeof(noddy_NoddyObject), /* tp_basicsize */
        0,                         /* tp_itemsize */
        0,                         /* tp_dealloc */
        0,                         /* tp_print */
        0,                         /* tp_getattr */
        0,                         /* tp_setattr */
        0,                         /* tp_reserved */
        0,                         /* tp_repr */
        0,                         /* tp_as_number */
        0,                         /* tp_as_sequence */
        0,                         /* tp_as_mapping */
        0,                         /* tp_hash  */
        0,                         /* tp_call */
        0,                         /* tp_str */
        0,                         /* tp_getattro */
        0,                         /* tp_setattro */
        0,                         /* tp_as_buffer */
        Py_TPFLAGS_DEFAULT,        /* tp_flags */
        "Noddy objects",           /* tp_doc */
    };
    static PyModuleDef noddymodule = {
        PyModuleDef_HEAD_INIT,
        "noddy",
        "Example module that creates an extension type.",
        -1,
        NULL, NULL, NULL, NULL, NULL
    };
    PyMODINIT_FUNC
    PyInit_noddy(void)
    {
        PyObject* m;
        noddy_NoddyType.tp_new = PyType_GenericNew;
        if (PyType_Ready(&noddy_NoddyType) < 0)
            return NULL;
        m = PyModule_Create(&noddymodule);
        if (m == NULL)
            return NULL;
        Py_INCREF(&noddy_NoddyType);
        PyModule_AddObject(m, "Noddy", (PyObject *)&noddy_NoddyType);
        return m;
    }

    5.总结

    Python使用了两个技术就实现了扩展:

    • 动态库,作为扩展库的包装方式,利用其动态加载特性实现import,通过约定的C导出函数查询接口和导出对象
    • C语言的结构体指针和函数指针实现动态语言的对象和C程序交换数据

    对于悟道的程序员来讲,计算机开发语言只有C语言和C语言产生的工具语言(java,.Net,js,Pyhton等),如果会使用词法分析flex和语法分析bison这两个工具,你可以自己设计一种语言。

    by the way 这篇文章使用gedit编辑,发现gedit书写html文档并不输notepad++和sublime text。

    天上我才必有用,千金散尽还复来!
  • 相关阅读:
    Objective-C 生成器模式 -- 简单实用和说明
    Objective-C 桥接模式 -- 简单实用和说明
    Objective-C 工厂模式(下) -- 抽象工厂模式
    Objective-C 工厂模式(上) -- 简单工厂模式
    Linux篇---Vi的使用
    【Spark篇】---SparkSQL中自定义UDF和UDAF,开窗函数的应用
    【Spark篇】---SparkStreaming算子操作transform和updateStateByKey
    【Spark篇】---SparkStream初始与应用
    【Spark篇】---SparkSQL on Hive的配置和使用
    【Spark篇】---Spark中Shuffle机制,SparkShuffle和SortShuffle
  • 原文地址:https://www.cnblogs.com/lutaishi/p/13436231.html
Copyright © 2020-2023  润新知