Python ctypes模块的基本使用
ctypes是Python的外部函数库。它提供了与C兼容的数据类型,并允许调用DLL或共享库中的函数。可使用该模块以纯Python形式对这些库进行封装。官方提供了详尽的文档:https://docs.python.org/zh-cn/3.9/library/ctypes.html,配合网友的示例可以很快上手。本文主要是记录一些基本操作。
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_20210617_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博客
更多推荐
所有评论(0)