ctypes 是 Python 的外部函数库。它提供了与 C 兼容的数据类型,并允许调用 DLL 或共享库中的函数。可使用该模块以纯 Python 形式对这些库进行封装。
官方提供了详尽的文档:https://docs.python.org/zh-cn/3.9/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)
#
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) # 打印,默认应该是当作函数访问的
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
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
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?