0、前言
ctypes 是 Python 的外部函数库。它提供了与 C 兼容的数据类型,并允许调用 DLL 或共享库中的函数。可使用该模块以纯 Python 形式对这些库进行封装。
官方提供了详尽的文档:https://docs.python.org/zh-cn/3.12/library/ctypes.html ,配合网友的示例可以很快上手。本文主要是记录一些基本操作。
import platform from ctypes import * if platform.system() == 'Windows': libc = cdll.LoadLibrary('msvcrt.dll') elif platform.system() =='Linux': libc = cdll.LoadLibrary('libc.so.6') libc.printf(b'Hello ctypes!\n')
讯享网
1、数据类型
ctypes 提供了一些基本数据类型用来映射 C 语言和 Python 的类型,对照表可见文档,常用的几个:
对一个 ctypes 类型乘以一个正数可以创建一个数组类型。
讯享网# int数组 int_arr = ctypes.c_int * 5 # 相当于C语言的 int[5] # 可以指定数据来初始化数组 my_arr = int_arr(1, 3, 5, 7, 9) # <__main__.c_long_Array_5 object at 0x0000023A3FF4F8C0> print(my_arr)
ctypes 预定义的指针类型只提供了几个, 可以使用 ctypes.POINTER 来定义新的指针类型,ctypes.pointer() 函数构造一个指针对象, ctypes.byref() 函数相当于对对象取地址。无参调用指针类型可以创建一个 NULL 指针, NULL 指针的布尔值是 False。
# int类型 num = ctypes.c_int(1992) # int指针 ctypes.POINTER(ctypes.c_int) # 对实例取地址,可作为指针参数传递 ctypes.byref(num) # 生成一个指针对象 ctypes.pointer(num) # 空指针 null_ptr = ctypes.POINTER(ctypes.c_int)() print(bool(null_ptr)) # 打印 False
结构体和联合必须继承自 ctypes 模块中的 Structure 和 Union 。子类必须定义 _fields_ 属性。 _fields_ 是一个二元组列表( tuple ),二元组中包含 field name 和 field type 。
type 字段必须是一个 ctypes 类型,比如 c_int,或者其他 ctypes 类型: 结构体、联合、数组、指针。
默认情况下,结构体和联合的字段与 C 的字节对齐是一样的。也可以在定义子类的时候指定类的 _pack_ 属性指定字节对齐大小。
讯享网# 如果指定大小端,可使用基类BigEndianStructure或LittleEndianStructure class MyStruct(ctypes.Structure): _pack_ = 1 # 指定为1字节对齐 _fields_ = [ ("a", ctypes.c_char), ("b", ctypes.c_int), ("c", ctypes.c_double) ] # 打印字节大小,测试pack设置是否有效 print("sizeof MyStruct:", ctypes.sizeof(MyStruct))
2、变量访问与函数调用
使用 ctypes.cdll.LoadLibrary(path) 加载对应的 dll 后,可以访问 C/C++ 导出的变量/函数符号。如果是 C++ 编译器,需要使用 extern "C"。此外,编译时选择的位数应和使用的 Python 位数一致,比如都是 x86 或者 x64 的。

