文章目录
ctypes
是 Python 的标准库,用于调用动态链接库的函数或访问变量。
bass.dll 是一个音频播放库,可以实现 .ogg 等音频类型的播放,也可以实现 MOD 类音频的播放,还可以录音。微小的 DLL 文件是它的优势之一,只有不到 100KB(不过在现在最新的 2.4 x86 版本中超过了一点)。老游戏常使用它来播放音乐,比如植物大战僵尸、宝石迷阵。
代码地址:仓库 的 bass 文件夹
动态链接库对象
ctypes
提供了 CDLL
和 WinDLL
这两个动态链接库类型。在 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 上还有 oledll
、windll
)这个 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 &