如何用C++ 写Python模块扩展(一)

最近作一个小软件须要用到虚拟摄像头,在网上找了找虚拟摄像头软件 发现 Vcam 软件有个API 能够用,有API固然是最好的啦,可是这个API只有C++和C#的。都说 **“人生苦短,得用python”**能用Python解决的事情尽可能别用C++,因而萌生了本身写个模块的想法。
值得庆幸的是以前研究过一段时间C++。
先贴两个python官方文档连接 C API 第三方模块开发指南html

开发环境准备

  1. 因为虚拟摄像头软件只有windows驱动 因此开发平台为Windows
  2. VS2013 (恰好系统里面有安装,听说水平好的能够直接用记事本写,我这种层次的仍是用IDE 比较好否则无法玩了)
  3. Anaconda3 或官方Python 3.6 注意区分32位和64位版本 考虑到一些其余工具的兼容性 我使用的是32位版本Anaconda
  4. 注意编译32位dll时必须用32位版本python的库,64位必须用64位的库,不一样版本Python库编译出来的dll可能不通用

工程配置

  1. 创建win32 DLL工程
    python

  2. 调整工程属性ios

    • 因为本次使用的是32位Python因此直接将平台设置为win32
    • 配置属性>>常规目标文件扩展名 设为*.pyd* 方便python直接调用
    • 配置属性>>C++>>常规附加包含目录 将python安装目录下的include文件夹包含进去
    • 配置属性>>链接器>>常规附加库目录 添加python安装目录下的libs目录
    • 配置属性>>链接器>>输入附加依赖项 添加python.lib
  3. 头文件windows

    • 写Python的C++扩展必须包含Python.hstructmember.h两个头文件api

      #include <windows.h>
        #include <iostream>
        #include <sstream>
        #include <Python.h>            
        #include <structmember.h>
  4. API文件导入略过数据结构

