DLL(Dynamic link Library)动态链接库,是windows上常用的程序包类型。很多业务场景中需要制作和引用DLL。 这里举例说明Python调用DLL的方法,以及DLL中代码回调Python函数的方式。
1. CDLL和WinDLL
在Python中,常使用ctypes库进行与C/C++之间的数据类型的转换,以及DLL的调用。
在制作dll时,我们知道需要约定一种调用规则,调用规则通常是对于函数参数的入栈和清空方式的约定,而调用规则比较常用的有两种, __cdecl和__stdcall,简单来说,前者是C/C++的默认规则,后者是Windows上常用的规则。(关于两者的不同请参考如下链接,这里不展开说明了:
https://blog.csdn.net/colinchan/article/details/4478721)
在使用VS制作dll时,默认的规则是__cdecl,如果要采用__stdcall则需要显式调用。如:
#include#include #include #define API_DLL __declspec(dllexport) #define CALL_TYPE __stdcall //#define CALL_TYPE __cdecl extern "C" { API_DLL int CALL_TYPE dllDebug(int a, int * iParam, char * oParam); API_DLL void CALL_TYPE dllRegisterPythonFunc(void * pyFuncPtr); API_DLL int CALL_TYPE dllCallbackPythonFunc(void); }
在Python中,使用ctypes库,对应__cdecl的DLL使用ctypes.CDLL方法引用;对应__stdcall的DLL则使用ctypes.WinDLL方法引用DLL:
def python_get_dll(winDll=False): dllDir = "D:/PythonDll/Release/PythonDll.dll" if winDll: myDll = ctypes.WinDLL(dllDir) else: myDll = ctypes.CDLL(dllDir) return myDll
2. Python调用DLL中的函数
在DLL中实现在1中声明的函数dllDebug,如下:
int CALL_TYPE dllDebug(int a, int * iParam, char * oParam) { sprintf(oParam,"Input:[%d %d %d]",iParam[0], iParam[1], iParam[2]); return a + iParam[0]; }
此函数中,三个参数,分别是一个int,一个int指针(数组),一个char指针(数组);函数的功能是把int数组中的内容添加到一个字符串中并保存在char指针指向的内存中;函数的返回值是int参数和int数组第一个元素的和。
在Python中调用此函数的方法如下:
def python_call_dll(myDll): dllFunc = myDll.dllDebug dllFunc.argtypes = [ctypes.c_int,ctypes.POINTER(ctypes.c_int),ctypes.c_char_p] dllFunc.restype = ctypes.c_int outStrBuf=" "*200 intList=[1,2,3] inIntBuf=(ctypes.c_int * len(intList))(*intList) ret = dllFunc(100,inIntBuf,outStrBuf) print outStrBuf print "python_call_dll:",ret
其中,myDll是此前的ctypes.CDLL的实例。myDll中可以调用DLL中的接口函数,如调用dllDebug函数,使用dllFunc=myDll.dllDebug,此后 *** 作dllFunc即可。
之后要对函数的参数类型(argtypes)和返回值类型(restype)进行匹配,参数类型是int,int *, char *,对应ctypes.c_int, ctypes.POINTER(ctypes.c_int), ctypes.c_char_p。
由于C代码中,指针对应了数组,因此要在Python代码中开辟指针指向的内存空间。对应char * 可以直接用outStrBuf=” ”*200这种形式开辟。而对于int *,需要先定义一个Python列表,再通过ctypes把列表转换成C中能够识别的int数组空间。
运行Python得到的结果:
C:Python27python.exe "D:/VTSystem/Source Code/PyDll/PySrc/DllTest.py"
Input:[1 2 3]
python_call_dll: 101
3. DLL回调Python函数——注册
Python是胶水语言,业务中我们有时也会遇到DLL回调Python函数的时候。调用的方式与C语言的回调方式也类似。
首先我们在DLL中定义回调函数对应的函数指针和注册函数:
typedef void(*_callback_python_func)(int iParam, int * oParam); _callback_python_func python_function; void CALL_TYPE dllRegisterPythonFunc(void * pyFuncPtr) { python_function = (_callback_python_func)pyFuncPtr; }
可以看到这个函数指针对应的回调函数的参数是一个int和一个int数组。返回值是一个bool型。
注册函数“dllRegisterPythonFunc”在1中被声明,并能够被Python调用和传入用于回调的函数。
这里DLL的接口用了void *类型。
回到Python中,我们定义一个Python函数用于被DLL回调:
def dll_callback_func(intParam,intArray): intArray[0]=ctypes.c_int(intParam) return True
这个函数的参数和返回值类型要与DLL中的函数指针匹配。这个函数的功能是把int参数赋值在int数组参数的首个元素中,并返回true。
下一步进行注册:
gCallbackFuncList=[] def python_register_callback_func(myDll): callbackFunc= ctypes.CFUNCTYPE(ctypes.c_bool,ctypes.c_int,ctypes.POINTER(ctypes.c_int))(dll_callback_func) gCallbackFuncList.append(callbackFunc) myDll.dllRegisterPythonFunc(callbackFunc)
注册需要使用ctypes.CFUNCTYPE进行函数匹配,CFUNCTYPE的第一个参数是函数的返回值类型,之后才是函数的参数类型,这点要注意。
另外,将转换后的函数命名为“callbackFunc”,此时将其保存在了一个全局变量数组中,其原因是避免Python的回收机制将定义的回调函数释放。
最后,调用DLL的注册函数把Python中的函数dll_callback_func赋值给DLL中的函数指针python_function。
4. DLL回调Python函数——调用
为了验证回调功能,我们在DLL中制作了一个函数,函数声明在1中:
int CALL_TYPE dllCallbackPythonFunc() { int outBuf[100] = {0}; python_function(1,outBuf); return outBuf[0]; }
函数中定义了一个数组,并使用了函数指针最终调用Python中的dll_callback_func函数把outBuf的第一个元素赋值成1,因此最后此函数返回值应当为1。
我们在Python中调用此DLL中的函数:
def python_test_callback(myDll): dllFunc = myDll.dllCallbackPythonFunc dllFunc.argtypes = [] dllFunc.restype = ctypes.c_int ret = dllFunc() print "python_test_callback",ret
运行结果如下:
C:Python27python.exe "D:/VTSystem/Source Code/PyDll/PySrc/DllTest.py"
python_test_callback 1
5. Demo代码
DLL代码:
#include#include #include #define API_DLL __declspec(dllexport) #define CALL_TYPE __stdcall //#define CALL_TYPE __cdecl extern "C" { API_DLL int CALL_TYPE dllDebug(int a, int * iParam, char * oParam); API_DLL void CALL_TYPE dllRegisterPythonFunc(void * pyFuncPtr); API_DLL int CALL_TYPE dllCallbackPythonFunc(void); } int CALL_TYPE dllDebug(int a, int * iParam, char * oParam) { sprintf(oParam,"Input:[%d %d %d]",iParam[0], iParam[1], iParam[2]); return a + iParam[0]; } typedef void(*_callback_python_func)(int iParam, int * oParam); _callback_python_func python_function; void CALL_TYPE dllRegisterPythonFunc(void * pyFuncPtr) { python_function = (_callback_python_func)pyFuncPtr; } int CALL_TYPE dllCallbackPythonFunc() { int outBuf[100] = {0}; python_function(1,outBuf); return outBuf[0]; }
Python代码
import ctypes def python_get_dll(winDll=False): dllDir = "D:/DllSrc/PythonDll/Release/PythonDll.dll" if winDll: myDll = ctypes.WinDLL(dllDir) else: myDll = ctypes.CDLL(dllDir) return myDll def python_call_dll(myDll): dllFunc = myDll.dllDebug dllFunc.argtypes = [ctypes.c_int,ctypes.POINTER(ctypes.c_int),ctypes.c_char_p] dllFunc.restype = ctypes.c_int outStrBuf=" "*200 intList=[1,2,3] inIntBuf=(ctypes.c_int * len(intList))(*intList) ret = dllFunc(100,inIntBuf,outStrBuf) print outStrBuf print "python_call_dll:",ret def dll_callback_func(intParam,intArray): intArray[0]=ctypes.c_int(intParam) return True gCallbackFuncList=[] def python_register_callback_func(myDll): callbackFunc= ctypes.CFUNCTYPE(ctypes.c_bool,ctypes.c_int,ctypes.POINTER(ctypes.c_int)) (dll_callback_func) gCallbackFuncList.append(callbackFunc) myDll.dllRegisterPythonFunc(callbackFunc) def python_test_callback(myDll): dllFunc = myDll.dllCallbackPythonFunc dllFunc.argtypes = [] dllFunc.restype = ctypes.c_int ret = dllFunc() print "python_test_callback",ret if __name__ == '__main__': myDll=python_get_dll(True) python_call_dll(myDll) python_register_callback_func(myDll) python_test_callback(myDll)
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)