ctypes是Python的一个外部库,提供和C语言兼容的数据类型,可以很方便地调用DLL中输出的C接口函数。

1.加载dll和取出函数

from ctypes import *
dll = cdll.LoadLibrary(dllpath)   #dllpath是字符串
dll = windll.LoadLibrary(dllpath)

上面两行使用哪一行,取决于导出函数的调用规范(cdecl或stdcall).也可以使用下面两行代替:

dll = CDLL(dllpath)    #注意和上面大小写的区别
dll = WinDLL(dllpath)

注意,这里使用的dll必须和python平台匹配,比如都是32位的或者都是64位的。因为本质上是一个exe加载一个dll,无法跨平台。

加载dll后,可直接得到dll中的导出函数地址.

func = dll.func_name  #func_name 是dll的导出函数

有时动态链接库导出c++函数时,并不是有效的Python标识符,例如 “??2@YAPAXI@Z” 。这种情况下,必须使用getattr 获取函数:

func = getattr(cdll.msvcrt,"??2@YAPAXI@Z")

在Windows上,有些动态链接库导出函数不是用名字,而是用序号(ordinal)。这时需通过索引获取:

func = cdll.kernel32[1]

2.函数参数和返回值

上面只是得到了函数地址,还无法进行函数调用.要进行正确的函数调用,需设置好参数和返回值类型.
ctypes支持的原生数据类型如下:

ctypes类型 C 类型 Python 类型
c_char char 1-character string
c_wchar wchar_t 1-character unicode string
c_byte char int/long
c_ubyte unsigned char int/long
c_bool bool bool
c_short short int/long
c_ushort unsigned short int/long
c_int int int/long
c_uint unsigned int int/long
c_long long int/long
c_ulong unsigned long int/long
c_longlong __int64 or longlong int/long
c_ulonglong unsigned __int64 or unsigned long long int/long
c_float float float
c_double double float
c_longdouble long double float float
c_char_p char * string or None
c_wchar_p wchar_t * unicode or None
c_void_p void * int/long or None

设置函数的参数类型使用函数的argtypes属性,直接赋值为一个ctypes类型的列表或元组。设置函数的返回值类型使用函数的restype属性。下面是示例代码:
python中,默认函数返回值是c_int型,此类型可以不用显示设置函数的restype属性,如果是参数类型是c_int型则需要设置。

fun.argtypes = (c_int, c_int,c_int,c_void_p) #设置函数参数类型为 int,int,int,void *
fun.restype  = c_float #设置返回值类型为 float

None、整数、字节串和(unicode)字符串是可以作为本地Python对象直接传递给函数调用的。

  • None是作为C的NULL指针传递。
  • 字节串和字符串作为内存块指针传递(char* 或 wchar_t*)。
  • Python整数作为平台相关的C语言int类型传递,其值会截断到C类型。

除了整数、字节串和字符串以外Python类型的参数传递,必须使用ctypes类型做包装。

在调用函数时,如果使用了错误的参数数量和调用规范时,ctypes尝试保护调用。不幸的是该功能仅在Windows上有用。它通过检查函数返回栈来实现,所以尽管发生了错误,但是函数还是调用了。
这很容易导致当前使用的整个Python环境崩溃,所以必须很小心的使用。

除了上述的基本类型,ctypes还支持自定义的结构体和联合体,它们可以出现在函数的参数或返回值中。

3.结构体

自定义的结构体和联合体必须继承自ctypes的Structure和Union,这两个类都在ctypes模块中定义。每一个子类必须定义"_fields_“属性,”_fields_"是一个二维的tuples列表,
描述类的每个数据成员的字段名和字段类型,这里的字段类型必须是一个ctypes类型,如c_int,或者任何其他的继承ctypes的类型,如Structure, Union, Array, 指针等。

例如有一个简单结构,包含两个整型x和y,可如下初始化一个结构:

from ctypes import *
import types  class Point(Structure):  _fields_ = [('x', c_int),  ('y', c_int)]
p1 = Point(1,2)
print(point.x, point.y) #输出 1 2#可以创建复杂的结构体,嵌套了其它结构体。如下:
class RECT(Structure):_fields_ = [("upperleft", POINT),("lowerright", POINT)]rc = RECT(p1)
print(rc.upperleft.x, rc.upperleft.y)   #输出 1 2
print(rc.lowerright.x, rc.lowerright.y) #输出 0 0#嵌套结构体可以通过下面多种方法初始化:
rc2 = RECT(POINT(1,2), POINT(3,4))
rc3 = RECT((1,2), (3,4))

如结构体用于链表操作,即包含指向结构体指针时,若直接定义:

from ctypes import *
import types  class Test(Structure):  _fields_ = [('x', c_int),  ('y', c_char),  ('next', Test)]   #这一行报错

则python会报错type未定义,如下定义则OK:

from ctypes import *
import types  class Test(Structure):  pass  Test._fields_ = [('x', c_int),  ('y', c_char),  ('next', POINTER(Test))]

字节对齐和字节顺序

默认情况下结构体和联合的对齐使用C编译器相同的方式。这可以通过类属性_pack_ 来重载其行为。这必须设置一个正数指定字段的最大对齐。这个功能与MSVC中的 #pragma pack(n) 功能一样。

ctypes中的结构体和联合使用本地字节序。想要用非本地字节序,可以使用 BigEndianStructure 、LittleEndianStructure 、 BigEndianUnion 、 LittleEndianUnion 基类。这些类无法包含指针字段。

位域

创建结构与联合体时,可以包含位域字段。只有整型域才可以使用位字段,位宽可以在_fields_元组的第三个选项中指定:

class Int(Structure):_fields_ = [("first_16", c_int, 16),   #这个字段占16位("second_16",c_int, 16)]

4.数组

数组就是序列,包含固定数量的相同类型的实例。推荐的创建数组类型的方式是使用正数和乘号应用到类型:

from ctypes import *class POINT(Structure):_fields_ = [("x", c_int), ("y", c_int)]TenPointsArrayType=POINT*10   #创建一个数组类型,它是10个Point元素组成的数组。class MyStruct(Structure):_fields_ = [("a", c_int),("b", c_float),("pts", POINT*4)] # 相当于C语言的:  POINT pts[4]print(len(MyStruct().point_array)) #输出  4arr = TenPointsArrayType()  #创建一个数组类的对象。
for pt in arr:print(pt.x, pt.y)TenIntegers = c_int*10  # 定义一个int[10]的类型(类)ii = TenIntegers(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) #显式初始化数组(类)
for i in ii: print(i)#高维数组,即数组的数组。
type_int_array_10 = c_int * 10                  #先定义一个数组类型
type_int_array_10_10 = type_int_array_10 * 10   #定义数组的数组(即二维数组)
my_array = type_int_array_10_10()               #创建二维数组对象
my_array[1][2] = 3                              #使用二维数组

5.指针和引用

有时C函数需要一个指针指向的数据作为参数,或想向指针指向的内存块写输出数据,或者数据太大不适合传递,这时就需要使用指针或者引用。

ctypes中使用POINTER和pointer表示指针,在使用POINTER时需要设置指向的数据类型,而pointer则直接从变量中得到一个特定类型的指针。

ctypes使用 byref() 函数表示指针概念,该函数也是直接从变量得到指针指向的数据类型。

对内存块的使用,实际上是区分输入/输出的。如果是该内存块是函数的输入(即函数内部是从内存块读数据),则使用指针,即POINTER() 或 pointer()。
如果是该内存块是函数的输出(即函数内部是写数据到内存块),则需使用create_string_buffer() 函数。

指针

例如创建一个类似于C语言的int *:

type_p_int = POINTER(c_int)  #创建指针类型,它指向整数
v = c_int(4)                 #定义一个整数,值为4.
p_int = type_p_int(v)        #给一个指针变量(p_int)赋值(为变量v的地址).print(p_int[0])
print(p_int.contents) #指针实例有一个contents属性,返回这个指针所指向的对象。

上面这段代码在C语言里相当于:

typedef int * type_p_int;
int v = 4;
type_p_int p = &v;
printf("%d",p[0]);
printf("%d",*p);

也可以不经过声明指针类型这一步,直接从变量得到指针,如下:

v = c_int(4)        #定义一个整数,值为4.
p_int = pointer(v)  #直接得到v的指针,不需创建指针类型(省去类型声明)。print(p_int[0])
print(p_int.contents)

注意:对指针类型 c_char_p,c_wchar_p,c_void_p 的赋值将会改变其指向的内存区域地址,而不是改变内存块的值(因为Python字符串是只读的)。

byref()

ctypes使用 byref() 函数传递参数引用。通常使用 byref()的地方同样也可用指针函数pointer(),但pointer()作为参数通常会额外创建一个指针对象,如果并不需要再次使用该指针对象的话,使用 byref() 会更快。

内存块

各种指针类型(c_char_p,c_wchar_p,c_void_p)指向的内存块实际上都是只读的。如果某个函数需要一个输入内存块保存输出值,不能传递这些指针。我们需要一个可写的内存块,使用create_string_buffer() 函数创建。

from ctypes import *p = create_string_buffer(3)      #创建3字节长的buf,且初始化为0
print(sizeof(p), repr(p.raw))    #输出 3 '\x00\x00\x00'p = create_string_buffer(""Hello")#创建一个字符串(包括结尾的0)的buf
print(sizeof(p), repr(p.raw))    #输出 6 'Hello\x00'p = create_string_buffer("Hello", 10)
print(sizeof(p), repr(p.raw))    #输出 10 'Hello\x00\x00\x00\x00\x00'p.value = "Hi"                   #修改buf内容(这是可变buf)
print(sizeof(p), repr(p.raw))    #输出10 'Hi\x00lo\x00\x00\x00\x00\x00'pw = create_unicode_buffer(3)    #创建一个unicode使用的buf,且初始化为0
printf(sizeof(pw))               #注意,这里将输出6。

想要创建包含unicode字符(对应C类型wchar_t)的可变内存块,使用create_unicode_buffer() 函数。

6.类型转换

通常情况下,ctypes会做严格的类型检查。这意味着,如果形参有一个POINTER(c_int)指针指向一个函数或者结构体的成员域类型,那么实参只能接受相同类型的实例。
但这个规则也有例外。比如,你可以传递兼容的数组类型来代替指针类型。例如对于POINTER(c_int)指针类型来说,可以使用c_int数组来代替。

class Bar(Structure):_fields_ = [("count", c_int), ("values", POINTER(c_int))]bar = Bar()
bar.values = (c_int * 3)(1, 2, 3)  #数组和指针的转化
bar.count = 3
for i in range(bar.count):print(bar.values[i])
#输出 1 2 3bar.values = None #设置指针为NULL

如果一个函数参数显式声明为某种指针类型(例如POINT(c_int) 类型),则传递该指针指向的对象类型也是可以的(例如这里可以传递c_int),ctypes会自动加上byref()函数进行类型转换。

在C语言中,你可以通过强制类型转换的方法来转换不兼容的类型。ctypes也提供了一个转换函数cast() 让你可以使用相同的方式进行类型转换。
cast()函数可以将一个ctypes指针(或数组)的实例转换成另外一个不同的指针类型(或数组)。cast()函数需要两个参数,第一个是转换前的指针实例
第二个是目标指针类型。它返回第二个参数类型的实例,并且这个实例与第一个参数共用同一块内存。

上面定义的Bar结构体中,它的value域可以支持POINTER(c_int)指针或者c_int数组,但不支持其他类型,如果需要其它类型,则可使用类型转换。

bar.values = (c_byte * 4)()  #报错,类型不对。需要 int * 或 int 数组。这里是byte数组。bar.values = cast((c_byte * 4)(), POINTER(c_int))  #正确。强制转换,把byte数组转化为int*print(bar.values[0])

7.回调函数

types允许从python回调中创建c回调函数指针。这个常常被称为回调函数。

首先,你必须为回调函数创建一个类,这个类知道调用协议,函数返回值类型,函数接受的参数个数及类型。

CFUNCTYPE工厂函数使用普通cdecl调用协议来为回调函数创建类型。并且,在Windows平台,WINFUNCTYPE工厂函数使用stdcall调用协议来为回调函数创建类型。
这两个工厂函数在调用时,参数表都是使用返回值作为第一个参数,而将回调函数所需要的参数作为剩下的参数。

在这里我将使用一个c标准库里的快排函数作为演示例子,快排是一个借助回调函数进行排序的函数。快排将会用到下面的整型数组:


CMPFUNC = CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int)) #定义回调函数类型def py_cmp_func(a, b): #实现回调函数功能print("py_cmp_func", a[0], b[0])return a[0] - b[0]cmp_func = CMPFUNC(py_cmp_func)  #回调函数对象IntArray5 = c_int*5
ia = IntArray5(5, 1, 7, 33, 99)
qsort = libc.qsort      #排序函数地址
qsort.restype = None    #排序函数返回值qsort(ia, len(ia), sizeof(c_int), CMPFUNC(py_cmp_func)) #调用排序函数,传入一个回调函数对象.for i in ia: print(i, end=" ")   #输出结果为: 1 5 7 33 99