Python模块包含的类建立(上)

  1. 首先建立一个struct 用来存放类的各项属性.ide

    struct IVCamRenderer; # 这个IVCamRenderer在VCam API文件里面有定义 这里从新声明下
     typedef struct _VCam
     {
         PyObject_HEAD     // 结构体的第一个元素必须是 PyObject_HEAD 宏
         IBaseFilter *       __vcam_renderer;   //VCam类的第一个成员
         IVCamRenderer *   __my_vcam;     //第二个成员
         因为要处理图片用到了GDI+ 此属性用来存放
     }VCam;       
     static PyMemberDef VCam_DataMembers[] = {  //类/结构的数据成员类说明 表.   根据官方文档说明此类表必需要要以一个元素全为NULL的数据结构结尾,后面还有一个Method 表也是如此
         { "__vcam_renderer", T_OBJECT, offsetof(VCam, __vcam_renderer), 0, "The vcam_renderer of instance" },
         { "__my_vcam", T_OBJECT, offsetof(VCam, __my_vcam), 0, "The vcam of instance." },
         { NULL, NULL, NULL, 0, NULL }   
     };

    咱们来看一下PyMemberDef 的定义函数

    /* An array of PyMemberDef structures defines the name, type and offset
        of selected members of a C structure.  These can be read by
        PyMember_GetOne() and set by PyMember_SetOne() (except if their READONLY
        flag is set).  The array must be terminated with an entry whose name
        pointer is NULL. */
     typedef struct PyMemberDef {
         char *name;    // 在Python中显示的名称
         int type;      // 变量类型
         Py_ssize_t offset;   // offset 变量在前面为模块类定义的模块中的offset
         int flags;     //读写权限标记
         char *doc;     //帮助文档内容
     } PyMemberDef;
     /* Types */
     #define T_SHORT     0
     #define T_INT       1
     #define T_LONG      2
     #define T_FLOAT     3
     #define T_DOUBLE    4
     #define T_STRING    5
     #define T_OBJECT    6
     /* XXX the ordering here is weird for binary compatibility */
     #define T_CHAR      7   /* 1-character string */
     #define T_BYTE      8   /* 8-bit signed int */
     /* unsigned variants: */
     #define T_UBYTE     9
     #define T_USHORT    10
     #define T_UINT      11
     #define T_ULONG     12
     /* Added by Jack: strings contained in the structure */
     #define T_STRING_INPLACE    13
     /* Added by Lillo: bools contained in the structure (assumed char) */
     #define T_BOOL      14
     #define T_OBJECT_EX 16  /* Like T_OBJECT, but raises AttributeError
                                when the value is NULL, instead of
                                converting to None. */
     #define T_LONGLONG      17
     #define T_ULONGLONG     18
     #define T_PYSSIZET      19      /* Py_ssize_t */
     #define T_NONE          20      /* Value is always None */
     /* Flags */
     #define READONLY            1
     #define READ_RESTRICTED     2
     #define PY_WRITE_RESTRICTED 4
     #define RESTRICTED          (READ_RESTRICTED | PY_WRITE_RESTRICTED)
  2. 写两个函数用来处理python类初始化资源申请和和类析构时资源释放工具

    初始化函数ui

    static void VCam_init(VCam* Self, PyObject* pArgs)        //构造方法.
     {       
         Self->__vcam_renderer = nullptr;
         Self->__my_vcam = nullptr;  
         HRESULT hr=::CoInitialize(nullptr);
         if (FAILED(hr = CoCreateInstance(CLSID_VCamRenderer, NULL, CLSCTX_INPROC, IID_IBaseFilter,
             reinterpret_cast<void**>(&(Self->__vcam_renderer))))) {
             PyErr_SetString(PyExc_OSError, "driver not installed!");
             return;  
         }
         // get [IVCamRender] interface from VCam Renderer filter
         if (FAILED(hr = Self->__vcam_renderer->QueryInterface(&(Self->__my_vcam)))) {
             PyErr_SetString(PyExc_OSError, "driver not installed!");
             return;
         }    
     }

    请不要在乎构造函数中一堆乱七八糟的代码 那些代码是VcamAPI初始化取对象的代码 正常简单点写就是假如类体内声明 一个 成员

    XXType * instance;

    构造时将其实例化一下申请一块内存

    self->instance = new xxx;

    析构函数

    static void VCam_Destruct(VCam* Self)                   //析构方法.
     {
         if (Self->__my_vcam)    Self->__my_vcam->SetConnectionNotificationEvent(reinterpret_cast<__int64>(nullptr));
         if (Self->__vcam_renderer) Self->__vcam_renderer->Release(), Self->__vcam_renderer = nullptr;
         if (Self->__my_vcam) Self->__my_vcam->Release(), Self->__my_vcam = nullptr;
         Py_TYPE(Self)->tp_free((PyObject*)Self);                //释放对象/实例.
     }

    析构时候 shift键构造时候申请的内存防止内存泄漏便可

    delete  self->instance;
     self->instance = nullptr;

    最后须要掉将Python对象释放

    Py_TYPE(Self)->tp_free((PyObject*)Self);                //释放对象/
  3. 写供Python调用的类中的各类方法

    举例:写一个将虚拟摄像头显示 调整为镜像显示的方法

    static PyObject* VCam_Mirror(VCam* Self, PyObject* Argvs)
     {
         Py_INCREF(Py_None);
         int mode=1;
         if (!PyArg_ParseTuple(Argvs, "|i", &mode))
         {
             cout << "Parse the argument FAILED! You should pass correct values!" << endl;
             return Py_None;
         }
         Self->__my_vcam->SetMirror(mode); //Mirror the output video (0: no mirror, others: mirror), non-persistent.
         return Py_None;
     }
    • 全部python方法返回值类型都必须为 PyObject*
    • 对于传入的python类型参数须要用 PyArg_ParseTuple 或者 PyArg_VaParseTupleAndKeywords 等来解析成对应的C类型我这边只传入位置参数 因此用PyArg_ParseTuple 便可
    • 对于解析函数中"|i"的解释:
      • i表示转换格式为int型,其余各类格式具体见api参数说明
      • 因为我定义此函数传入一个带默认值的位置参数"|"后面表示接的参数带有默认值,带有默认值的参数在解析前必须初始化一个值 具体见上面API
      • 这个例子仅仅传入了一个参数,传入多个参数只须要在解析格式字符串中放入多个格式字符,后面用多个变量的引用去接收返回值,用来接收返回值的变量类型必须和格式声明一致,如"ssi" 表示传入三个参数参数类分别str,str,int 用来接收的C变量为char*,char*,int 且三个参数必须所有传入
    • 函数返回值因为本次没有什么东西须要返回因此直接返回一个Py_None
    • Py_INCREF(Py_None); 是干吗用的?
      因为CPython的内存管理机制特性 全部Python对象的引用都会有一个计数器,当计数器为0时CPython的垃圾回收机制就会将该对象的内存空间释放,在Python中引用对象Python会自动处理计数,可是在本身写的C代码里面直接对Python对象引用必须自行操做计数 引用前必须经过Py_INCREF 增长引用计数 再去使用对象 使用完后必须经过Py_DECREF 释放引用计数 不然可能形成程序崩溃或内存泄漏。
      由于这里要调用一个Py_None 返回给Python 因此必须在调用前增长一个引用 因为这个Py_None对象直接返回给了Python python在用完之后会自行减掉计数,因此释放计数不须要本身来作,也不能本身作不然可能引发程序崩溃
      其实上面写法是有问题的,开头直接申请了一个Py_None计数 如果后面这个Py_None没有被返回给Python且没有被释放那么这个Py_None在程序关闭前将永远占用一个内存,因此返回None能不能写的更加简单? 答案是确定的 python 的头文件里面定义了一个宏 Py_RETURN_NONE 直接帮你处理了返回 Py_None 和引用计数问题。
    • 返回其余类型数据
      • 因为Python函数方法必须返回PyObject类型,因此函数返回值须要构造一个PyObject,构造完后还得作引用计数操做
      • 好麻烦有没有 不过Python Api提供个一个Py_BuildValue函数 直接帮你处理好引用计数问题和Python对象建立问题
      • Py_BuildValue 具体见api参数说明

    看完引用计数问题感受有点明白了 CPython 垃圾回收机制原理了有没有

    • 参数解析的其余方法 PyArg_VaParseTupleAndKeywordsPyArg_UnpackTuple 等用法详见手册

先写到这 后面再开一篇 如何用C++ 写Python模块扩展(二)

相关文章
相关标签/搜索