Python之点到为止: 调用动态链接库(DLL)

2020年04月01日 5952点热度 1人点赞 0条评论

在Windows编程中, 免不了与动态链接库打交道. 它可以试得程序模块化, 方法重用的等等. 对, 类似Python中的模块.

使用环境

整理了一下几种Python会调用动态链接库的环境, 毕竟这种情况很少. 一般多出在旧项目改造.

  1. 没有DLL源码.
  2. 不会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')

分为四组, 组内两条语句结果是一样得. 我们就按照四组来对比说一下.

  1. WinDLL
  2. OleDLL
  3. CDLL
  4. 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

文章评论