在 Windows MSVC 中,可以这样写:
#ifdef MYDLL_EXPORTS #define MYDLL_API __declspec(dllexport) #else #define MYDLL_API __declspec(dllimport) #endif extern "C" { // 导出变量 extern MYDLL_API int my_number; // 导出函数 MYDLL_API int my_func(int a, int b); }
对于上面导出的变量和函数,访问方式如下:(可以看到函数可以直接访问,变量通过 type.in_dll() 函数访问)
讯享网import ctypes dll = ctypes.cdll.LoadLibrary("D:/TestSpace/TestCpp_mydll/x64/Release/mydll.dll") # 访问变量 print(dll.my_number) # 打印<_FuncPtr object at 0x0000022537F1B450>,默认应该是当作函数访问的 print(ctypes.c_int.in_dll(dll, 'my_number').value) # 访问函数 print(dll.my_func(12, 34))
ctypes 默认假定函数返回 int 类型,可以设置函数对象的 restype 属性来指定具体类型。对于参数类型也可以通过设置函数对象的 argtypes 来指定具体类型,防止不合理的参数传递。
my_func = dll.my_func # 函数对象 my_func.argtypes = [ctypes.c_double, ctypes.c_double] # 指定参数类型 my_func.restype = ctypes.c_double # 指定返回值类型 print(my_func(12, 34.5))
对于指针参数或者结构体参数的构造参见上一节,或者下面测试代码的内容。
3、测试代码
(C/C++ 代码在 Windows VS 环境编写)
讯享网// 下列 ifdef 块是创建使从 DLL 导出更简单的宏的标准方法。 // 在预处理器中定义了 MYDLL_EXPORTS 符号,而调用方不包含此符号。 // 源文件中包含此文件的任何其他项目都会将 MYDLL_API 视为是从 DLL 导入的, // 而此 DLL 则将用此宏定义的符号视为是被导出的。 #ifdef MYDLL_EXPORTS #define MYDLL_API __declspec(dllexport) #else #define MYDLL_API __declspec(dllimport) #endif // ctypes需要提供c接口,主要是目前cpp还没有统一的abi extern "C" { // 导出一个全局变量 extern MYDLL_API int my_number; // 函数 MYDLL_API int my_func(int a, int b); MYDLL_API double my_func2(double a, double b); MYDLL_API int * my_func3(char *a, double *b, const char *str); // 指针 extern MYDLL_API char *char_ptr; extern MYDLL_API int *int_ptr; extern MYDLL_API int *null_ptr; // 打印dll中的变量 MYDLL_API void print_var(); // 结构体 struct MYDLL_API MyStruct { int a; double b; }; MYDLL_API MyStruct my_func4(const MyStruct &arg); MYDLL_API MyStruct * my_func5(MyStruct *arg); }
// MSVC模板默认带pch,设置为不使用预编译头 // #include "pch.h" #include "mydll.h" #include <iostream> MYDLL_API int my_number = 1992; MYDLL_API int my_func(int a, int b) { return a + b; } MYDLL_API double my_func2(double a, double b) { return a + b; } MYDLL_API int * my_func3(char * a, double * b, const char *str) { std::cout << "dll myfunc3:" << str << std::endl; my_number = *a + int(*b); return &my_number; } MYDLL_API char *char_ptr = new char(65); MYDLL_API int *int_ptr = new int(250); MYDLL_API int *null_ptr = nullptr; MYDLL_API void print_var() { std::cout << "dll print var:" << "\n\tmy number:" << my_number << "\n\tchar ptr:" << *char_ptr << "\n\tint ptr:" << *int_ptr << std::endl; } MYDLL_API MyStruct my_func4(const MyStruct & arg) { MyStruct ret{ 12,34.5 }; std::cout << "dll myfunc4:" << "in:" << arg.a << " " << arg.b << "ret:" << ret.a << " " << ret.b << std::endl; return ret; } MYDLL_API MyStruct * my_func5(MyStruct * arg) { if (arg) { arg->a = 67; arg->b = 89.5; } return arg; }
讯享网import ctypes # dll = ctypes.cdll.LoadLibrary("msvcrt.dll") # dll.printf(b"hello ctypes\n") dll = ctypes.cdll.LoadLibrary( "D:/TestSpace/TestCpp__mydll/x64/Release/mydll.dll") # 访问变量 print(dll.my_number) # 打印<_FuncPtr object at 0x0000022537F1B450>,默认应该是当作函数访问的 print(ctypes.c_int.in_dll(dll, 'my_number').value) # 访问函数 print(dll.my_func) print(dll.my_func(12, 34)) # ctypes 默认假定函数返回 int 类型,可以设置函数对象的 restype 属性来指定具体类型。 # 对于参数类型也可以通过设置函数对象的 argtypes 来指定具体类型,防止不合理的参数传递。 my_func2 = dll.my_func2 my_func2.argtypes = [ctypes.c_double, ctypes.c_double] my_func2.restype = ctypes.c_double print(my_func2(12, 34.5)) my_func3 = dll.my_func3 my_func3.argtypes = [ctypes.c_char_p, ctypes.POINTER(ctypes.c_double)] my_func3.restype = ctypes.POINTER(ctypes.c_int) arg1 = ctypes.c_char(12) arg2 = ctypes.c_double(1000) # byref相当于取引用,c字符串传字节数组 ret = my_func3(ctypes.byref(arg1), ctypes.byref(arg2), b'hello ctypes.') print(ret.contents.value) dll.print_var() # 指针变量 # ptr = ctypes.POINTER(ctypes.c_int) # char(65)->print(b'A'),可能会把后面挨着的字节内容也打印 print(ctypes.c_char_p.in_dll(dll, 'char_ptr').value) print(ctypes.POINTER(ctypes.c_int).in_dll(dll, 'int_ptr').contents.value) ctypes.POINTER(ctypes.c_int).in_dll(dll, 'int_ptr').contents.value = 520 # 空指针bool值为false print(bool(ctypes.POINTER(ctypes.c_int).in_dll(dll, 'null_ptr'))) dll.print_var() # struct或union必须派生ctypes给的基类Structure和Union # 每个子类都必须定义一个_fields_属性,_fields_必须是一个2-tuples列表,包含一个字段名和一个字段类型。 class MyStruct(ctypes.Structure): _fields_ = [ ("a", ctypes.c_int), ("b", ctypes.c_double) ] my_func4 = dll.my_func4 my_func4.restype = MyStruct arg = MyStruct() arg.a = 10 arg.b = 11 # print(ctypes.sizeof(MyStruct)) ret = my_func4(arg) print('my_func4 ret:', ret.a, ' ', ret.b) my_func5 = dll.my_func5 my_func5.restype = ctypes.POINTER(MyStruct) ret = my_func5(ctypes.byref(arg)).contents print('my_func5 ret:', ret.a, ' ', ret.b)
4、参考
参考文档:ctypes --- Python 的外部函数库 — Python 3.9.17 文档
参考博客:浅谈python中使用C/C++:ctypes - 知乎
参考博客:python3 ctypes模块使用方法与心得体会--- int* ,char*等指针类型互转_ctypes int指针_Fu_Lin_的博客-CSDN博客
参考博客:https://www.cnblogs.com/gaowengang/p/7919219.html

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/127986.html