Python --- ctypes库的使用

ctypes 的官方文档

  • 英文文档:https://docs.python.org/3/library/ctypes.html
  • 中文文档:https://docs.python.org/zh-cn/3.10/library/ctypes.html

Python--ctypes(数据类型详细踩坑指南):https://zhuanlan.zhihu.com/p/145165873

Python 调用 C/C++ 的程序主要有两种方式:

  1. 使用 ctypes 调用动态库
  2. 通过 Python C 扩展 ( http://icejoywoo.github.io/2015/10/30/intro-of-extending-cpython.html ) 方式

ctypes 的方式相对来说成本较低,首先 ctypes 是内置库,使用方便,使用的过程中与 C/C++ 动态库的逻辑是完全独立的,互相可以单独维护。但是相对也有明显的缺点,C++ 在编译后的函数名会变,ctypes 使用起来不方便,API 相对也比较繁琐,写起来略微麻烦一些,使用出错的话会导致进程退出。本文主要介绍 ctypes 的基本用法,可以对现有的 C/C++ 代码进行简单的二次封装后进行使用。个人认为 ctypes 本身还是比较适合轻量级的使用场景,如果逻辑较为复杂的,请考虑使用 C/C++ 扩展的方式。

注意:部分示例代码引用了 ctypes c_int 类型。在 sizeof(long) == sizeof(int) 的平台上此类型是 c_long 的一个别名。所以,在程序输出 c_long 而不是你期望的 c_int 时不必感到迷惑 --- 它们实际上是同一种类型。

1、ctypes 基本数据类型映射表

ctypes 是 Python 的外部函数库。提供了与 C 兼容的数据类型,并允许调用 DLL 或共享库中的函数。可使用该模块以纯 Python 形式对这些库进行封装。

下面主要介绍如何使用 ctypes 模块对 C 语言编译的动态链接库要求的数据类型进行封装,主要包括以下几类:

  • C语言中基础的数据类型 ( 如char, int等 )
  • 数组类型
  • 指针类型
  • 结构体类型
  • 嵌套结构体
  • 结构体数组
  • 结构体指针
  • 指针数组
  • 结构体指针数组

参数类型预先设定好,或者在调用函数时再把参数转成相应的 c_*** 类型。

ctypes 的类型对应如下:

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

对应的指针类型是在后面加上 "_p",如 int* 是 c_int_p 等等。在 python 中要实现 c 语言中的结构,需要用到 类。

动态链接库

下面是测试用的C语言代码

#include 
#include 

typedef struct student {
    char class;
    int grade;
    long array[3];
    int *point;
}student_t;

typedef struct nest_stu {
    char rank;
    student_t nest_stu;
    student_t strct_array[2];
    student_t *strct_point;
    student_t *strct_point_array[2];
} nest_stu_t;

typedef struct g_student {
    int g_grade;
} g_stu;

g_stu g_stu_t = {11};

int test_func(char char_arg, int int_arg, float float_arg, char *stu_buf, char *nest_stu_buf, char *out_buf)
{
    //data type test
    printf("char arg: %c\n", char_arg);
    printf("int arg: %d\n", int_arg);
    printf("float arg: %f\n", float_arg);

    student_t *stu_p = NULL;
    nest_stu_t *nest_stu_p = NULL;

    stu_p = (student_t *)stu_buf;
    nest_stu_p = (nest_stu_t *)nest_stu_buf;
    //struct type test
    printf("struct class: %c\n", stu_p->class);
    printf("struct grade: %d\n", stu_p->grade);
    printf("struct array[0]: %d array[1]: %d\n", stu_p->array[0], stu_p->array[1]);
    printf("struct point: %d\n", *(stu_p->point));

    //nest struct test
    printf("nest struct rank: %d\n", nest_stu_p->rank);
    printf("nest struct stu grade: %d\n", nest_stu_p->nest_stu.grade);

    //struct array
    printf("nest struct array[0] grade: %d\n", nest_stu_p->strct_array[0].grade);
    printf("nest struct array[1] grade: %d\n", nest_stu_p->strct_array[1].grade);

    //struct point
    printf("nest struct point grade: %d\n", nest_stu_p->strct_point->grade);
    //struct point array
    printf("nest struct point array[0] grade: %d\n", nest_stu_p->strct_point_array[0]->grade);
    printf("nest struct point array[1] grade: %d\n", nest_stu_p->strct_point_array[1]->grade);

    //out buf test
    memcpy(out_buf, stu_p, sizeof(int)*2);

    return 1;
}

生成动态链接库。使用编译命令gcc -Wall -g -fPIC -shared -o libstruct.so.0 ctype_code.c生成动态链接库, 可以使用nm -D <文件名>查看内部的符号信息,如下图:

Python --- ctypes库的使用_第1张图片

还可以使用 readelf -s 查看 elf 文件内的符号分布。( ​Linux 下 gcc 编译生成动态链接库*.so文件并调用:https://blog.csdn.net/xuq09/article/details/84134939 )
关于动态链接库的生成、内部符号和数据的定义还是有必要了解一下

基础数据类型

# -*- coding: utf-8 -*-

from ctypes import *

# 字符,仅接受one character bytes, bytearray or integer
char_type = c_char(b"a")

byte_type = c_char(1)           # 字节
string_type = c_wchar_p("abc")  # 字符串
int_type = c_int(2)             # 整型

# 直接打印输出的是对象信息,获取值需要使用 value 方法
print(char_type, byte_type, int_type)
print(char_type.value, byte_type.value, string_type.value, int_type.value)

print(c_int())                    # c_long(0)
print(c_wchar_p("Hello, World"))  # c_wchar_p(140018365411392)
print(c_ushort(-3))               # c_ushort(65533)

当给指针类型的对象 c_char_p、c_wchar_p 和 c_void_p 等赋值时,将改变它们所指向的 内存地址,而 不是 它们所指向的内存区域的 内容 (这是因为 Python 的 bytes 对象是不可变的):

from ctypes import *

s = "Hello, World"
c_s = c_wchar_p(s)
print(c_s)        # c_wchar_p(139966785747344)
print(c_s.value)  # Hello World
c_s.value = "Hi, there"
print(c_s)         # 内存地址已经改变 c_wchar_p(139966783348904)
print(c_s.value)   # Hi, there
print(s)           # 第一个对象没有改变 Hello, World

但要注意不能将它们传递给会改变指针所指内存的函数。如果你需要可改变的内存块,ctypes 提供了 create_string_buffer() 函数,它提供多种方式创建这种内存块。当前的内存块内容可以通过 raw 属性存取,如果你希望将它作为NUL结束的字符串,请使用 value 属性:

from ctypes import *

p = create_string_buffer(3)
print(sizeof(p), repr(p.raw))
p = create_string_buffer(b"Hello")
print(sizeof(p), repr(p.raw))
print(repr(p.value))

p = create_string_buffer(b"Hello", 10)
print(sizeof(p), repr(p.raw))

p.value = b"Hi"
print(sizeof(p), repr(p.raw))

create_string_buffer() 函数替代以前的 ctypes 版本中的 c_buffer() 函数 和 c_string() 函数。create_unicode_buffer() 函数创建包含 unicode 字符的可变内存块,与之对应的C语言类型是 wchar_t。

数组 类型

数组的创建和C语言的类似,给定数据类型和长度 即可,如下:

from ctypes import *

# 数组
# 定义类型
char_array = c_char * 3
# 初始化
char_array_obj = char_array(b"a", b"b", 2)
# 打印只能打印数组对象的信息
print(char_array_obj)
# 打印值通过value方法
print(char_array_obj.value)

也可以在创建的时候直接进行初始化,如下:

from ctypes import *

int_array = (c_int * 3)(1, 2, 3)
for i in int_array:
    print(i)

char_array_2 = (c_char * 3)(1, 2, 3)
print(char_array_2.value)

这里需要注意,通过value方法获取值只适用于字符数组,其他类型如print(int_array.value)的使用会报错。

5个0的整形数组:
(c_int * 5)()

前三个数为1-3,后续全为0的10长度浮点型数组:
(c_double * 10)(1, 2, 3)

对于Python而言,数字类型的数组是一个可迭代对象,其可通过for循环、next方法等方式进行迭代,
以获取其中的每一个值。例:
for i in (c_double * 10)(1, 2, 3):

   print(i)

输出结果为1.0、2.0、3.0以及后续的7个0.0。

数组对象在数据类型上可看作是指针,且指针变量在ctypes中就等同于int类型,故所有涉及到指针传递的地方,均无需考虑修改argtypes属性的问题。直接以默认的int类型传入即可。

多维 数组

多维数组与一维数组在语法上大体类似,但在字符串数组上略有不同。这里讨论 数字类型的高维数组。此外,为简单起见,都是对二维数组进行讨论,更高维度的数组在语法上与二维数组是相似的,不再赘述。高维数组类可简单的通过在一维数组类外部再乘上一个数字实现:

(c_int * 4) * 3

这样就得到了一个所有值均为0的二维数组对象。又例:

# 只实例化了第一个一维数组的全部 以及 第二个一维数组的前两个值,而其他所有值均为0。
((c_int * 4) * 3)((1, 2, 3, 4), (5, 6))

二维数组在使用时与一维数组一致,其可直接作为指针参数传入C的函数接口进行访问,在C语言内部其等同于C语言中声明的二维数组。而对于Python,这样的数组对象可通过双层的for循环去迭代获取每个数值。

字符串 数组

字符串数组在ctypes中的行为更接近于C语言中的字符串数组,其需要采用二维数组的形式来实现,而不是Python中的一维数组。首先,需要通过c_char类型乘上一个数,得到一个字符串类型,而后将此类型再乘上一个数,就能得到可以包含多个字符串的字符串数组。例:

# 实例化了一个3字符串数组,每个字符串最大长度为10。
((c_char * 10) * 3)()

对于C语言而言,上述的字符串数组实例可直接当做字符串指针传入C函数,其行为等同于在C中声明的char (*)[10] 指针。下详细讨论Python中对此对象的处理。

首先,字符串数组也是可迭代对象,可通过for循环迭代取值,对于上例的对象,其for循环得到的每一个值,都是一个10个长度的字符串对象。这样的字符串对象有两个重要属性:value和raw。value属性得到是普通字符串,即忽略了字符串终止符号(即C中的\0)以后的所有内容的字符串,而raw字符串得到的是当前对象的全部字符集合,包括终止符号。也就是说,对于10个长度的字符串对象,其raw的结果就一定是一个10个长度的字符串。例:

for i in ((c_char * 10) * 3)():
   print(i.value)
   print(i.raw)

上述代码中,i.value 的输出全为空字符串(b’’),而对于 i.raw,其输出则为b’\x00\x00…’,总共10个 \x00。也就是说,

  • value 会忽略字符串终止符号后的所有字符,是最常用的取值方式,
  • raw得到不忽略终止字符的字符串。

create_string_buffer

接下来讨论 ctypes 中对字符串对象的赋值方法。由于 ctypes 的字符串对象通过某个固定长度的字符串类实例化得到,故在赋值时,这样的字符串对象只可以接受等同于其声明长度的字符串对象作为替代值,这是普通 Python 字符串做不到的。要得到这样的定长字符串,需要用到 ctypes 的create_string_buffer 函数。

ctypes 提供了 create_string_buffer() 函数创建一定长度的内存区域。当前的内存块 内容可以通过raw 属性 存取,如果是创建 NULL 结束的字符串,使用 value 属性获取内存块的值。

# -*- coding: utf-8 -*-

from ctypes import *

p = create_string_buffer(b"hello", 10)  # create a 10 byte buffer
print(sizeof(p), repr(p.raw))

# 快速创建内存区域的方法
p = create_string_buffer(3)    # create a 3 byte buffer, initialized to NUL bytes
print(sizeof(p), repr(p.raw))  # 3 b'\x00\x00\x00'

# create a buffer containing a NUL terminated string
p = create_string_buffer(b"Hello")    
print(sizeof(p), repr(p.raw))  # 6 b'Hello\x00'
print(repr(p.value))  # b'Hello'

p = create_string_buffer(b"Hello", 10)  # create a 10 byte buffer
print(sizeof(p), repr(p.raw))  # 10 b'Hello\x00\x00\x00\x00\x00'
p.value = b"Hi"
print(sizeof(p), repr(p.raw))  # 10 b'Hi\x00lo\x00\x00\x00\x00\x00'

create_string_buffer 函数用于创建固定长度的带缓冲字符串。其接受两个参数,

  • 第一参数:字节字符串。必须是字节类型的字符串
  • 第二参数:目标长度,
  • 返回值:为被创建的定长度字符串对象,可以赋值给字符串数组中的某个对象。

在 Python2中,普通的字符串就是字节字符串,在Python3中,所有的字符串默认为Unicode字符串,故可以通过字符串的 encode()、decode() 方法进行编码方式的转化。

from ctypes import *

charList = ((c_char * 10) * 3)()
strList = ['aaa', 'bbb', 'ccc']

for i in range(3):
   charList[i] = create_string_buffer(strList[i].encode(), 10)

for i in charList:
   print(i.value)

或者这样写:

from ctypes import *

# 生成一个长度为 10 的前面部分是 abcdefg ,后面用 NULL 补齐的字符数组。
temp_string = create_string_buffer(b"abcdefg", 10)
print(temp_string)
print(temp_string.value)
print(temp_string.raw)

注意,通过create_string_buffer函数创建的字符串对象,其长度必须严格等同于被赋值的字符串对象的声明长度,即如果声明的是10长度字符串,那么create_string_buffer的第二参数就必须也是10,否则代码将抛出TypeError异常,提示出现了类型不一致

在字符串数组的初始化过程中,这样的字符串对象也可作为初始化的参数。例:

from ctypes import *

strList = ['aaa', 'bbb', 'ccc']
charList = ((c_char * 10) * 3)(*[create_string_buffer(i.encode(), 10) for i in strList])

for i in charList:
    print(i.value.decode())

上述代码将实例化与初始化合并,通过列表推导式得到了3个10长度的缓冲字符串,并使用星号展开,作为实例化的参数。则这样得到的charList效果等同于上例中通过依次赋值得到的字符串数组对象。最后通过for循环输出字符串对象的value属性(一个bytes字符串),且通过decode方法将bytes转化为str。

参数 的 引用 传递  ( byref )

有时候 C 函数接口可能由于要往某个地址写入值,或者数据太大不适合作为值传递,从而希望接收一个 指针 作为数据参数类型。这和 传递参数引用 类似。

ctypes 暴露了 byref() 函数用于通过引用传递参数,使用 pointer() 函数也能达到同样的效果,只不过 pointer() 需要更多步骤,因为它要先构造一个真实指针对象。所以在 Python 代码本身不需要使用这个指针对象的情况下,使用 byref() 效率更高。

byref  不会构造一个指针对象,因此速度比 pointer 快。( byref() 只是用来传递引用参数 )。其 _obj 是只读属性,不能更改。

指针和引用 是非常常用的(特别在C中)

  • byref(temp_var)        temp_var 的 引用
  • pointer(i)                   temp_var 的 指针

其中如果不是必须使用指针的话,引用会更快。

>>> 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'
>>>

指针类型POINTER() )

POINTER() 用于定义 某个类型 的 指针。 POINTER 返回 类型对象

其实就是:POINTER 就是用来定义 指针类型。

使用 POINTER() 创建指针 总共2步操作:

  • 首先 使用 POINTER 来定义 指针类型
  • 然后 再通过 指针类型创建指针

示例:

from ctypes import *

# POINTER(c_int)       相当于 C/C++ 中  int*
# POINTER(c_double)    相当于 C/C++ 中  double *
# POINTER(c_float)     相当于 C/C++ 中  float*

int_ptr = POINTER(c_int)
int_99 = c_int(99)
ptr = int_ptr(int_99)  # 相当于 C/C++ 中  int* ptr = &int_99
print(ptr)
print(ptr[0])
print(ptr.contents)
print(ptr.contents.value)

# <__main__.LP_c_long object at 0x0000026597FB1AC0>
# 99
# c_long(99)
# 99

POINTER() 常用的作用:给 argtypes 指定函数的参数类型、给 restype 指定返回值类型。

ctypes.POINTER(ctypes.c_float) == type(ptr)  # True ptr 的类型可通过 POINTER 获得

如果不指定,默认的类型都将被当做是整形。实参类型(除了 None,integer,stringbytes,可隐式转换)和返回值的类型用 ctypes.c_ 系列类型显示的指定。(见ctypes 文档:None, integers, bytes objects and (unicode) strings are the only native Python objects that can directly be used as parameters in these function calls.)。实际上,最终得到的返回结果的类型已经由显示指定的 ctypes.c_ 类型,转化为了对应的 python 类型。比如指定 .restype = c_char_p,函数的返回值的类型将是 bytes 类型。根据 ctypes 官方文档的描述:ctypes实例由一个内存块和若干描述符构成,描述符用来访问内存块。内存块并不保存 python 对象,而仅仅保存 python 对象的内容。外部访问保存的内容时,都会构造一个新的 python 对象。

指针pointer() )

