Python加载动态库主要用于使用C/C++弥补Python的性能,这个主题解决了Python调用动态库中函数与变量,这个使用Python的ctypes模块就可以实现,Darknet就是采用这种给方式。调用类复杂点,需要使用Cython编程,我们后面单独开一个主题来说明。

编译动态库

代码

头文件bmp.h
#ifndef YQ_BMP_H
#define YQ_BMP_H
// 导出变量
__declspec(dllexport) int counter = 0; // 位置在前面
// 导出函数
__declspec(dllexport) int getCount(int); // 位置在前面
// 实现一个基于BMP处理的类
class __declspec(dllexport) YQBmp{ // 位置在class关键字后,类名前
public:
YQBmp();
YQBmp(const char*);
void rotate(int);
virtual ~YQBmp();
private:
int fd; // 文件描述符
};
#endif
请关注下其中的变量、函数与类导出方式,这里没有采用def,而是直接使用最方便的方式,注意这种方式会使用修饰名。
实现文件bmp.cpp
#include "bmp.h"
#include 
int getCount(int num){
counter += num;
return counter;
}
YQBmp::YQBmp(){
std::cout << "默认文件!" << std::endl;
}
YQBmp::YQBmp(const char*filename){
std::cout << filename << std::endl;
}
void YQBmp::rotate(int){
std::cout << "图像的旋转处理!" << std::endl;
}
YQBmp::~YQBmp(){
std::cout << "释放" << std::endl;
}
编译脚本
我们使用了cl直接编译链接动态库:
@cl /EHsc /MD /utf-8 /nologo bmp.cpp /link /MACHINE:X64 /NOLOGO /DLL /OUT:libbmp.dll /IMPLIB:bmp.lib
dll: bmp.cpp bmp.h
@cl /EHsc /MD /utf-8 /nologo bmp.cpp /link /MACHINE:X64 /NOLOGO /DLL /OUT:libbmp.dll /IMPLIB:bmp.lib
clean:
@del *.obj *.dll *.pdb *.ilk *.exe *.lib *.exp 2>/Nul
查看dll文件
在python中,lib就基本上没有什么用了,这个文件是C/C++链接的时候,查找dll的符号信息。
C:\01works\13python\codes\python_dll>@dumpbin /exports libbmp.dll
Microsoft (R) COFF/PE Dumper Version 14.24.28319.0
Copyright (C) Microsoft Corporation. All rights reserved.
Dump of file libbmp.dll
File Type: DLL
Section contains the following exports for libbmp.dll
00000000 characteristics
FFFFFFFF time date stamp
0.00 version
1 ordinal base
9 number of functions
9 number of names
ordinal hint RVA name
1 0 00001560 ??0YQBmp@@QEAA@AEBV0@@Z
2 1 00001080 ??0YQBmp@@QEAA@PEBD@Z
3 2 00001030 ??0YQBmp@@QEAA@XZ
4 3 00001110 ??1YQBmp@@UEAA@XZ
5 4 00001780 ??4YQBmp@@QEAAAEAV0@AEBV0@@Z
6 5 00003240 ??_7YQBmp@@6B@
7 6 00005080 ?counter@@3HA
8 7 00001000 ?getCount@@YAHH@Z
9 8 000010D0 ?rotate@YQBmp@@QEAAXH@Z
Summary
1000 .data
1000 .pdata
2000 .rdata
1000 .reloc
2000 .text
Python调用动态库
Python库调用API结构
C类型封装与转换
_ctypes._SimpleCData(_ctypes._CData)
HRESULT
c_bool
c_byte
c_char
c_char_p
c_double
c_float
c_long
c_longlong
c_short
c_ubyte
c_ulong
c_ulonglong
c_ushort
c_void_p
c_wchar
c_wchar_p
py_object
核心类
builtins.object
|- CDLL
| |- OleDLL
| |- PyDLL
| |- WinDLL
|- LibraryLoader
核心类两个:
CDLL
LibraryLoader
C工具函数封装
ARRAY(typ, len)
CFUNCTYPE(restype, *argtypes, **kw)
DllCanUnloadNow()
DllGetClassObject(rclsid, riid, ppv)
FormatError(...)
POINTER(...)
PYFUNCTYPE(restype, *argtypes)
SetPointerType(pointer, cls)
WINFUNCTYPE(restype, *argtypes, **kw)
WinError(code=None, descr=None)
addressof(...)
alignment(...)
byref(...)
c_buffer(init, size=None)
cast(obj, typ)
create_string_buffer(init, size=None)
create_unicode_buffer(init, size=None)
get_errno(...)
get_last_error(...)
pointer(...)
resize(...)
set_errno(...)
set_last_error(...)
sizeof(...)
string_at(ptr, size=-1)
wstring_at(ptr, size=-1)
内置对象
DEFAULT_MODE = 0
GetLastError = <_funcptr object>
RTLD_GLOBAL = 0
RTLD_LOCAL = 0
cdll = 
memmove = 
memset = 
oledll = 
pydll = 
pythonapi = 
windll = 
加载动态库
CDLL的使用
CDLL帮助
from ctypes import CDLL
CDLL?
�[1;31mInit signature:�[0m �[0mCDLL�[0m�[1;33m(�[0m�[0mname�[0m�[1;33m,�[0m �[0mmode�[0m�[1;33m=�[0m�[1;36m0�[0m�[1;33m,�[0m �[0mhandle�[0m�[1;33m=�[0m�[1;32mNone�[0m�[1;33m,�[0m �[0muse_errno�[0m�[1;33m=�[0m�[1;32mFalse�[0m�[1;33m,�[0m �[0muse_last_error�[0m�[1;33m=�[0m�[1;32mFalse�[0m�[1;33m)�[0m�[1;33m�[0m�[0m
�[1;31mDocstring:�[0m
An instance of this class represents a loaded dll/shared
library, exporting functions using the standard C calling
convention (named 'cdecl' on Windows).
The exported functions can be accessed as attributes, or by
indexing with the function name. Examples:
.qsort -> callable object
['qsort'] -> callable object
Calling the functions releases the Python GIL during the call and
reacquires it afterwards.
�[1;31mFile:�[0m c:\program files\python36\lib\ctypes\__init__.py
�[1;31mType:�[0m type
�[1;31mSubclasses:�[0m PyDLL, WinDLL, OleDLL
help(CDLL)
Help on class CDLL in module ctypes:
class CDLL(builtins.object)
| An instance of this class represents a loaded dll/shared
| library, exporting functions using the standard C calling
| convention (named 'cdecl' on Windows).
|
| The exported functions can be accessed as attributes, or by
| indexing with the function name. Examples:
|
| .qsort -> callable object
| ['qsort'] -> callable object
|
| Calling the functions releases the Python GIL during the call and
| reacquires it afterwards.
|
| Methods defined here:
|
| __getattr__(self, name)
|
| __getitem__(self, name_or_ordinal)
|
| __init__(self, name, mode=0, handle=None, use_errno=False, use_last_error=False)
| Initialize self. See help(type(self)) for accurate signature.
|
| __repr__(self)
| Return repr(self).
|
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)

构造器的第二个参数handle可以指定两种方式: (目测效果一样)

RTLD_GLOBAL = 0

RTLD_LOCAL = 0

加载后的动态库对象,使用下标方式获取:

下标方式是动态库中导出对象(变量,函数,类)的序号。

CDLL使用例子

加载库并访问导出对象

两个操作

创建库;

遍历库对象;

from ctypes import CDLL
obj_dll = CDLL("codes/python_dll/libbmp.dll")
print(obj_dll[1])
print(obj_dll[2])
help(obj_dll[2])
Help on _FuncPtr in module ctypes object:
class _FuncPtr(_ctypes.PyCFuncPtr)
| Function Pointer
|
| Method resolution order:
| _FuncPtr
| _ctypes.PyCFuncPtr
| _ctypes._CData
| builtins.object
|
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)
|
| ----------------------------------------------------------------------
| Methods inherited from _ctypes.PyCFuncPtr:
|
| __bool__(self, /)
| self != 0
|
| __call__(self, /, *args, **kwargs)
| Call self as a function.
|
| __new__(*args, **kwargs) from _ctypes.PyCFuncPtrType
| Create and return a new object. See help(type) for accurate signature.
|
| __repr__(self, /)
| Return repr(self).
|
| ----------------------------------------------------------------------
| Data descriptors inherited from _ctypes.PyCFuncPtr:
|
| argtypes
| specify the argument types
|
| errcheck
| a function to check for errors
|
| restype
| specify the result type
|
| ----------------------------------------------------------------------
| Methods inherited from _ctypes._CData:
|
| __ctypes_from_outparam__(...)
|
| __hash__(self, /)
| Return hash(self).
|
| __reduce__(...)
| helper for pickle
|
| __setstate__(...)
上面使用序号与导出名都可以。
名字在编译的时候被修饰了。
使用导出的函数
from ctypes import CDLL
obj_dll = CDLL("codes/python_dll/libbmp.dll")
re = obj_dll[8](55)
print(re)
re = obj_dll[8](20)
print(re)
re = obj_dll[8](15)
print(re)
re = obj_dll['?getCount@@YAHH@Z'](1000)
print(re)
290
310
325
1325
使用导出的变量
from ctypes import CDLL, c_long, py_object, cast, POINTER,c_int
obj_dll = CDLL("codes/python_dll/libbmp.dll")
re = obj_dll[8](55)
print(re)
print(c_int.in_dll(obj_dll, "?counter@@3HA").value)
1375
1375
from ctypes import CDLL, c_long, py_object, cast, POINTER,c_int
help(c_int.in_dll)
Help on built-in function in_dll:
in_dll(...) method of _ctypes.PyCSimpleType instance
C.in_dll(dll, name) -> C instance
access a C instance in a dll
关于每个类型的in_dll函数
每个类型在内置的_CData类中提供如下函数:
使用help查找不到,在官方文档有介绍。
class _CData(metaclass=_CDataMeta):
_b_base: int = ...
_b_needsfree_: bool = ...
_objects: Optional[Mapping[Any, int]] = ...
@classmethod
def from_buffer(cls: Type[_CT], source: bytearray, offset: int = ...) -> _CT: ...
@classmethod
def from_buffer_copy(cls: Type[_CT], source: bytearray, offset: int = ...) -> _CT: ...
@classmethod
def from_address(cls: Type[_CT], address: int) -> _CT: ...
@classmethod
def from_param(cls: Type[_CT], obj: Any) -> _UnionT[_CT, _CArgObject]: ...
@classmethod
def in_dll(cls: Type[_CT], library: CDLL, name: str) -> _CT: ...

使用导出的类

实际上ctypes主要用来调用函数库,对类的调用,缺乏支持。

因为类的成员函数与对象栈之间目前没有支持(在C++中可以通过汇编切换寄存器来实现对象栈的调用,VSx64位目前不支持汇编)。

为了使用DLL导出的类,直接使用行不通,我们使用Cython编程来扩展。