ctypes 调用 dll
1. 加载 Windows API 和 C 运行库
先看例子
from ctypes import *
u32 = windll.LoadLibrary('user32.dll') #加载user32.dll
u32.MessageBoxW(0, u'内容', u'标题',0)
crt = cdll.LoadLibrary('msvcrt.dll') #加载C运行库
crt.printf('hello world !\n')
调用 C 库用 cdll,而调用 Windows API 用 windll。
之所以有 cdll 和 windll 的区别,是因为 Windows API 和 C 库函数的调用规范不一样,前者遵从的是 __stdcall,后者是 __cdecl。
__cdecl 是 C Declaration 的缩写,表示 C 编译器默认的函数调用规范,这种方式下由调用者负责清理参数栈。比如在VC中,如果函数前面不加任何修饰,默认的就是 __cdecl。
__stdcall 是 Standard Call 的缩写,这种方式下被调的函数自己负责清理参数栈。在 VC 中,如果函数前有 __stdcall 修饰的话,编译出来的就是这种类型。
Windows API 全部采用的是 __stdcall 。因为 Windows API 要被各种编译器下的各种语言调用,如果采用 __cdecl 的方式,由于各种编译器中使用的栈结构也许并不相同,所以就不敢保证其每种编译器都能正确清理 Windows API 产生的参数栈。而采用 __stdcall 的方式,由被调用者自己清理参数栈,就能避免了以上麻烦。
在 Windows 环境,LoadLibrary() 的参数可以不包括扩展名。还有一种更简短的写法,比如下面三个是等效的:
from ctypes import *
windll.LoadLibrary('user32.dll').MessageBoxW(0, u'内容', u'标题',0)
windll.LoadLibrary('user32').MessageBoxW(0, u'内容', u'标题',0)
windll.user32.MessageBoxW(0, u'内容', u'标题',0)
下面三个也一样
from ctypes import *
cdll.LoadLibrary('msvcrt.dll').printf('hello world !\n')
cdll.LoadLibrary('msvcrt').printf('hello world !\n')
cdll.msvcrt.printf('hello world !\n')
2. 隐式类型转换
int、str、unicode,这三种类型是可以直接作为参数传递的,不用指明对应的C类型
from ctypes import *
cdll.msvcrt.printf('%d, %s\n', 100, 'abc')
3. 显式类型转换
int、str、unicode 之外的类型在作为参数时要做显式类型转换,指出对应的C类型
from ctypes import *
cdll.msvcrt.printf('%.2f\n', c_double(3.14))
4. 指针和引用
byref() 可以引用内存地址,相当于 C 的 &
from ctypes import *
i = c_int(0)
cdll.msvcrt.sscanf('100', '%d', byref(i))
print i.value
pointer() 也可以达到相同的效果
from ctypes import *
i = c_int(0)
cdll.msvcrt.sscanf('100', '%d', pointer(i))
print i.value
二者的区别在于,pointer() 会构造一个指针对象,而 byref() 只是一个函数,所以 byref() 的开销较小。
如果只是用于传递参数,那么用 byref() 就足够了。
5. 字符串指针
直接用 create_string_buffer() 和 create_unicode_buffer()
from ctypes import *
s = create_string_buffer('abc') #初始值abc
cdll.msvcrt.sscanf('def', '%s', s)
print s.value #现在是def
6. 回调函数
为了测试回调函数,先编写一个 test.dll,下面是 C 代码
#include <stdio.h>
typedef void __stdcall (*MyCallback)(int);
__declspec(dllexport) void __stdcall TestMyCallback( int i, MyCallback mycallback)
{
printf("TestMyCallback:%d\n", i);
mycallback(i*10);
}
上面这个函数接受两个参数,第一个是整型变量 i,第二个是一个回调函数。
首先打印变量 i,然后再把 i*10 传给回调函数。
( __declspec(dllexport) 关键字用于从dll导出函数,详见这里 )
下面的Python代码中实现了回调函数,并传给了test.TestMyCallbackPython
from ctypes import *
def mycallback( i ):
print 'mycallback:', i
c_mycallback = WINFUNCTYPE(None, #返回值类型 None 代表 void
c_int #第一个参数类型 int
)(mycallback) #括号内是需要被包装的Python函数
windll.test.TestMyCallback(100, c_mycallback)
WINFUNCTYPE 的用法实际包括两个步骤:
- 首先要声明一个 C 函数原型
c_function_type = WINFUNCTYPE(返回值类型,第一个参数类型,第二个参数类型...)
- 然后再用这个原型包装 Python 函数
c_function = c_function_type(Python函数)
WINFUNCTYPE 遵守 __stdcall 调用规范,对应的 __cdecl 版本是 CFUNCTYPE