回调函数的重要提示:

确保你在C代码的使用生命周期里保持引用CFUNCTYPE对象。ctypes并不会帮你做这样的事情,如果你没有做保证,它们就会被垃圾回收,然后当你调用这个回调函数时将会导致程序崩溃。

8.windows类型

Windows API有一些特殊之处,Windows API函数不使用标准C的调用约定(前面已经提到过)。

因此需注意两点:

  • LoadLibrary时不能够使用cdll.LoadLibrary而使用windll.LoadLibrary。
  • 在声明回调函数指针类型的时候,不能用CFUNCTYPE而是用WINFUNCTYPE。

Windows API有很多内建类型,ctypes内部都已经定义好了,在子模块wintypes下,可以直接使用。

代码举例如下:

from ctypes import *
from ctypes import wintypes# HWND 等类型已定义好了,可直接使用.
WNDENUMPROC = WINFUNCTYPE(wintypes.BOOL,    #定义回调函数类型wintypes.HWND,wintypes.LPARAM)def EnumWindowsProc(hwnd, lParam):  #实现回调函数功能length = user32.GetWindowTextLengthW(hwnd) + 1buffer = create_unicode_buffer(length)user32.GetWindowTextW(hwnd, buffer, length)print(buffer.value)return Trueuser32 = windll.LoadLibrary('user32.dll')  #加载dll
user32.EnumWindows(WNDENUMPROC(EnumWindowsProc), 0)

8.结构体指针

返回结构体指针的函数. c代码如下:

include <stdlib.h>
typedef struct{undefinedint a;int b;
}mystruct;mystruct * create(){undefinedmystruct * s = (mystruct *)calloc(1, sizeof(mystruct));s->a = 100;s->b = 200;return s;
}void destroy(mystruct * s){undefinedfree(s);
}

python代码如下:

from ctypes import *
class mystruct(Structure):_fields_ = [('a', c_int),('b', c_int)]dll = cdll.LoadLibrary(dllpath)
dll.create.restype = POINTER(mystruct) #设置返回值类型为结构体指针
p = dll.create() #调用函数
print(p.contents.a,p.contents.b) #输出 100 200

如果返回的是结构体数组指针,同样也是将函数返回类型设置为结构体指针,在接收到值之后,使用时加上下标即可,注意是结构体数组指针,不是结构体指针数组
python代码如下:

from ctypes import *
class mystruct(Structure):_fields_ = [('a', c_int),('b', c_int)]dll = cdll.LoadLibrary(dllpath)
dll.create.restype = POINTER(mystruct) #设置返回值类型为结构体指针
p = dll.create() #调用函数
print(p[i].a,p[i].b) #注意是结构体数组指针,不是结构体指针数组,不然应该还需要

##如果是向函数内部传入一个结构体数组指针,而函数需要改变此结构体的内存,则可以作如下处理:

返回结构体指针参数的函数. c代码如下:

include <stdlib.h>
typedef struct{undefinedint a;int b;
}mystruct;int change(mystruct*stru,int*num){undefinedfor(i=0;i < 3;i++){undefinedstru[i].a = i;stru[i].b = i+1;}int n_num = 2;num = &n_num; return 0;
}

python代码如下:

from ctypes import *
class mystruct(Structure):_fields_ = [('a', c_int),('b', c_int)]
dll = cdll.LoadLibrary(dllpath)
stru_info= create_string_buffer(sizeof(mystruct) * NUM)
p_rec = POINTER(mystruct)(stru_info)
info_num = c_int()
ret = dll.create(p_rec, byref(info_num)) #调用函数
print(p_rec[i],p_rec[i].b)**同理,如果创建int类型的数组内存,则可以用
int_buffer = create_string_buffer(sizeof(c_int) * NUM)
p_int_buffer = POINTER(c_int)(int_buffer)
传递参数时直接使用p_int_buffer
使用时直接p_int_buffer[i]取值即可##如果是向函数内部传入一个结构体数组指针,而函数不需要改变此结构体的内存,则可以作如下处理:
c代码如下:
```c
include <stdlib.h>
typedef struct{undefinedint a;int b;
}mystruct;int change(mystruct*stru,int num){undefinedfor(i=0;i < num;i++){undefinedcout<<stru[i].a<<endl;cout<<stru[i].b<<endl;}return 0;
}

python代码如下:

from ctypes import *
class mystruct(Structure):_fields_ = [('a', c_int),('b', c_int)]
dll = cdll.LoadLibrary(dllpath)
info_num = 3
stru_info_list = []#添加一些结构体,此处略去
stru_info_p = (mystruct*info_num)(*stru_info_list)
num = c_int(info_num)
ret = dll.create(byref(strstru_info_p), num) #调用函数
print(p_rec[i],p_rec[i].b)同理,如果只是传递一个数组指针,不需要改变内容的话,假设传第一个int数组:
int_list = []
num = len(int_list)
p_int_list = (c_int*num)(*int_list)####9.解析
上述例子中,如果创建int类型的数组内存
假设c函数是int fun(int*buffer_list)#传递的是int数组的指针int_buffer = create_string_buffer(sizeof(c_int) * NUM)
如果直接传递的参数为byref(int_buffer)
即:ret = dll.fun(byref(int_buffer))那么对于int_buffer的使用就需要解析
首先需要取int_buffer得二进制内容,即int_buffer.raw,然后解析
buffer = int_buffer.raw
buffer_int = (struct.unpack('i', buffer[i * 4 : (i + 1) * 4]))[0]

python-ctypes用法个人总结相关推荐

  1. c调用python第三方库_用 Python ctypes 来调用 C/C++ 编写的第三方库

    看到一篇简洁的文章--如何用Python ctypes调用C++(ctypes调用C没这么多麻烦事),不敢独享... 如果需要用 Python 调用 C/C++ 编写的第三方库(这些第三方库很可能就是 ...

  2. ctypes python3_聊聊Python ctypes 模块

    摘要:模块ctypes是Python内建的用于调用动态链接库函数的功能模块,一定程度上可以用于Python与其他语言的混合编程.由于编写动态链接库,使用C/C++是最常见的方式,故ctypes最常用于 ...

  3. Python pandas用法

    Python pandas用法 无味之味关注 12019.01.10 15:43:25字数 2,877阅读 91,914 介绍 在Python中,pandas是基于NumPy数组构建的,使数据预处理. ...

  4. python goto 用法

    python goto 用法 pip install goto-statement from goto import with_goto @with_goto def range(start, sto ...

  5. Python SQLite 用法

    Python SQLite 用法 具体可以参考网址 代码: #导入 import sqlite3 #连接库,如果数据库不存在,那么它就会被创建,最后将返回一个数据库对象. # test.db:数据路路 ...

  6. 聊聊Python ctypes 模块(转载)

    聊聊Python ctypes 模块(转载) https://zhuanlan.zhihu.com/p/20152309?columnSlug=python-dev 作者:Jerry Jho 链接:h ...

  7. python ctypes实现api测试_Python与C之间的相互调用(Python C API及Python ctypes库)

    2010-01-24 17:58 14237人阅读 评论(11) 我实现 Python C API 此部分可以参考我原来的文章< 准备工作: 闲话少说,看看Python C API.事实上,Py ...

  8. import的用法python_Python导入模块,Python import用法(超级详细)

    Python导入模块,Python import用法(超级详细) 使用 Python 进行编程时,有些功能没必须自己实现,可以借助 Python 现有的标准库或者其他人提供的第三方库.比如说,在前面章 ...

  9. python with用法

    @python with用法 python中with可以明显改进代码友好度,比如: [python] view plaincopyprint? with open('a.txt') as f: pri ...

  10. Python与C之间的相互调用(Python C API及Python ctypes库)

    2010-01-24 17:58 14237人阅读 评论(11) 收藏 举报 目录(?)[-] Python C API 准备工作: C中内嵌Python 获取返回值 利用C扩展Python Pyth ...

最新文章

  1. 产品经理10大基础技能(5):读透神经网络和机器学习
  2. system v和posix的共享内存对比 共享内存位置
  3. sendkeys.send 始终输出英文._PLC的三种输出方式,你知道有哪些吗?
  4. 从wireshake分析http和https的通信过程
  5. McAfee Agent漏洞可导致黑客以Windows 系统权限运行代码
  6. laravel 分页使用
  7. ilove中文_iloveyou歌词中文版是什么歌
  8. UWB定位系统在冬奥会上满足哪些需求
  9. NPS之Socks流量分析以及未授权复现
  10. 如何区分电梯卡为id卡ic卡_如何分辨IC卡和ID卡
  11. 炒黄金短线交易如何放大收益
  12. 《1024伐木累》-小白篇之开发网站,三天!(结束篇)-总章节十三
  13. php 24字母和 数字进行转化
  14. 课程向:深度学习与人类语言处理 ——李宏毅,2020 (P9)
  15. 谷歌浏览器与驱动下载,以及放置位置
  16. 三相有功无功电流检测方法
  17. wpf展开树节点_WPF中展开一个TreeView控件的所有树节点
  18. ImageExpert
  19. 我以为我对数据库索引十分了解,直到我遇到了阿里面试官。
  20. 广州大学计算机綦科简历,基于八叉树空间分割的三维点云模型密写(綦科,谢冬青,2011)...

热门文章

  1. potian的软件开发常用工具箱
  2. 第7周项目2 - 建立链队算法库
  3. Vue向下滚动加载更多数据-scroll-案例
  4. [Oracle datagard]从库恢复之 ORA-16032: parameter ORA-07286:问题
  5. SSL证书 SSL免费获取 SSL安装-华为云
  6. Python: PS 滤镜特效 -- Marble Filter
  7. CDH spark启动spark-shell报错:Permission denied: user=root, access=WRITE, inode=/user
  8. Acrel-2000型电力监控系统可实现配电所配电回路用电的实时监控和管理
  9. 【数据结构】图的四种存储结构
  10. 金字塔原理(麦肯锡) 要点汇总