可以将 ctypes 类型数据传入 pointer() 函数创建指针。​pointer() 函数内部其实还是通过POINTER() 函数实现的。

  • pointer() 简化了步骤,使用更方便。pointer 用来获取一个变量的指针,相当于 C 里面的 & 。pointer()用于将对象转化为指针。pointer 会创建一个实际的指针对象,当不需要实际指针对象时可以使用 byref()。.contents 属性即其所指的对象,但是指向是能够变动的。给 ptr.contents 赋值则可以更改指针的指向,但是指针类型必须匹配ctypes.addressof 返回 C 实例的地址pointer 的用法需要注意的是,必须在 ctypes 类型上使用,不能在 python 类型上使用。

所有 ctypes 类型的实例都包含一个存放 C 兼容数据的内存块;该内存块的地址可由 addressof() 辅助函数返回。 还有一个实例变量被公开为 _objects;此变量包含其他在内存块包含指针的情况下需要保持存活的 Python 对象。

示例:

from ctypes import *

i = c_int(42)
print(i)
pi = pointer(i)
print(pi)

# 指针实例拥有 contents 属性,它返回指针指向的真实对象,如上面的 i 对象。
print(pi.contents)

# ctypes 并没有 OOR (返回原始对象), 每次访问这个属性时都会构造返回一个新的相同对象
print(pi.contents is i)
print(pi.contents is pi.contents)

i = c_int(99)
# 指针的 contents 属性赋值为另一个c_int实例将会导致该指针指向该实例的内存地址:
pi.contents = i
print(pi.contents)

# 指针对象也可以通过整数下标进行访问
print(pi[0])

# 通过整数下标赋值可以改变指针所指向的真实内容
print(i)
pi[0] = 22
print(i)

示例:

from ctypes import *

# 指针类型
int_obj = c_int(3)
int_p = pointer(int_obj)
print(int_p)
# 使用contents方法访问指针
print(int_p.contents)
# 获取指针指向的值
print(int_p[0])

temp_var = c_float(12345.789)
print(temp_var)
print(temp_var.value)
print('*****************************************')
ptr = pointer(temp_var)

print(ptr)
print(ptr.contents)         # 指针的指向
print(ptr.contents.value)   # 指针所指向的内容
print('*****************************************')
print(hex(addressof(ptr.contents)))
print((hex(addressof(temp_var))))
print('*****************************************')
ptr.contents = c_float(321.321)  # 改变指针的指向
print(ptr.contents.value)
print(temp_var.value)
print('*****************************************')
temp_var_2 = c_float(55.55)
print(temp_var_2)
ptr_2 = pointer(temp_var_2)
ptr_2.contents.value = 99.99  # 改变指针所指向的内容
print(temp_var_2)

python 的 打印

def func_print():
    # 字符串格式化
    num = int(input('请输入一个十进制整数'))  # 将str类型转换成int类型
    print(num, '的二进制为:', bin(num))     # 个数可变的位置参数法
    print(str(num) + '的二进制数为:', bin(num))  # 使用+作为连接符
    print('%s的二进制数为:%s' % (num, bin(num)))  # 格式化字符串法
    print('{0}的二进制数为:{1}'.format(num, bin(num)))  # 格式化字符串
    print(f'{num}的二进制数为:{bin(num)}')  # 格式化字符串法
    print('{0}的八进制数为:{1}'.format(num, oct(num)))
    print(f'{num}的十六进制数为:{hex(num)}')

