Python之点到为止: 调用动态链接库(DLL)
在Windows编程中, 免不了与动态链接库打交道. 它可以试得程序模块化, 方法重用的等等. 对, 类似Python中的模块.
使用环境
整理了一下几种Python会调用动态链接库的环境, 毕竟这种情况很少. 一般多出在旧项目改造.
- 没有DLL源码.
- 不会C/C++
- 懒
第二种情况是不推荐使用这种方法调用已经存在的dll文件, 除非那个DLL文件内部函数很简单, 不涉及到什么指针一类的东西(不是python无法完成这样的操作, 而是对不懂C/C++的童鞋来说无论是理解还是操作都比较痛苦).
相关库
毕竟Python是跨平台的, 所以操作dll自然不在话下. 这要归功于ctypes这个库.
ctypes
是 Python 的外部函数库。它提供了与 C 兼容的数据类型,并允许调用 DLL 或共享库中的函数。可使用该模块以纯 Python 形式对这些库进行封装。
关于调用DLL的方式:
import ctypes
ctypes.windll.LoadLibrary('d.dll')
ctypes.WinDLL('d.dll')
ctypes.oledll.LoadLibrary('d.dll')
ctypes.OleDLL('d.dll')
ctypes.cdll.LoadLibrary('d.dll')
ctypes.CDLL('d.dll')
ctypes.pydll.LoadLibrary('d.dll')
ctypes.PyDLL('d.dll')
分为四组, 组内两条语句结果是一样得. 我们就按照四组来对比说一下.
- WinDLL
- OleDLL
- CDLL
- PyDLL
其中1, 2只能在Windows上使用, 3,4则是跨平台的. 这别蒙也别弄混, 其他系统中. 比如Linux上使用的动态链接库不是 .dll 文件, 而是 .so 文件. 然而在Windows环境中这四种都可以使用, 1234之间明显区别除了跨平台那就是, cdll加载使用标准cdecl
调用约定导出函数的库,而windll、oledll库使用stdcall
调用约定调用函数。而pydll与cdll相同.
PS: cdecl
就是标准的C/C++标准, stdcall
常用于Win32. 如果不懂先不用了解, 后续可以百度看一下差别. 如果需要深入使用那么这个概念是要了解的。
1和2的区别是OleDLL会返回HRESULT代码, HRESULT就是一个用于描述错误或警告的32位值. 体现了DLL是否在正确就加载信息. 而WinDLL则作为标准输出来显示错误.
那么至于PyDLL与其他三种的区别就是, 其他三种在调用由这些库导出的任何函数之前释放Python 全局解释器锁, 然后重新获取它. 而PyDLL并且在函数执行后检查了Python错误标志. 如果设置了错误标志, 则会引发Python异常. (ps:关于这个类, 没用过有没有了解的大神说一下在什么情况下使用这个类.)
PS: 这部分信息有些硬核, 对于Python的全局解释锁(GIL)就不深入了, 有兴趣可以单独拿出来讲讲.
实操
准备了两个简单的DLL文件>>>点击下载
先用x86.dll举例子, 等下再说这两个有什么区别. 打开Python Shell, 或者编写个脚本自己测试怎么方便怎么来.
Python 3.8.1 (default, Mar 2 2020, 13:06:26) [MSC v.1916 64 bit (AMD64)] :: Anaconda, Inc. on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import ctypes
>>> dll = ctypes.CDLL(r"C:\Users\Virace\Downloads\x86.dll")
有些心急的怕是已经在解释器中敲上代码了, BUT, 这段代码在大部分人的环境下因该会报错像这样.
解释一下为什么: 64位程序无法直接调用32位dll, 至于为什么百度去了解吧. 回过头看下我发的代码, python信息输出是不是有个 64 bit, 这回了解为什么准备两个dll了吧. 当然实际使用对于前面说的两种情况(没有源码和不懂C), 只能切换Python.
回到代码, 选择好正确得方式后你就可以加载dll了. 这两个dll只增加了三个简单的函数: add 、sub、msgbox , 下面是C源码.
extern "C" __declspec(dllexport) int add(int a, int b)
{
return (a + b);
}
extern "C" __declspec(dllexport) int sub(int a, int b)
{
return (a - b);
}
extern "C" __declspec(dllexport) int msgbox(LPCWCH title, LPCWCH msg)
{
return MessageBox(0, msg, title, 0);
}
那么也知道怎么选择DLL或者Python位数了, 也知道DLL公开函数了. 下面就是使用方法了.
import ctypes
dll = ctypes.CDLL(r"C:\Users\Virace\Downloads\x64.dll")
print(dll.add(22, 33))
print(dll.sub(22, 33))
dll.msgbox('这里是标题', '这里是信息提示.')
总结
调用其实很简单, 但是如果不知道DLL有不同调用协议, 也不了解x64无法调用x32dll的话, 会是个大坑. 当你连位数都不清楚的时候当然也就想不到这方面的问题.
哦对了, 我的测试环境Python环境为3.8. 如果使用老版本可能会与文中不符, 那只能查阅文档了(貌似一些旧版本还不支持中文路径dll导入??? 3.8是没有这种情况).
这期文章虽然篇幅很长, 但还是点到为止系列. 至于为什么...... 小伙子~悟去吧.
那么我就点到为止了
Just give a hint.
相关链接: 腾讯开发者手册(ctypes): https://cloud.tencent.com/developer/section/1370537 官方文档(ctypes): https://docs.python.org/zh-cn/3.8/library/ctypes.html
文章评论