導(dǎo)出模塊
當(dāng)python執(zhí)行import dllmodule時(shí)禁漓,執(zhí)行了以下步驟:
- 查找名字為dllmodule的動(dòng)態(tài)模塊
2.定位C/C++導(dǎo)出模塊初始化函數(shù)是目,名字為init+dllmodule
3.執(zhí)行這個(gè)初始化函數(shù)妹萨。
4.這個(gè)是初始化函數(shù)調(diào)用函數(shù)Py_InitModule
年枕,這個(gè)函數(shù)會(huì)更新sys.modules,從而將這個(gè)模塊快加入到python中乎完。
Py_InitModule
函數(shù)有兩個(gè)參數(shù)熏兄,第一個(gè)為模塊名,第二個(gè)為PyMethodDef
列表(或者NULL)树姨,保存模塊中的函數(shù)信息摩桶。
導(dǎo)出函數(shù)
使用PyMethodDef
定義需要導(dǎo)出的函數(shù),定義格式如下:
static PyMethodDef addfuncMethods[] = {
{"func_name", func, func_feature_flags, "function docstring"},
{NULL} // 結(jié)束標(biāo)志
};
其中帽揪,函數(shù)指針func必須使用固定格式:
static PyObject* func(PyObject *self, PyObject *args)
or
static PyObject* func(PyObject *self, PyObject *args, PyObject *kwds)
func_feature_flags說明函數(shù)類型硝清,有如下幾種:
METH_VARARGS: 只使用占位參數(shù),比如f(a,b),f(a, b)
METH_KEYWORDS:包含了關(guān)鍵字參數(shù)转晰,比如f(c), f(a,*c)
METH_NOARGS:沒有參數(shù)芦拿,比如f()
METH_STATIC對(duì)應(yīng)python中的static函數(shù),staticmethod
METH_STATIC對(duì)應(yīng)class函數(shù),classmethod
3.1 C++導(dǎo)出函數(shù)的處理過程
- 從args/kwds參數(shù)中提取出參數(shù)查邢,需要將python類型的參數(shù)處理為C/C++類型蔗崎。
- 這一步是我們想在C++層做的所有的邏輯扰藕。
- 將C++邏輯得到的結(jié)果轉(zhuǎn)為python類型,然后return实胸。
- 注意處理exceptions和引用計(jì)數(shù)/內(nèi)存泄漏他嫡。
舉例說明
static PyObject* msg_box(PyObject *self, PyObject *args) #函數(shù)聲明
{
const char *msg;
if (!PyArg_ParseTuple(args, "s", &msg)) #1 參數(shù)轉(zhuǎn)換
return NULL;
MessageBox(0, msg, "test", 0);#2 C++邏輯
Py_INCREF(Py_None);# 4引用計(jì)數(shù)
return Py_None; # 3 return python類型結(jié)果
}
提取占位參數(shù)
提取占位參數(shù),使用函數(shù)PyArg_ParseTuple(PyObject *args, char *format, ...)
比如PyArg_ParseTuple(args, "sbi", &argStr, &argChar, &argInt)
庐完,args包含三個(gè)參數(shù)钢属,分別是string(char *),char和int型门躯,轉(zhuǎn)換后C++使用對(duì)應(yīng)類型的變量保存淆党。
關(guān)鍵字參數(shù)使用函數(shù)PyArg_ParseTupleAndKeywords
,使用方式類似讶凉。
函數(shù)返回值
python的函數(shù)一定有返回值的染乌,所以導(dǎo)出給python用的C++函數(shù)也必須有返回值,沒有也要返回Py_None
懂讯。
此外荷憋,函數(shù)的返回值類型必須為PyObject 。為了達(dá)到這個(gè)目的褐望,需要將返回的結(jié)果轉(zhuǎn)為python類型勒庄,可以使用Py_BuildValue()/PyInt_From()/PyString_From*()/..等函數(shù)轉(zhuǎn)換串前,這些函數(shù)生成的函數(shù)對(duì)象,可以直接返回表实蔽,不需要加引用荡碾。
引用計(jì)數(shù)
非常重要,可能導(dǎo)致引用泄露導(dǎo)致內(nèi)存泄漏局装,也可以能減多了坛吁,在某一個(gè)對(duì)象正在被管理的情況下,這個(gè)對(duì)象已被釋放或者被指向錯(cuò)了铐尚。
Py_INCREAF/Py_DECREF 手動(dòng)加減引用計(jì)數(shù)拨脉。
New Reference(Owned Reference)
Borrowed Reference
Steal Reference
每個(gè)函數(shù)必須要以NewReference返回結(jié)果。
要看一個(gè)API是borrowed reference還是new reference塑径,若borrowed女坑,返回前要increase
異常和異常處理
調(diào)用Python C API錯(cuò)誤,則API會(huì)設(shè)置異常并且返回一個(gè)錯(cuò)誤標(biāo)志统舀。我們調(diào)用了API后必須檢查錯(cuò)誤標(biāo)記匆骗,只需要return it(類比stack),不需要設(shè)置異常誉简。
若我們希望自己raise一個(gè)異常碉就,則必須自己設(shè)置exception,并且返回錯(cuò)誤比較值闷串。
設(shè)置異常:PyErr_Set*()瓮钥,設(shè)置后最終保存在sys.exc_type, sys.exc_value, sys.exc_traceback
調(diào)用API后發(fā)生了異常,但是我們自己處理掉了烹吵,這時(shí)需要清空異常:PyErr_Clear()
檢查現(xiàn)在是否有一個(gè)異常正在發(fā)生:PyErr_Occurred()
假如現(xiàn)在有一個(gè)未處理的異常碉熄,這時(shí)若我們主動(dòng)去再次設(shè)置異常,則會(huì)覆蓋肋拔。(這其實(shí)是不對(duì)的)
若有一個(gè)異常發(fā)生锈津,但是我們并沒有處理,也沒有return到上層:異常被保留凉蜂,并且在任何一個(gè)API調(diào)用時(shí)琼梆,都會(huì)拋出一個(gè)異常。
A函數(shù)拋了個(gè)異常窿吩,但是沒有處理也沒有return茎杂,A函數(shù)調(diào)用后我們又調(diào)用了B,則B會(huì)拋異常纫雁,但是這個(gè)異常其實(shí)是A的煌往。
若只return了錯(cuò)誤標(biāo)記,但是沒有處理異常:vm會(huì)知道轧邪,并且會(huì)提示(有異常發(fā)生刽脖,但是vm不知道發(fā)生了什么)悼粮。
錯(cuò)誤標(biāo)記是什么:
若函數(shù)返回PyObject*, 則將NULL作為錯(cuò)誤碼曾棕。
若返回int,則返回0/-菜循。
1