示例:

from ctypes import *

ct_arr_ptr = pointer((c_int * 5)(1, 2, 3, 4, 5))
# 这是ct_arr_ptr对象的地址
print(ct_arr_ptr)
# <__main__.LP_c_int_Array_5 object at 0x7f2496542d90>

# 这是 ct_arr_pt r所指向的内容在内存中的真实地址
print(hex(addressof(ct_arr_ptr.contents)))

# 这是 contents 对象的地址,每次都临时生成新的,但是都引用上面那个不变的内存里的东西.
print(ct_arr_ptr.contents)

创建空指针的方式

 不带参数的调用指针类型创建一个 NULL 指针, NULL 指针有一个 False 布尔值

from ctypes import *

null_ptr = POINTER(c_int)()
print(bool(null_ptr))

指针类型的转换

ctypes 提供 cast()方法将一个 ctypes 实例转换为指向另一个ctypes数据类型的指针。cast()接受两个参数

  • 参数1 是 ctypes 对象,它是或可以转换成某种类型的指针,
  • 参数2 是 ctypes 指针类型。它返回第二个参数的一个实例,该实例引用与第一个参数相同的内存块。

示例:

from ctypes import *

int_p = pointer(c_int(5))
print(int_p)

cast_type = cast(int_p, POINTER(c_char))
print(cast_type)
print(cast_type.contents)
print(cast_type.contents.value)

函数指针 CFUNCTYPE

用来定义 ctype 的函数指针,指向 python 函数,实际是传给 c 语言调用的。可以看到 python 和 c 的相互调用方式了:

  • python 里 loadlibrary 方式调用 c; 
  • python 提供 FUNCTYPE 的 python 回调给 c 语言调用;
libc = CDLL('/lib/x86_64-linux-gnu/libc.so.6')
qsort = libc.qsort
qsort.restype
>> 
CMPFUNCP = CFUNCTYPE(c_int,POINTER(c_int),POINTER(c_int))
type(CMPFUNCP)
>> 
def python_cmp(a,b):
    print('cmp in python')
    return a[0]-b[0]
ia = (c_int * 10)(1,3,5,7,9,2,4,6,8,10)
qsort(id,len(ia),sizeof(c_int),CMPFUNCP(python_cmp()))
ia
1 2 3 4 5 6 7 8 9 10 <__main__.c_int_Array_10 object at 0x7f6a121c26a8>
#更简单的写法是用decorator:
@CFUNCTYPE(c_int,POINTER(c_int),POINTER(c_int))
def python_cmp(a,b):
    print('%s: %s',a[0],b[0])
    return a[0]-b[0]
qsort(ia,len(ia),sizeof(c_int),python_cmp)

FUNCTYPE 和 PFUNCTYPE 的区别?
FUNCTYPE 是封装的 python函数是给c语言调用的,它将不受GIL影响,纯c的。

而 PFUNCTYPE 封装的 python 函数仍然受 GIL 影响,这个区别还是很大的.

  • c 希望调用的 python不要卡在GIL里的话,用 FUNCTYPE;
  • 如果有些GIL操作再c里不可忽略,用PFUNCTYPE.

其他的指针类型

基本类型中只包含了 c_char_p 和 c_void_p 两个指针类型,其他的指针类型该如何使用?数组该如何定义和使用?我们来看看这两个类型的使用。

//sum_module.c
#include 

int sum(int a[], size_t len) {
    int ret = 0;
    for (size_t i = 0; i < len; i++) {
        ret += a[i];
    }
    return ret;
}

int sum2(int* a, size_t len) {
    int ret = 0;
    for (size_t i = 0; i < len; i++) {
        ret += a[i];
    }
    return ret;
}

生成动态库

gcc -fPIC -shared sum_module.c -o sum_module.so
import ctypes

lib = ctypes.cdll.LoadLibrary("sum_module.so")

lib.sum([1, 2, 3], 3)

#Traceback (most recent call last):
#  File "demo.py", line 7, in 
#    lib.sum([1, 2, 3], 3)
#ctypes.ArgumentError: argument 1: : Don't know how to convert parameter 1

会发现 ctypes 报错了,不知道类型如何进行转换,也就是说 ctypes 的隐式转换是不支持数组类型的。我们需要用 ctypes 的数组来传参数。

import ctypes

lib = ctypes.cdll.LoadLibrary("sum_module.so")

array = (ctypes.c_int * 3)(1, 2, 3)
print lib.sum(array, len(array))

i = ctypes.c_int(5)
print lib.sum(i, 1)

ctypes 的数组定义就是用 ctypes 中的类型 * 大小。

指针的用法

import ctypes

lib = ctypes.cdll.LoadLibrary("sum_module.so")

i = ctypes.c_int(5)
lib.sum2.argtypes = (ctypes.POINTER(ctypes.c_int), ctypes.c_size_t)
print lib.sum2(ctypes.pointer(i), 1)

示例:

import ctypes

i = ctypes.c_int(5)
print ctypes.pointer(i)  # <__main__.LP_c_int object at 0x10566f7a0>

i = 5
print ctypes.pointer(i)  # TypeError: _type_ must have storage info

对于单个字符串,其不需要通过指针指针转换即可当做指针传递。例:

void printStr(char *str)
{
   printf("%s", str);
}

则Python中:

dllObj = CDLL('a.dll')

dllObj.printStr('Hello!')

由此可见,对于单个字符串传进 dll,则直接通过字符串传递即可,传递过程中字符串会自动被转化为指针。而对于返回单个字符串的C函数,需要通过修改 restype 属性为 c_char_p 后,即可在Python中 直接接收字符串返回值。还有传递 char* 指针参数,然后回去改变值,需要定义变量传递,可参考如下:

C 部分

void modifyStr(char *str)
{
  //使用strcpy将1233赋值给str
  strcpy(str,”1233”);
}

Python 部分

from ctypes import *

# 将python变量转换成char* 变量
str_data = ""
cdata = c_char_p(str(str_data).encode())
dllObj = CDLL('a.dll')

dllObj.modifyStr(cdata)

# 将char*数据转换成 python变量
str_data = str(cdata.value, encoding="utf-8")
print("str_data", str_data)

对于单个数值的指针,则需要通过byref或者pointer函数取得。首先考虑如下函数:

void swapNum(int *a, int *b)
{
   int temp = *a;
   *a = *b;
   *b = temp;
}

此函数接收两个int类型指针,并在函数内部交换指针所在位置的整形值。此时如果通过Python传入这两个指针参数,就需要使用到byref或者pointer函数。byref函数类似于C语言中的取地址符号&,其直接返回当前参数的地址,而pointer函数更为高级,其返回一个POINTER指针类型,一般来说,如果不需要对指针进行其他额外处理,推荐直接调用byref函数获取指针,这是较pointer更加快速的方法。此外,这两个函数的参数都必须是ctypes类型值,可通过ctypes类型实例化得到,不可直接使用Python内部的数值类型。例:

from ctypes import *

dllObj = CDLL('a.dll')
a, b = c_int(1), c_int(2)
dllObj.swapNum(byref(a), byref(b))

以上代码首先通过c_int类型实例化得到了两个c_int实例,其值分别为1和2。然后调用上文中的swapNum函数,传入的实际参数为byref函数的返回指针。这样就相当于在C语言中进行形如swapNum(&a, &b)的调用。

要将 c_int 类型再转回 Python 类型,可以访问实例对象的 value 属性:print(a.value, b.value)

对于pointer函数,其同样接受一个ctypes实例作为参数,并返回一个POINTER指针类型,而不是简单的一个指针。POINTER指针类型在传参时也可直接作为指针传入C语言函数,但在Python中,其需要先访问 contents 属性,得到指针指向的数据,其一般为ctypes类型的实例,然后再访问 value 属性,得到实例所对应的Python类型的数据。例:

from ctypes import *

dllObj = CDLL('a.dll')

a, b = pointer(c_int(1)), pointer(c_int(2))
print(a.contents.value, b.contents.value)
dllObj.swapNum(a, b)
print(a.contents.value, b.contents.value)

函数 "进 / 出 参数" 的定义

python 并不会直接读取到 dll 的源文件,所以 python 不仅看不到函数要什么,也看不到函数返回什么。,那么如何告诉 python,函数需要 什么参数、返回什么类型的值?答案是用 argtypes 和 restype

  • argtypes :函数 的 参数类型。
  • restype:函数 的 返回值类型。默认情况下 python 认为函数返回了一个C中的 int 类型。如果函数返回别的类型,就需要用到 restype 命令。

示例:

  • fuction.argtypes = [c_char_p, c_int]    只指定了 参数类型,没有指定返回类型,所以返回类型默认是 int
  • function.restype = c_char_p    指定 fuction 这个函数的返回值是 c_char_p

如果需要返回非 int 的类型就需要进行指定。指定参数类型的好处在于,ctypes 可以处理指针的转换,无需代码中进行转换。继续使用上面 sum2 函数为例

i = ctypes.c_int(5)
lib.sum2.argtypes = (ctypes.POINTER(ctypes.c_int), ctypes.c_size_t)
print lib.sum2(ctypes.pointer(i), 1)
print lib.sum2(i, 1)

可以使用 pointer(i) 和 i 作为 sum2 的第一个参数,会自动处理是否为指针的情况。

示例:调用了 strchr 函数,这个函数接收一个字符串指针以及一个字符作为参数,返回另一个字符串指针。

>>> strchr = libc.strchr
>>> strchr(b"abcdef", ord("d"))  
8059983
>>> strchr.restype = c_char_p    # c_char_p is a pointer to a string
>>> strchr(b"abcdef", ord("d"))
b'def'
>>> print(strchr(b"abcdef", ord("x")))
None
>>>

如果希望避免上述的 ord("x") 调用,可以设置 argtypes 属性,第二个参数就会将单字符的 Python 二进制字符对象转换为 C 字符:

>>> strchr.restype = c_char_p
>>> strchr.argtypes = [c_char_p, c_char]
>>> strchr(b"abcdef", b"d")
'def'
>>> strchr(b"abcdef", b"def")
Traceback (most recent call last):
  File "", line 1, in 
ArgumentError: argument 2: exceptions.TypeError: one character string expected
>>> print(strchr(b"abcdef", b"x"))
None
>>> strchr(b"abcdef", b"d")
'def'
>>>

如果外部函数返回了一个整数,你也可以使用要给可调用的 Python 对象(比如函数或者类)作为 restype 属性的值。将会以 C 函数返回的 整数 对象作为参数调用这个可调用对象,执行后的结果作为最终函数返回值。这在错误返回值校验和自动抛出异常等方面比较有用。

>>> GetModuleHandle = windll.kernel32.GetModuleHandleA  
>>> def ValidHandle(value):
...     if value == 0:
...         raise WinError()
...     return value
...
>>>
>>> GetModuleHandle.restype = ValidHandle  
>>> GetModuleHandle(None)  
486539264
>>> GetModuleHandle("something silly")  
Traceback (most recent call last):
  File "", line 1, in 
  File "", line 3, in ValidHandle
OSError: [Errno 126] The specified module could not be found.

WinError 函数可以调用 Windows 的 FormatMessage() API 获取错误码的字符串说明,然后 返回 一个异常。 WinError 接收一个可选的错误码作为参数,如果没有的话,它将调用 GetLastError() 获取错误码。

请注意,使用 errcheck 属性可以实现更强大的错误检查手段;详情请见参考手册。

结构体、联合

结构体类型的实现:

  • "结构体" 和 "联合" 必须继承自 ctypes 模块中的 Structure 和 Union 。
  • 子类必须定义 _fields_  属性。 _fields_ 是一个二元组列表,二元组中包含 field name 和 field type 。type 字段必须是一个 ctypes 类型,比如 c_int,或者其他 ctypes 类型: 结构体、联合、数组、指针。
  • _pack_ 属性 决定结构体的字节对齐方式,默认是4字节对齐,创建时使用_pack_=1 可以指定1字节对齐。

注意,在 Python 中定义的结构体,其 变量名、类名 等均可以不同于 C 语言中的变量名,但结构体变量的数量、数据类型与顺序必须严格对应于C源码中的定义,否则可能将导致内存访问出错。

示例 1:

一个简单的 TestStruct 结构体,它包含名称为 x 和 y 的两个变量,还展示了如何通过构造函数初始化结构体。

from ctypes import *


class TestStruct(Structure):
    _fields_ = (
        ('x', c_int),
        ('y', c_double),
    )

以上代码定义了一个结构体类型,其等同于 C 中的 struct 声明。此结构体定义了两个结构体变量:x 对应于一个 int 类型,y 对应于一个double类型。

结构体类型 可以通过实例化得到一个结构对象,在实例化的同时也可传入初始化参数,作为结构变量的值。在得到结构对象后,也可通过点号访问结构体成员变量,从而对其赋值。例:

from ctypes import *


class TestStruct(Structure):
    _fields_ = (
        ('x', c_int),
        ('y', c_double),
    )


test_struct = TestStruct(1, 2)
print(test_struct.x, test_struct.y)

test_struct.x, test_struct.y = 10, 20
print(test_struct.x, test_struct.y)

上述代码通过 TestStruct(1, 2) 得到一个 结构体实例 test_struct ,第一次输出结果即为1 2.0。而后,再通过属性访问的方式修改了结构体中的两个变量,则第二次输出结果为10 20.0。

上面定义的结构体可直接传入C代码中,且上文已经提到,两边定义的结构体变量的各种名称均可不同,但数据类型、数量与顺序必须一致。例:

struct TestStruct
{
   int a;
   double b;
}

extern "C"
{
   void printStruct(TestStruct testStruct);
}

void printStruct(TestStruct testStruct)
{
   printf("%d %f\n", testStruct.a, testStruct.b);
}

Python 部分:

from ctypes import *

dllObj = CDLL('1.dll')

class TestStruct(Structure):
    _fields_ = (
        ('x', c_int),
        ('y', c_double),
    )

test_struct = TestStruct(1, 2)
dllObj.printStruct(test_struct)

由此可见,在Python中实例化得到的结构体实例,可以直接当做C中的结构体实参传入。

结构体也可以指针的方式传入,通过 byref 或者 pointer 函数即可实现。同样的,这两个函数都可直接接受结构体实例作为参数进行转化,byref 返回简单指针,而 pointer 返回指针对象,可访问其contents 属性得到指针所指向的值。例:

C部分,上述 printStruct 函数修改为接受结构体指针的版本:

void printStruct(TestStruct *testStruct)
{
   printf("%d %f\n", testStruct -> a, testStruct -> b);
}

Python部分:

from ctypes import *

dllObj = CDLL('1.dll')

class TestStruct(Structure):
    _fields_ = (
        ('x', c_int),
        ('y', c_double),
    )

test_struct = TestStruct(1, 2)
dllObj.printStruct(byref(test_struct))

上述代码将结构体对象testStruct作为byref的参数,从而将其转换为指针传入printStruct函数中。示例:

from ctypes import *

dllObj = CDLL('1.dll')

class TestStruct(Structure):
    _fields_ = (
        ('x', c_int),
        ('y', c_double),
    )

test_struct = pointer(TestStruct(1, 2))
dllObj.printStruct(test_struct)
print(test_struct.contents.x, test_struct.contents.y)

上述代码通过结构体对象生成了一个指针类型,并将此指针传入函数,可达到同样的效果。且在Python内部,结构体指针类型可以访问其contents属性,得到指针所指向的结构体,然后可继续访问结构体的x与y属性,得到结构体中保存的值。

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

from ctypes import *
import types


class TestStruct(Structure):
    _fields_ = (
        ('x', c_int),
        ('y', c_double),
        ('next', TestStruct)
    )

则 python 会报错 type 未定义,这时就需要 POINTER 

from ctypes import *
import types


class TestStruct(Structure):
    pass


Test._fields_ = [
    ('x', c_int),
    ('y', c_char),
    ('next', POINTER(TestStruct))
]

示例 2:

from ctypes import *

# 学生信息如下
stu_info = [
    ("class", "A"),
    ("grade", 90),
    ("array", [1, 2, 3]),
    ("point", 4)
]


# 创建 结构体 类
class Student(Structure):
    _fields_ = [
        ("class", c_char),
        ("grade", c_int),
        ("array", c_long * 3),
        ("point", POINTER(c_int))
    ]


print("sizeof Student: ", sizeof(Student))

# 实例化
long_array = c_long * 3
long_array_obj = long_array(1, 2, 3)
int_p = pointer(c_int(4))
stu_info_value = [c_char(b"A"), c_int(90), long_array_obj, int_p]

stu_obj = Student(*stu_info_value)
# 这样打印报错,因为字段名和python关键字class重名了,这是需要特别注意的点
# print("stu info:", stu_obj.class, stu_obj.grade, stu_obj.array[0], stu_obj.point[0])
print("stu info:", stu_obj.grade, stu_obj.array[0], stu_obj.point[0])

输出:

sizeof Student: 40
stu info: 90 1 4

如果把_pack_改为1,则输出:

sizeof Student: 37
stu info: 90 1 4

嵌套 结构体

嵌套结构体的使用需要创建基础结构体的类型,然后将基础结构体的类型作为嵌套结构体 的成员,注意基础结构体所属字段的字段类型是基础结构体的类名,如下:

# -*- coding: utf-8 -*-

from ctypes import *


# 创建 结构体 类
class Student(Structure):
    _fields_ = [
        ("class", c_char),
        ("grade", c_int),
        ("array", c_long * 3),
        ("point", POINTER(c_int))
    ]


# 创建 类型, nest_stu 字段的类型为基础结构体的类名
class NestStudent(Structure):
    _fields_ = [
        ("rank", c_char),
        ("nest_stu", Student)
    ]


# 实例化
long_array = c_long * 3
long_array_obj = long_array(1, 2, 3)
int_p = pointer(c_int(4))
stu_info_value = [c_char(b"A"), c_int(90), long_array_obj, int_p]

stu_obj = Student(*stu_info_value)

# 实例化
nest_stu_info_list = [c_char(b"M"), stu_obj]
nest_stu_obj = NestStudent(*nest_stu_info_list)

print(f"nest stu info: {nest_stu_obj.rank}", end=" ")
print(f"basic stu info: {nest_stu_obj.nest_stu.grade}")

结构体 数组

结构体数组与普通数组的创建类似,需要提前创建结构体的类型,然后使用struct type * array_length 的方法创建数组。

# -*- coding: utf-8 -*-

from ctypes import *


# 创建结构体类
class Student(Structure):
    _fields_ = [
        ("class", c_char),
        ("grade", c_int),
        ("array", c_long * 3),
        ("point", POINTER(c_int))
    ]


# 增加结构体数组成员
class NestStudent(Structure):
    _fields_ = [
        ("rank", c_char),
        ("nest_stu", Student),
        ("struct_array", Student * 2)
    ]


# 实例化
long_array = c_long * 3
long_array_obj = long_array(1, 2, 3)
int_p = pointer(c_int(4))
stu_info_value = [c_char(b"A"), c_int(90), long_array_obj, int_p]

stu_obj = Student(*stu_info_value)

# 结构体数组
# 创建结构体数组类型
stu_array = Student * 2
# 用Student类的对象实例化结构体数组
stu_array_obj = stu_array(stu_obj, stu_obj)

# 实例化
nest_stu_info_list = [c_char(b"M"), stu_obj, stu_array_obj]
nest_stu_obj = NestStudent(*nest_stu_info_list)

# 打印结构体数组第二个索引的grade字段的信息
print("stu struct array info: ", end=' ')
print(nest_stu_obj.struct_array[1].grade, end=' ')
print(nest_stu_obj.struct_array[1].array[0])

结构体 指针

首先创建结构体,然后使用 ctype 的指针方法包装为指针。

from ctypes import *


# 创建结构体类
class Student(Structure):
    _fields_ = [
        ("class", c_char),
        ("grade", c_int),
        ("array", c_long * 3),
        ("point", POINTER(c_int))
    ]


