如何用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模塊擴展(二)

相關文章
相關標籤/搜索