Python 通过 ctypes 使用动态链接库(以音频播放库 bass 为例)

ctypes 是 Python 的标准库,用于调用动态链接库的函数或访问变量。

bass.dll 是一个音频播放库,可以实现 .ogg 等音频类型的播放,也可以实现 MOD 类音频的播放,还可以录音。微小的 DLL 文件是它的优势之一,只有不到 100KB(不过在现在最新的 2.4 x86 版本中超过了一点)。老游戏常使用它来播放音乐,比如植物大战僵尸、宝石迷阵。

代码地址:仓库 的 bass 文件夹

动态链接库对象

ctypes 提供了 CDLLWinDLL 这两个动态链接库类型。在 Windows 上,CDLL 可能不可用,WinDLL 可用;在非 Windows 平台上,CDLL 可用,WinDLL 不可用。platform 模块提供了平台信息,可以用这些信息确定需要使用的动态链接库类型。例如,加载 bass 动态链接库可以用这样的代码:

from pathlib import Path
lib = Path(__file__).parent / 'lib' / ('x64' if platform.machine().endswith('64') else 'x86')

if platform.system().lower() == 'windows':
    from ctypes import WinDLL
    bass_module = WinDLL((lib / 'bass.dll').as_posix())
else:
    from ctypes import CDLL, RTLD_GLOBAL
    bass_module = CDLL((lib / 'libbass.so').as_posix(), mode=RTLD_GLOBAL)

ctypes 还提供了 cdll(在 Windows 上还有 oledllwindll)这个 DLL 加载器,用特定的形式加载 DLL。加载器提供了 LoadLibrary 这个方法,接受一个文件名,返回加载的 DLL。在 Windows 上可以使用 cdll.<模块名> 来表示 LoadLibrary('<模块名>.dll')

ctypes 类型

ctypes 提供了一系列类型,可以作为后面提到的函数参数使用。这些类型都对应着 C 语言中的类型,也对应着 Python 类型,如 ctypes.c_int 对应 C 的 int 类型,也对应 Python 整数;ctypes.c_char_p 对应 C 的 char * 和 Python 字节串对象;ctypes.c_void_p 对应 C 的 void*。在 ctypes.wintype 中提供了更多 Windows 特有类型,如 DWORD 双字、HANDLE 句柄。

ctypes 类型可以和 Python 类型相转化,如 c_int(100) 表示一个 C int 型变量,包含值 100。而 c_int(100).value 则是 Python 整型 100。

>>> from ctypes import *
>>> ci = c_int(10000)  # 没有移除检测
>>> ci  # 有的平台上 int 和 long 都是 32 位整型,这时 c_int 是 c_long 的别名
c_long(10000)
>>> ci.value is ci.value  # value 是一个数据描述器,每次使用时都会重新构建一个对象
False
>>> ci.value = 100
>>> ci
c_long(100)
>>> ci.value is ci.value  # 因为 Python 的小整数优化,每个 100 都是同一个启动时创建的对象
True
>>> s = c_char_p(b'Hello World')  # c_char_p 是以空字节结尾的字节串
>>> s.value
b'Hello World'
>>> s  # 输出的是这个字节串的内存地址,也是 s 这个指针的值
c_char_p(2789944353728)
>>> s.value = b'Hello'
>>> s  # 改变 value 会改变 s 的值
c_char_p(2789944355360)

ctypes.POINTER(T) 创建指向 T 的指针的类型。带参数调用指针类型会返回指向这个变量的指针。要创建 C 中的 &i,可以使用 ctypes.pointer(i)。如果不需要对指针进一步处理,可以使用更快的 ctypes.byref(i)。指针的 contents 属性是指针指向的内容(这也是和其它 ctypes 类型的 value 属性一样,构造了一个新的对象)。给 constants 赋值会改变指针指向的位置,而 不是 指针指向的内容。

>>> PI = POINTER(c_int)
>>> pi = PI(ci)
>>> pi.contents
c_long(100)
>>> pi.contents = c_int(200)  # 这会改变指针的值
>>> pi.contents
c_long(200)
>>> ci
c_long(100)

无参调用指针类型则会返回空指针(ctypes 会阻止空指针的访问,但对野指针的访问没有处理方法)。空指针的布尔值是 False

>>> NULL = PI()
>>> bool(NULL)
False
>>> NULL.contents
Traceback (most recent call last):
    ....
ValueError: NULL pointer access

cast(value, type) 函数可以强制把 value(它的类型必须是一种可以解读为指针的类型,如整型、c_void_p)转换成 type 类型(type 必须是指针类型),就像 C 的 (type)value

>>> void_p = cast(pi, c_void_p)
>>> void_p.value  # c_void_p 有 value 属性(是指针的值)但没有 contents 属性
3096050126856
>>> cast(pi, PI).contents
c_long(100)

数组类型用一个类型乘以一个非负整数创建。如 c_int * 5 就是含有 5 个元素,元素类型为 c_int 的数组。调用数组类型可以创建数组,并把参数复制到数组中(不足的地方补 0)。数组就像长度不可变的列表一样,可以读写元素。

>>> ARRAY = c_int * 5
>>> array = ARRAY(1, 2, 3)
>>> array[0]  # 元素已经被隐式转换成了 Python int 而不是 ctypes.c_int
1
>>> array[3]  # 未初始化的地方补零
0
>>> len(array)
5
>>> array[1:]  # 这会创建一个列表,而不是 C 中的 array + 1
[2, 3, 0, 0]
>>> pi = pointer(ci)
>>> pi[0]  # 就像 C 一样,指针也可以用下标运算符(但不能使用 len 函数)
100
>>> pi[0] = 300  # 这和 pi.contents = 300 不同:前者类似于 *pi = 300,
>>> ci           # 而后者类似于 int temp = 300; pi = &temp;
c_long(300)

C 中 typedef 是用类型别名实现的,define 一般可以用常量和函数实现。比如 bass.h 文件中 typedef HMUSIC DWORD 可以翻译成:

HMUSIC = DWORD

#define BASSVERSION 0x204#define LOBYTE(a) (BYTE)(a) 可以翻译成:

BASSVERSION = 0x204

def LOBYTE(a: int):
    return a & 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值