class NestStudent(Structure):
    _fields_ = [
        ("rank", c_char),
        ("nest_stu", Student),
        ("strct_array", Student * 2),
        ("struct_point", POINTER(Student))
    ]


# 实例化
long_array = c_long * 3
long_array_obj = long_array(1, 2, 3)
int_p = pointer(c_int(4))
stu_info_value = [c_char(b"A"), c_int(90), long_array_obj, int_p]

stu_obj = Student(*stu_info_value)

# 结构体指针
# # 创建结构体数组类型
stu_array = Student * 2
# # 用Student类的对象实例化结构体数组
stu_array_obj = stu_array(stu_obj, stu_obj)
# 曾接结构体指针成员,注意使用类型初始化指针是POINTER()


# 实例化,对Student的对象包装为指针使用pointer()
nest_stu_info_list = [c_char(b"M"), stu_obj, stu_array_obj, pointer(stu_obj)]
nest_stu_obj = NestStudent(*nest_stu_info_list)

# 结构体指针指向Student的对象
print(f"stu struct point info: {nest_stu_obj.struct_point.contents}")
# 访问Student对象的成员
print(f"stu struct point info: {nest_stu_obj.struct_point.contents.grade}")

结构体 指针 数组

创建结构体指针数组的顺序为先创建结构体,然后包装为指针,最后再创建数组,用结构体指针去实例化数组。

# -*- coding: utf-8 -*-

from ctypes import *


# 创建结构体类
class Student(Structure):
    _fields_ = [
        ("class", c_char),
        ("grade", c_int),
        ("array", c_long * 3),
        ("point", POINTER(c_int))
    ]


# 实例化
long_array = c_long * 3
long_array_obj = long_array(1, 2, 3)
int_p = pointer(c_int(4))
stu_info_value = [c_char(b"A"), c_int(90), long_array_obj, int_p]

stu_obj = Student(*stu_info_value)

# 结构体指针数组
# 创建结构体数组类型
stu_array = Student * 2
# # 用Student类的对象实例化结构体数组
stu_array_obj = stu_array(stu_obj, stu_obj)
# 创建结构体指针数组
stu_p_array = POINTER(Student) * 2
# 使用pointer()初始化
stu_p_array_obj = stu_p_array(pointer(stu_obj), pointer(stu_obj))


# 曾接结构体指针成员,注意使用类型初始化指针是POINTER()
class NestStudent(Structure):
    _fields_ = [
        ("rank", c_char),
        ("nest_stu", Student),
        ("strct_array", Student * 2),
        ("strct_point", POINTER(Student)),
        ("strct_point_array", POINTER(Student) * 2)
    ]


# 实例化,对Student的对象包装为指针使用pointer()
nest_stu_info_list = [
    c_char(b"M"), 
    stu_obj, 
    stu_array_obj, 
    pointer(stu_obj), 
    stu_p_array_obj
]
nest_stu_obj = NestStudent(*nest_stu_info_list)

# 数组第二索引为结构体指针
print(nest_stu_obj.strct_point_array[1])
# 指针指向Student的对象
print(nest_stu_obj.strct_point_array[1].contents)
# Student对象的grade字段
print(nest_stu_obj.strct_point_array[1].contents.grade)

实例1: ctypes + socket

  1. 对端用c语言tcp socket发送

  2. python socket.recv , 得到bytes(b’xxxx’)

  3. ctypes Structure

  4. 转换bytes to ctypes 解析数据,用Structure按c的方式解析数据.

def struct2stream(s):
    length = ctypes.sizeof(s)
    p = ctypes.cast(ctypes.pointer(s), ctypes.POINTER(ctypes.c_char * length))
    return p.contents.raw


def stream2struct(string, stype):
    if not issubclass(stype, ctypes.Structure):
        raise ValueError('Not a ctypes.Structure')
    length = ctypes.sizeof(stype)
    stream = (ctypes.c_char * length)()
    stream.raw = string
    p = ctypes.cast(stream, ctypes.POINTER(stype))
    return p.contents


class CommandHeader(ctypes.Structure):
    _pack_ = 4
    _fields_ = [
        # Size of this descriptor (in bytes)
        ('MsgCommand', ctypes.c_int),
        ('MsgParam', ctypes.c_int),
        ('unkown', ctypes.c_short),
        ('unkown1', ctypes.c_short),
        ('startx', ctypes.c_short),
        ('starty', ctypes.c_short),
        ('width', ctypes.c_short),
        ('height', ctypes.c_short),
        ('len', ctypes.c_int)
    ]

class StructConverter(object):
    def __init__(self):
        pass

    @classmethod
    def encoding(cls, raw, structs):
        """
            'encode' means raw binary stream to ctype structure. 
        """
        if raw is not None and structs is not None:
            return stream2struct(raw, structs)
        else:
            return None

    @classmethod
    def decoding(cls, data):
        """
            'decode means ctpye structure to raw binary stream
        """
        if data is not None:
            return struct2stream(data)
        else:
            return None

收发过程:

#receive
try:
    raw = fds.recv(ctypes.sizeof(CommandHeader))
except socket.error as e:
    exit(1)
header = StructConverter.encoding(raw, CommandHeader)

#send
resp = CommandHeader()
try:
    fds.send(StructConverter.decoding(data=resp))
except socket.error as e:
    LOGGER.info('%s', e)
    return

实例2 libusb使用

参考 https://pypi.org/project/libusb1/

并以自己实现的 python 调用 libusb 底层库的实现为例子:

def aoa_update_point(self, action, x, y, ops=0):

    global report
    if ops == 0:
        # left = up =0
        x, y = self.axis_convert(x, y)
        real_x = x - self.ref_x
        real_y = y - self.ref_y
    else:
        real_x = x
        real_y = y

    # LOGGER.info('real point(%d %d)',real_x,real_y)

    if action == 'move' or action == 'down':
        report = Report(REPORT_ID, 1, 0, 0, 0, int(real_x), int(real_y))
    if action == 'up':
        report = Report(REPORT_ID, 0, 0, 0, 0, int(real_x), int(real_y))

    if ops == 0:
        self.set_ref(x, y)

    #transfer是1个Structurue pointer obj
    transfer = U.libusb_alloc_transfer(0)
    # contents是实体 
    transfer.contents.actual_length = sizeof(Report)
    # p_report = cast(pointer(report), c_void_p)
    transfer.contents.buffer = cast(pointer(report), c_void_p)

    # put report buffer into control_buffer
    control_buffer = create_string_buffer(sizeof(Report) + LIBUSB_CONTROL_SETUP_SIZE)

    # python级别的内存填充,memmove + addressof
    memmove(addressof(control_buffer) +
            LIBUSB_CONTROL_SETUP_SIZE, addressof(report),
            sizeof(report))

    # 可以看出这个是signed char 0x1 ---> 0x1 0x0 小端!
    # 实际调用了:
    # setup = cast(addressof(control_buffer), libusb_control_setup_p).contents
    U.libusb_fill_control_setup(
        addressof(control_buffer),
        U.LIBUSB_ENDPOINT_OUT | U.LIBUSB_REQUEST_TYPE_VENDOR,
        AndroidA0AProtocl.AOA_SEND_HID_EVENT.value[0],
        1,
        0,
        6)

    # LOGGER.info(control_buffer.raw)

    U.libusb_fill_control_transfer(
        transfer,
        self.aoa_handle,
        pointer(control_buffer),
        null_callback,
        None,
        0)

    transfer.contents.flags = U.LIBUSB_TRANSFER_FREE_BUFFER | U.LIBUSB_TRANSFER_FREE_TRANSFER

    rets: int = U.libusb_submit_transfer(transfer)
    if rets < 0:
        LOGGER.info(U.libusb_error_name(rets))
        return rets
    return rets

2、寻找动态链接库

在编译型语言中,动态链接库会在 编译、链接 或者 程序运行 时访问。

ctypes.util 模块提供了一个函数,可以帮助确定需要加载的库。

  • ctypes.util.find_library(name) :尝试寻找一个库,然后返回其路径名, name 是库名称, 且去除了 lib 等前缀 和 .so 、 .dylib 、版本号 等后缀(这是 posix 连接器 -l 选项使用的格式)。如果没有找到对应的库,则返回 None 。

确切的功能取决于系统。

  • 在 Linux 上, find_library() 会尝试运行外部程序(/sbin/ldconfiggccobjdump 以及 ld) 来寻找库文件,返回库文件的文件名。如果其他方式找不到的话,会使用环境变量 LD_LIBRARY_PATH 搜索动态链接库。
  • On macOS, find_library() tries several predefined naming schemes and paths to locate the library, and returns a full pathname if successful:

    >>> from ctypes.util import find_library
    >>> find_library("c")
    '/usr/lib/libc.dylib'
    >>> find_library("m")
    '/usr/lib/libm.dylib'
    >>> find_library("bz2")
    '/usr/lib/libbz2.dylib'
    >>> find_library("AGL")
    '/System/Library/Frameworks/AGL.framework/AGL'
    >>>
  • 在 Windows 中, find_library() 在系统路径中搜索,然后返回全路径,但是如果没有预定义的命名方案, find_library("c") 调用会返回 None

from ctypes.util import find_library
print(find_library("m"))
print(find_library("c"))
print(find_library("bz2"))

使用 ctypes 包装动态链接库,更好的方式 可能 是在开发的时候就确定名称,然后硬编码到包装模块中去,而不是在运行时使用 find_library() 寻找库。

3、加载 DLL

