python通过ctypes调用dll

2014-08-12 (2014-08-27更新)

ctypes库简介

python调用c等语言的方法很多如,python c api, boost.python, Swig 等等。ctypes库可以通过一种较为简单的方法,在python中直接调用dll动态链接库,且ctypes是python默认已安装的一个标准库。

调用方法

python中如果要通过ctypes模块调用dll,在程序开头需要导入ctypes模块 import ctypes

由于调用约定的不同,python调用dll的方法也不同,主要有两种调用规则,即 cdecl和stdcal,还有其他的一些调用约定,关于他们的不同,可以查阅其他资料

假定有一个test.dll文件,含有int sum(int a, int b)函数,则调用方法为:

stdcal的调用方法

import ctypes
dll=ctypes.windll.LoadLibrary('test.dll')
n=dll.sum(1,2)
import ctypes
dll=ctypes.WinDll('test.dll')
n=dll.sum(1,2)
from cypes import *
dll=WinDll('test.dll')
sum=dll.sum
n=sum(1,2)

其中ctypes.windll为ctypes.WinDll类的一个对象,已经在ctypes模块中定义好的。

在linux中用相同的方法,只需把dll文件换成so文件

在windows下加载so文件或在linux下加载dll文件均无法运行

cdecl的调用方法

与stdcal调用方式相似

from ctypes import *
dll=CDll('test.dll')
n=dll.sum(1,2)

python与dll参数传递

python中仅有4个类型可以直接传递给dll函数

  • None:C语言的NULL指针
  • byte objects:ANSI字符串(char *)
  • string:unicode字符串(wchar_t *)
  • integer:C语言整形(int)

传递其他类型参数给dll函数,必须使用ctypes 数据类型

ctypes基础数据类型

ctypes定义了一系列类型用于兼容c数据类型,这些类型以c_开头

ctypes type C type Python type
c_bool _Bool bool (1)
c_char char 1-character bytes object
c_wchar wchar_t 1-character string
c_byte char int
c_ubyte unsigned char int
c_short short int
c_ushort unsigned short int
c_int int int
c_uint unsigned int int
c_long long int
c_ulong unsigned long int
c_longlong __int64 or long long int
c_ulonglong unsigned __int64 or unsigned long long int
c_size_t size_t int
c_ssize_t ssize_t or Py_ssize_t int
c_float float float
c_double double float
c_longdouble long double float
c_char_p char * (NUL terminated) bytes object or None
c_wchar_p wchar_t * (NUL terminated) string or None
c_void_p void * int or None

可以通过value属性修改ctypes类型数据的值

from ctypes import *
i=c_int(42)
i.value=40
c=c_char(b"x")
c.value=b"y"
s=c_wchar_p("hello")
s.value="hi"
f=c_double(4.3)

c_char_p和c_wchar_p是字符串指针,在改变value值时,相当于指向另一个地址,原字符串长度是不可变的,可变长度字符串需要用到create_string_buffer()或create_unicode_buffer()

from ctypes import *
p=create_string_buffer(b"hello")
p.value=b"hi"

ctypes复杂数据类型

数组

n=10
a=(c_double*n)()
a[0]=1

结构

from ctypes import *
class POINT(Structure):
    _fields_=[("x",c_int),
              ("y",c_int)]
pt1=POINT(10,20)

引用

用byref()函数可以实现传递引用参数

>>> i = c_int()
>>> f = c_float()
>>> s = create_string_buffer(b'\000' * 32)
>>> print(i.value, f.value, repr(s.value))
0 0.0 b''
>>> libc.sscanf(b"1 3.14 Hello", b"%d %f %s",
...             byref(i), byref(f), s)
3
>>> print(i.value, f.value, repr(s.value))
1 3.1400001049 b'Hello'

指针

指针也可以实现传递应用类型,但是指针比byref()功能更多,ctypes的Pointer()类型还可以在python中像数组一样访问

用contents属性可以访问指针内容

>>> from ctypes import *
>>> i=c_int(42)
>>> pi=pointer(i)
>>> pi.contents
c_long(42)
>>> j=c_int(99)
>>> pi.contents=j
>>> pi[0]
99

python调用dll返回值类型

python通过ctypes调用的dll函数返回值,被默认假定为

  • integer

如果返回值是其他类型,必须通过restype属性指定

>>> strchr = libc.strchr
>>> strchr(b"abcdef", ord("d")) 
8059983
>>> strchr.restype = c_char_p
>>> strchr(b"abcdef", ord("d"))
b'def'

python默认已加载库

kernel32, user32, msvcrt是python默认已加载的库,可以通过cdll.msvcrt.printf(), windll.kernel32.GetMessageBoxW()形式调用

msvcrt是ms标准c库函数

kernel32, user32内函数需要指明是A版(ANSI字符)还是W版(Unicode字符)windows接口。

GetSystemDirectory = windll.kernel32.GetSystemDirectoryA
buf = create_string_buffer(100)
GetSystemDirectory(buf,100)
print buf.value
MessageBox = windll.user32.MessageBoxW
MessageBox(None, u"Hello World", u"Hi", 0)

其他

通过ctypes使用回调函数,递归数据类型, 空指针等更多功能,可以参考python文档Generic Operating System Services中的ctypes部分

Fork me on GitHub