最近做一個小軟件需要用到虛擬攝像頭,在網上找了找虛擬攝像頭軟件 發現 Vcam 軟件有個API 可以用,有API當然是最好的啦,但是這個API只有C++和C#的。都說 “人生苦短,得用python”能用Python解決的事情盡量別用C++,於是萌生了自己寫個模塊的想法。
值得慶幸的是之前研究過一段時間C++。
先貼兩個python官方文檔鏈接
C API
第三方模塊開發指南
開發環境准備
- 由於虛擬攝像頭軟件只有windows驅動 所以開發平台為Windows
- VS2013 (剛好系統里面有安裝,據說水平好的可以直接用記事本寫,我這種層次的還是用IDE 比較好不然沒法玩了)
- Anaconda3 或官方Python 3.6 注意區分32位和64位版本 考慮到一些其他工具的兼容性 我使用的是32位版本Anaconda
- 注意編譯32位dll時必須用32位版本python的庫,64位必須用64位的庫,不同版本Python庫編譯出來的dll可能不通用
工程配置
-
建立win32 DLL工程
 -
調整工程屬性
- 由於本次使用的是32位Python所以直接將平台設置為win32
- 配置屬性>>常規目標文件擴展名 設為.pyd 方便python直接調用
- 配置屬性>>C++>>常規附加包含目錄 將python安裝目錄下的include文件夾包含進去
- 配置屬性>>連接器>>常規附加庫目錄 添加python安裝目錄下的libs目錄
- 配置屬性>>連接器>>輸入附加依賴項 添加python.lib
-
頭文件
-
寫Python的C++擴展必須包含Python.h 和 structmember.h兩個頭文件
#include <windows.h> #include <iostream> #include <sstream> #include <Python.h> #include <structmember.h>
-
-
API文件導入略過
Python模塊包含的類創建(上)
-
首先創建一個struct 用來存放類的各項屬性.
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)
-
寫兩個函數用來處理python類初始化資源申請和和類析構時資源釋放
初始化函數
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); //釋放對象/
-
寫供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_VaParseTupleAndKeywords 、PyArg_UnpackTuple 等用法詳見手冊
先寫到這 后面再開一篇
如何用C++ 寫Python模塊擴展(二)