有很多方式可以将动态链接库加载到 Python 进程。其中之一是 实例化 以下 类 的其中一个:

  • class ctypes.CDLL(namemode=DEFAULT_MODEhandle=Noneuse_errno=Falseuse_last_error=Falsewinmode=None)

    此类的实例 就是 已加载的动态链接库。库中的函数使用标准 C 调用约定,并假定返回 int 。在 Windows 上创建 CDLL 实例可能会失败,即使 DLL 名称确实存在。 当某个被加载 DLL 所依赖的 DLL 未找到时,将引发 OSError 错误并附带消息 "[WinError 126] The specified module could not be found". 此错误消息不包含缺失 DLL 的名称,因为 Windows API 并不会返回此类信息,这使得此错误难以诊断。 要解决此错误并确定是哪一个 DLL 未找到,你需要找出所依赖的 DLL 列表并使用 Windows 调试与跟踪工具确定是哪一个未找到。

    参见:查找 DLL 依赖的工具 ( https://docs.microsoft.com/cpp/build/reference/dependents )

  • class ctypes.OleDLL(namemode=DEFAULT_MODEhandle=Noneuse_errno=Falseuse_last_error=Falsewinmode=None) 。仅 Windows 环境可用: 此类的实例即加载好的动态链接库,其中的函数使用 stdcall 调用约定,并且假定返回 windows 指定的 HRESULT 返回码。 HRESULT 的值包含的信息说明函数调用成功还是失败,以及额外错误码。 如果返回值表示失败,会自动抛出 OSError 异常。
  • class ctypes.WinDLL(namemode=DEFAULT_MODEhandle=Noneuse_errno=Falseuse_last_error=Falsewinmode=None)。仅 Windows 环境可用:此类的实例即加载好的动态链接库,其中的函数使用 stdcall 调用约定,并假定默认返回 int 。调用动态库导出的函数之前,Python会释放 global interpreter lock ,并在调用后重新获取。
  • class ctypes.PyDLL(namemode=DEFAULT_MODEhandle=None)。这个类实例的行为与 CDLL 类似,只不过 不会 在调用函数的时候释放 GIL 锁,且调用结束后会检查 Python 错误码。 如果错误码被设置,会抛出一个 Python 异常。所以,它只在直接调用 Python C 接口函数的时候有用。

共享库也可以通用使用一个预制对象来加载,这种对象是 LibraryLoader 类的实例,具体做法或是通过调用 LoadLibrary() 方法,或是通过将库作为加载器实例的属性来提取。

  • class ctypes.LibraryLoader(dlltype) 加载共享库的类。 
            dlltype 应当为 CDLL, PyDLL, WinDLL 或 OleDLL 类型之一。
  • LoadLibrary(name)  加载共享库到进程中并将其返回。 此方法总是返回一个新的库实例。

​可用的 预制库加载器 有如下这些:

  • ctypes.cdll: cdll  是 CDll类对象。 
  • ctypes.windll: 仅限 Windows:windll WinDLL类  的 对象。 
  • ctypes.oledll: 仅限 Windows:创建 OleDLL 实例。
  • ctypes.pydll: 创建 PyDLL 实例。
  • ctypes.pythonapi: 访问 C Python api,可以使用 ctypes.pythonapi,一个 PyDLL 的实例,它将 Python C API 函数作为属性公开。 请注意所有这些函数都应返回 C int,当然这也不是绝对的,因此你必须分配正确的 restype 属性以使用这些函数。

访问 dll,首先需引入 ctypes 库:from ctypes import *

"stdcall调用约定" 和 "cdecl调用约定" 声明的导出函数,python 在加载使用时是不同。

stdcall 调用约定:两种加载方式

from ctypes import *

# 方法 1
Objdll = ctypes.windll.LoadLibrary("dllpath")  

# 方法 2
Objdll = ctypes.WinDLL("dllpath")

cdecl 调用约定:也有两种加载方式

from ctypes import *

# 方法 1
Objdll = ctypes.cdll.LoadLibrary("dllpath")  

# 方法 2
Objdll = ctypes.CDLL("dllpath")

加载 dll 的几种常用方法

ctypes 导出了 cdll对象,在 Windows 系统中还导出了 windll 和 oledll 对象,通过操作这些对象的属性,可以载入外部的动态链接库。

  • cdll :载入按标准的 cdecl 调用协议导出的函数。
  • Windows 系统中还导出了 windll 和 oledll 对象windll :导入的库按 stdcall 调用协议调用其中的函数。 oledll :也按 stdcall 调用协议调用其中的函数,并假定该函数返回的是 Windows HRESULT错误代码,并当函数调用失败时,自动根据该代码甩出一个OSError异常。

加载完 dll 后会返回一个 DLL 对象,使用其中的函数方法则相当于操作该对象的对应属性。

注意:经过 stdcall 声明的方法,如果不是用 def 文件声明的导出函数或者 extern "C" 声明的话,编译器会对函数名进行修改

  • 函数参数类型,通过设置函数的 argtypes 属性
  • 函数返回类型,函数默认返回 c_int 类型,如果需要返回其他类型,需要设置函数的 restype 属性

要加载一个dll,有好几种方法:

from ctypes import *

dll_1 = cdll.filename
dll_2 = cdll.LoadLibrary("filename")
dll_3 = CDLL("filename")

cdll.filename.func_1()
# 等价于
dll_hwnd = cdll.filename
dll_hwnd.func_1()

这三个都会调用 filename.dll ( Windows下自动补充后缀) 并返回一个句柄一样的东西,我们便可以通过该句柄去调用该库中的函数。

这里还有一个用法,由于 dll 导出的时候会有一个 exports 表,上面记录了哪些函数是导出函数,同时这个表里也有函数的序号,因此我们可以这样来访问表里的第一个函数

from ctypes import *

cdll.filename[1]

( 要注意是从1而非0开始编号 ) 返回的是该函数的指针一样的东西,如果我们要调用的话就在后面加 (parameter) 即可。关于 exports 表,GCC 似乎编译时会自动生成 def,可以在里面查,如果只有DLL的话,可以用VC的depends,或者dumpbin来查。

4、调用 DLL 中的函数

动态链接库的 导出函数 有 2 中方式进行访问

  • 属性方式:属性方式访问会缓存这个函数,因而每次访问它时返回的都是同一个对象。
  • 索引方式:索引方式访问,每次都会返回一个新的对象
from ctypes import CDLL

libc = CDLL("libc.so.6")  # On Linux

print(libc.time == libc.time)        # True
print(libc['time'] == libc['time'])  # False

4.1 示例:( Windows 环境 )

Windows 会自动添加通常的 .dll 文件扩展名。

from ctypes import *

kernel32 = windll.kernel32
print(kernel32)

user32 = windll.user32
print(user32)

libc = cdll.msvcrt
print(libc)

调用 dll 对象的属性来操作函数

from ctypes import *

libc = cdll.msvcrt
print(libc.printf)
print(windll.kernel32.GetModuleHandleA)

print(libc.time(None))
print(hex(windll.kernel32.GetModuleHandleA(None)))

print(windll.kernel32.MyOwnFunction)

调用 time() 函数返回一个系统时间戳,调用 GetModuleHandleA() 函数返回一个 win32 模块句柄。调用的两个函数都使用了空指针(用 None 作为空指针)

如果你用 cdecl 调用方式调用 stdcall 约定的函数,则会甩出一个异常 ValueError。反之亦然。

>>> cdll.kernel32.GetModuleHandleA(None)  
Traceback (most recent call last):
  File "", line 1, in 
ValueError: Procedure probably called with not enough arguments (4 bytes missing)
>>>

>>> windll.msvcrt.printf(b"spam")  
Traceback (most recent call last):
  File "", line 1, in 
ValueError: Procedure probably called with too many arguments (4 bytes in excess)
>>>

另外:printf 将打印到真正标准输出设备,而 *不是* sys.stdout,因此这些实例只能在控制台提示符下工作,而不能在 IDLE 或 PythonWin 中运行。

>>> printf = libc.printf
>>> printf(b"Hello, %s\n", b"World!")
Hello, World!
14
>>> printf(b"Hello, %S\n", "World!")
Hello, World!
14
>>> printf(b"%d bottles of beer\n", 42)
42 bottles of beer
19
>>> printf(b"%f bottles of beer\n", 42.5)
Traceback (most recent call last):
  File "", line 1, in 
ArgumentError: argument 2: exceptions.TypeError: Don't know how to convert parameter 2
>>>

除了 整数、字符串 以及 字节串 之外,所有的 Python 类型都必须使用它们对应的 ctypes 类型包装,才能够被正确地转换为所需的C语言类型。

>>> printf(b"An int %d, a double %f\n", 1234, c_double(3.14))
An int 1234, a double 3.140000
31
>>>

UNICODE 和 ANSI 版本

注意:Win32 系统的动态库,比如 kernel32 和 user32,通常会同时导出同一个函数的 ANSI 版本和 UNICODE 版本。

  • UNICODE 版本通常会在名字最后以 W 结尾
  • ANSI 版本的则以 A 结尾。

win32的 GetModuleHandle 函数会根据一个模块名返回一个 模块句柄,该函数暨同时包含这样的两个版本的原型函数,并通过宏 UNICODE 是否定义,来决定宏 GetModuleHandle 导出的是哪个具体函数。

/* ANSI version */
HMODULE GetModuleHandleA(LPCSTR lpModuleName);
/* UNICODE version */
HMODULE GetModuleHandleW(LPCWSTR lpModuleName);

windll 不会通过这样的魔法手段来帮你决定选择哪一种函数,你必须显式的调用 GetModuleHandleA 或 GetModuleHandleW,并分别使用字节对象或字符串对象作参数。

getattr() 方法 来 获得函数

有时候,dlls的导出的函数名不符合 Python 的标识符规范,比如 "??2@YAPAXI@Z"。此时,你必须使用 getattr() 方法来获得该函数。

>>> getattr(cdll.msvcrt, "??2@YAPAXI@Z")  
<_FuncPtr object at 0x...>
>>>

通过 exports 获得函数

Windows 下,有些 dll 导出的函数没有函数名,而是通过其顺序号调用。对此类函数,你也可以通过 dll 对象的数值索引来操作这些函数。

>>> cdll.kernel32[1]  
<_FuncPtr object at 0x...>
>>> cdll.kernel32[0]  
Traceback (most recent call last):
  File "", line 1, in 
  File "ctypes.py", line 310, in __getitem__
    func = _StdcallFuncPtr(name, self)
AttributeError: function ordinal 0 not found
>>>

4.2 示例:( Linux 环境 )

必须使用 包含 文件扩展名的文件名来导入共享库。因此不能简单使用对象属性的方式来导入库。因此,你可以使用方法 LoadLibrary(),或构造 CDLL 对象来导入库。

from ctypes import *

libc_so_1 = cdll.LoadLibrary("libc.so.6")
print(libc_so_1)

libc_so_2 = CDLL("libc.so.6")
print(libc_so_2)

示例 1:

写一个简单的 Hello 来说明一下最基本的用法。首先定义一个简单的 c 函数。

//hello_module.c
#include 

int hello(const char* name) {
    printf("hello %s!\n", name);
    return 0;
}

编译生成动态库,动态库不同的系统后缀不同(Windows 的 dll,Linux 的 so,Mac 的 dylib),需要注意,本文以 so 为例。

gcc -fPIC -shared hello_module.c -o hello_module.so

通过 ctypes 来进行动态库加载及函数调用,注意 windows 的调用方式有专有的 API。

import ctypes

lib = ctypes.cdll.LoadLibrary("hello_module.so")

lib.hello("world")  # hello world!

以上便是简单的 ctypes 使用流程,加载动态库,然后就可以调用动态库中的函数。

有几点需要注意的地方:

  1. 类型的隐私转换的,python 的 str 转换为了 c 的 const char*
  2. 默认的函数返回值认为是 int,不为 int 的需要自行修改
  3. 函数的参数类型未指定,只能使用 ctypes 自带的类型隐私转换

需要说明的一点是,int 和 uint 都有对应的 8、16、32、64 的类型可供使用。

示例 2:

# -*- coding: utf-8 -*-

from ctypes import *

# 学生信息如下
stu_info = [
    ("class", "A"),
    ("grade", 90),
    ("array", [1, 2, 3]),
    ("point", 4)
]


# 创建结构提类
class Student(Structure):
    _fields_ = [
        ("class", c_char),
        ("grade", c_int),
        ("array", c_long * 3),
        ("point", POINTER(c_int))
    ]


print("sizeof Student: ", sizeof(Student))

# 实例化
long_array = c_long * 3
long_array_obj = long_array(1, 2, 3)
int_p = pointer(c_int(4))
stu_info_value = [c_char(b"A"), c_int(90), long_array_obj, int_p]

stu_obj = Student(*stu_info_value)

# 结构体指针数组
# 创建结构体数组类型
stu_array = Student * 2
# # 用Student类的对象实例化结构体数组
stu_array_obj = stu_array(stu_obj, stu_obj)
# 创建结构体指针数组
stu_p_array = POINTER(Student) * 2
# 使用pointer()初始化
stu_p_array_obj = stu_p_array(pointer(stu_obj), pointer(stu_obj))


# 曾接结构体指针成员,注意使用类型初始化指针是POINTER()
class NestStudent(Structure):
    _fields_ = [
        ("rank", c_char),
        ("nest_stu", Student),
        ("strct_array", Student * 2),
        ("strct_point", POINTER(Student)),
        ("strct_point_array", POINTER(Student) * 2)
    ]


# 实例化,对Student的对象包装为指针使用pointer()
nest_stu_info_list = [c_char(b"M"), stu_obj, stu_array_obj, pointer(stu_obj), stu_p_array_obj]
nest_stu_obj = NestStudent(*nest_stu_info_list)

# # 数组第二索引为结构体指针
# print(nest_stu_obj.strct_point_array[1])
# # 指针指向Student的对象
# print(nest_stu_obj.strct_point_array[1].contents)
# # Student对象的grade字段
# print(nest_stu_obj.strct_point_array[1].contents.grade)


# 实例化动态链接库的载入对象
so_obj = cdll.LoadLibrary("./libstruct.so")

# 准备入参
char_arg = c_char(b"Z")
int_arg = c_int(13)
float_arg = c_float(3.14159)

# 准备出参
out_buf = create_string_buffer(b"", sizeof(Student))

# 注意C语言源码中入参要求是指针,所以这里需要再次使用pointer()
# rest = so_obj.test_func(char_arg, int_arg, float_arg, pointer(stu_obj), pointer(nest_stu_obj), out_buf)
# 或者使用ctypes.bryef()方法自动转换,更快一点
rest = so_obj.test_func(char_arg, int_arg, float_arg, byref(stu_obj), byref(nest_stu_obj), out_buf)

# 打印函数返回值
print("func rest: ", rest)
# 打印出参
print("out buf: ", out_buf[0:sizeof(c_int) * 2])

输出:

stu info: 90 1 4
char arg: Z
int arg: 13
float arg: 3.141590
struct class: A
struct grade: 90
struct array[0]: 1 array[1]: 2
struct point: 4
nest struct rank: 77
nest struct stu grade: 90
nest struct array[0] grade: 90
nest struct array[1] grade: 90
nest struct point grade: 90
nest struct point array[0] grade: 90
nest struct point array[1] grade: 90
func rest: 1
out buf: b'A\x00\x00\x00Z\x00\x00\x00' # 此处90转换为字符打印了

如果python需要使用动态链接库的符号,直接调用即可,如下:

# 调用 C 源码中的 g_stu 结构体
so_symble = so_obj.g_stu_t
print(so_symble)

输出:

<_FuncPtr object at 0x7fa8aff57120>

最后,关于共用体、位域、函数返回值等方法,这部分的方法建议看一下官方文档。

5、回调函数

ctypes 允许创建一个指向 Python 可调用对象的 C 函数。它们有时候被称为 回调函数 。

首先,你必须为回调函数创建一个类,这个类知道调用约定,包括返回值类型以及函数接收的参数类型及个数。

  • CFUNCTYPE() 工厂函数使用 cdecl 调用约定创建回调函数类型。
  • 在 Windows 上, WINFUNCTYPE() 工厂函数使用 stdcall 调用约定为回调函数创建类型。

这些工厂函数的第一个参数是返回值类型,回调函数的参数类型作为剩余参数。

这里展示一个使用 C 标准库函数 qsort() 的例子,它使用一个回调函数对数据进行排序。 qsort() 将用来给整数数组排序

from ctypes import *


def py_cmp_func(a, b):
    print("py_cmp_func", a[0], b[0])
    return 0


libc = cdll.msvcrt           # 导入 lib

IntArray5 = c_int * 5             # 定义 整数型数组 类型
ia = IntArray5(5, 1, 7, 33, 99)   # 数组初始化

qsort = libc.qsort                # 得到 qsort 函数
qsort.restype = None              # 设置 返回值类型

# qsort() 的参数
#     : 指向待排序数据的指针,
#     : 元素个数,
#     : 每个元素的大小,
#     : 指向排序函数的指针,即回调函数。
# 回调函数接收两个元素的指针,
#     如果第一个元素小于第二个,则返回一个负整数,
#     如果相等则返回0,否则返回一个正整数。
# 所以,回调函数要接收两个整数指针,返回一个整数。首先我们创建回调函数的 类型
CMP_FUNC = CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int))  # 创建回调函数的 类型

