PY08-04:Python加载动态库


  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
  • 核心类两个:
    1. CDLL
    2. 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使用例子

加载库并访问导出对象

  • 两个操作
    1. 创建库;
    2. 遍历库对象;
from ctypes import CDLL

obj_dll = CDLL("codes/python_dll/libbmp.dll")
print(obj_dll[1])
print(obj_dll[2])
help(obj_dll[2])
<_FuncPtr object at 0x000001E1C0D67BA8>
<_FuncPtr object at 0x000001E1C0D67C78>
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编程来扩展。


你可能感兴趣的:(PY08-04:Python加载动态库)