cmp_func = CMP_FUNC(py_cmp_func)  # 创建回调函数

qsort(ia, len(ia), sizeof(c_int), cmp_func)  # 调用函数

工厂函数可以当作装饰器工厂,所以可以这样写:

from ctypes import *


def py_cmp_func(a, b):
    print("py_cmp_func", a[0], b[0])
    return 0


@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]


libc = cdll.msvcrt                # 导入 lib
IntArray5 = c_int * 5             # 定义 整数型数组 类型
ia = IntArray5(5, 1, 7, 33, 99)   # 数组初始化
qsort = libc.qsort                # 得到 qsort 函数
qsort.restype = None              # 设置 返回值类型
qsort(ia, len(ia), sizeof(c_int), py_cmp_func)

6、函数原型

函数原型 就相当于 C/C++ 中 函数的声明,也仅仅只是声明,没有定义。

外部函数 也可通过实例化 函数原型 来创建。 "函数原型" 类似于 C 中的函数原型;它们在不定义具体实现的情况下描述了一个函数(返回类型、参数类型、调用约定)。

工厂函数 必须使用函数所需要的结果类型和参数类型来调用,并可被用作装饰器工厂函数,在此情况下可以通过 @wrapper 语法应用于函数。 

  • ctypes.CFUNCTYPE(restype*argtypesuse_errno=Falseuse_last_error=False)。返回的函数原型会创建使用标准 C 调用约定的函数。 该函数在调用过程中将释放 GIL。 如果 use_errno 设为真值,则在调用之前和之后系统 errno 变量的 ctypes 私有副本会与真正的 errno 值进行交换;use_last_error 会为 Windows 错误码执行同样的操作。
  • ctypes.WINFUNCTYPE(restype*argtypesuse_errno=Falseuse_last_error=False)。Windows only: The returned function prototype creates functions that use the stdcall calling convention. The function will release the GIL during the call. use_errno and use_last_error have the same meaning as above.
  • ctypes.PYFUNCTYPE(restype*argtypes)。返回的函数原型会创建使用 Python 调用约定的函数。 该函数在调用过程中将 不会 释放 GIL。

这些工厂函数所创建的函数原型可通过不同的方式来实例化,具体取决于调用中的类型与数量。

  • prototype(address)。在指定地址上返回一个外部函数,地址值必须为整数。
  • prototype(callable)。基于 Python callable 创建一个 C 可调用函数(回调函数)。
  • prototype(func_spec[, paramflags])。返回由一个共享库导出的外部函数。 func_spec 必须为一个 2 元组 (name_or_ordinal, library)。 第一项是字符串形式的所导出函数名称,或小整数形式的所导出函数序号。 第二项是该共享库实例。
  • prototype(vtbl_indexname[, paramflags[, iid]])。返回将调用一个 COM 方法的外部函数。 vtbl_index 虚拟函数表中的索引。 name 是 COM 方法的名称。 iid 是可选的指向接口标识符的指针,它被用于扩展的错误报告。COM 方法使用特殊的调用约定:除了在 argtypes 元组中指定的形参,它们还要求一个指向 COM 接口的指针作为第一个参数。

可选的 paramflags 形参会创建相比上述特性具有更多功能的外部函数包装器。

paramflags 必须为一个与 argtypes 长度相同的元组。

此元组中的每一项都包含有关形参的更多信息,它必须为包含一个、两个或更多条目的元组。

第一项是包含形参指令旗标组合的整数。

1    指定函数的一个输入形参。

2    输出形参。 外部函数会填入一个值。

4    默认为整数零值的输入形参。

可选的第二项是字符串形式的形参名称。 如果指定此项,则可以使用该形参名称来调用外部函数。可选的第三项是该形参的默认值。

示例:演示了如何包装 Windows 的 MessageBoxW 函数以使其支持默认形参和已命名参数。 相应 windows 头文件的 C 声明是这样的:

WINUSERAPI int WINAPI
MessageBoxW(
    HWND hWnd,
    LPCWSTR lpText,
    LPCWSTR lpCaption,
    UINT uType);

这是使用 ctypes 的包装:

from ctypes import c_int, WINFUNCTYPE, windll
from ctypes.wintypes import HWND, LPCWSTR, UINT

prototype = WINFUNCTYPE(c_int, HWND, LPCWSTR, LPCWSTR, UINT)
paramflags = (1, "hwnd", 0), (1, "text", "Hi"), (1, "caption", "Hello from ctypes"), (1, "flags", 0)
MessageBox = prototype(("MessageBoxW", windll.user32), paramflags)

MessageBox()
MessageBox(text="Spam, spam, spam")
MessageBox(flags=2, text="foo bar")

示例:演示了输出形参。 这个 win32 GetWindowRect 函数通过将指定窗口的维度拷贝至调用者必须提供的 RECT 结构体来提取这些值。 这是相应的 C 声明:

WINUSERAPI BOOL WINAPI
GetWindowRect(
     HWND hWnd,
     LPRECT lpRect);

这是使用 ctypes 的包装:

>>> from ctypes import POINTER, WINFUNCTYPE, windll, WinError
>>> from ctypes.wintypes import BOOL, HWND, RECT
>>> prototype = WINFUNCTYPE(BOOL, HWND, POINTER(RECT))
>>> paramflags = (1, "hwnd"), (2, "lprect")
>>> GetWindowRect = prototype(("GetWindowRect", windll.user32), paramflags)
>>>

带有输出形参的函数如果输出形参存在单一值则会自动返回该值,或是当输出形参存在多个值时返回包含这些值的元组,因此当 GetWindowRect 被调用时现在将返回一个 RECT 实例。

输出形参可以与 errcheck 协议相结合以执行进一步的输出处理与错误检查。 Win32 GetWindowRect API 函数返回一个 BOOL 来表示成功或失败,因此此函数可执行错误检查,并在 API 调用失败时引发异常:

>>> def errcheck(result, func, args):
...     if not result:
...         raise WinError()
...     return args
...
>>> GetWindowRect.errcheck = errcheck
>>>

如果 errcheck 不加更改地返回它所接收的参数元组,则 ctypes 会继续对输出形参执行常规处理。 如果你希望返回一个窗口坐标的元组而非 RECT 实例,你可以从函数中提取这些字段并返回它们,常规处理将不会再执行:

>>> def errcheck(result, func, args):
...     if not result:
...         raise WinError()
...     rc = args[1]
...     return rc.left, rc.top, rc.bottom, rc.right
...
>>> GetWindowRect.errcheck = errcheck
>>>

7、ctypes 提供的工具函数

ctypes.addressof(obj)

ctypes.addressof(obj):以整数形式返回内存缓冲区地址。 obj 必须为一个 ctypes 类型的实例。

ctypes.alignment(obj_or_type)

ctypes.alignment(obj_or_type):返回一个 ctypes 类型的对齐要求。 obj_or_type 必须为一个 ctypes 类型或实例。

ctypes.byref(obj[, offset])

ctypes.byref(obj[, offset]):返回指向 obj 的轻量指针,该对象必须为一个 ctypes 类型的实例。 offset 默认值为零,且必须为一个将被添加到内部指针值的整数。

byref(obj, offset) 对应于这段 C 代码 : (((char *)&obj) + offset)
返回的对象只能被用作外部函数调用形参。 
它的行为类似于 pointer(obj),但构造起来要快很多。

ctypes.cast(obj, type)

ctypes.cast(objtype)

此函数类似于 C 的强制转换运算符。 它返回一个 type 的新实例,该实例指向与 obj 相同的内存块。 type 必须为指针类型,而 obj 必须为可以被作为指针来解读的对象。

ctypes.create_string_buffer(init_or_size, size=None)

ctypes.create_string_buffer(init_or_sizesize=None):此函数会创建一个可变的字符缓冲区。 返回的对象是一个 c_char 的 ctypes 数组。init_or_size 必须是一个指明数组大小的整数,或者是一个将被用来初始化数组条目的字节串对象。如果将一个字节串对象指定为第一个参数,则将使缓冲区大小比其长度多一项以便数组的最后一项为一个 NUL 终结符。 可以传入一个整数作为第二个参数以允许在不使用字节串长度的情况下指定数组大小。

ctypes.create_unicode_buffer(init_or_size, size=None)

ctypes.create_unicode_buffer(init_or_sizesize=None):此函数会创建一个可变的 unicode 字符缓冲区。 返回的对象是一个 c_wchar 的 ctypes 数组。init_or_size 必须是一个指明数组大小的整数,或者是一个将被用来初始化数组条目的字符串。如果将一个字符串指定为第一个参数,则将使缓冲区大小比其长度多一项以便数组的最后一项为一个 NUL 终结符。 可以传入一个整数作为第二个参数以允许在不使用字符串长度的情况下指定数组大小。

ctypes.DllCanUnloadNow()

ctypes.DllCanUnloadNow():仅限 Windows:此函数是一个允许使用 ctypes 实现进程内 COM 服务的钩子。 它将由 _ctypes 扩展 dll 所导出的 DllCanUnloadNow 函数来调用。

ctypes.DllGetClassObject()

ctypes.DllGetClassObject():仅限 Windows:此函数是一个允许使用 ctypes 实现进程内 COM 服务的钩子。 它将由 _ctypes 扩展 dll 所导出的 DllGetClassObject 函数来调用。

ctypes.util.find_library(name)

ctypes.util.find_library(name):尝试寻找一个库并返回路径名称。 name 是库名称并且不带任何前缀如 lib 以及后缀如 .so.dylib 或版本号(形式与 posix 链接器选项 -l 所用的一致)。 如果找不到库,则返回 None

确切的功能取决于系统。

ctypes.util.find_msvcrt()

ctypes.util.find_msvcrt():仅限 Windows:返回 Python 以及扩展模块所使用的 VC 运行时库的文件名。 如果无法确定库名称,则返回 None

如果你需要通过调用 free(void *) 来释放内存,例如某个扩展模块所分配的内存,重要的一点是你应当使用分配内存的库中的函数。

ctypes.FormatError([code])

ctypes.FormatError([code]):仅限 Windows:返回错误码 code 的文本描述。 如果未指定错误码,则会通过调用 Windows api 函数 GetLastError 来获得最新的错误码。

ctypes.GetLastError()

ctypes.GetLastError():仅限 Windows:返回 Windows 在调用线程中设置的最新错误码。 此函数会直接调用 Windows GetLastError() 函数,它并不返回错误码的 ctypes 私有副本。

ctypes.get_errno()

ctypes.get_errno():返回调用线程中系统 errno 变量的 ctypes 私有副本的当前值。

ctypes.get_last_error()

ctypes.get_last_error():仅限 Windows:返回调用线程中系统 LastError 变量的 ctypes 私有副本的当前值。

ctypes.memmove(dst, src, count)

ctypes.memmove(dstsrccount)

与标准 C memmove 库函数相同:将 count 个字节从 src 拷贝到 dst。 dst 和 src 必须为整数或可被转换为指针的 ctypes 实例。

ctypes.memset(dst, c, count)

ctypes.memset(dstccount):与标准 C memset 库函数相同:将位于地址 dst 的内存块用 count 个字节的 c 值填充。 dst 必须为指定地址的整数或 ctypes 实例。

ctypes.POINTER(type)

ctypes.POINTER(type):这个工厂函数创建并返回一个新的 ctypes 指针类型。 指针类型会被缓存并在内部重用,因此重复调用此函数耗费不大。 type 必须为 ctypes 类型。

ctypes.pointer(obj)

ctypes.pointer(obj):此函数会创建一个新的指向 obj 的指针实例。 返回的对象类型为 POINTER(type(obj))

注意:如果你只是想向外部函数调用传递一个对象指针,你应当使用更为快速的 byref(obj)

ctypes.resize(obj, size)

ctypes.resize(objsize):此函数可改变 obj 的内部内存缓冲区大小,其参数必须为 ctypes 类型的实例。 没有可能将缓冲区设为小于对象类型的本机大小值,该值由 sizeof(type(obj)) 给出,但将缓冲区加大则是可能的。

ctypes.set_errno(value)

ctypes.set_errno(value):设置调用线程中系统 errno 变量的 ctypes 私有副本的当前值为 value 并返回原来的值。

ctypes.set_last_error(value)

ctypes.set_last_error(value):仅限 Windows:设置调用线程中系统 LastError 变量的 ctypes 私有副本的当前值为 value 并返回原来的值。

ctypes.sizeof(obj_or_type)

ctypes.sizeof(obj_or_type):返回 ctypes 类型或实例的内存缓冲区以字节表示的大小。 其功能与 C sizeof 运算符相同。

ctypes.string_at(address, size=- 1)

ctypes.string_at(addresssize=- 1):此函数返回从内存地址 address 开始的以字节串表示的 C 字符串。 如果指定了 size,则将其用作长度,否则将假定字符串以零值结尾。

ctypes.WinError(code=None, descr=None)

ctypes.WinError(code=Nonedescr=None):仅限 Windows:此函数可能是 ctypes 中名字起得最差的函数。 它会创建一个 OSError 的实例。 如果未指定 code,则会调用 GetLastError 来确定错误码。 如果未指定 descr,则会调用 FormatError() 来获取错误的文本描述。

ctypes.wstring_at(address, size=- 1)

ctypes.wstring_at(addresssize=- 1):此函数返回从内存地址 address 开始的以字符串表示的宽字节字符串。 如果指定了 size,则将其用作字符串中的字符数量,否则将假定字符串以零值结尾。

你可能感兴趣的:(python,c++,开发语言)