python调用C++——ctypes
python3调用cpp的方法——python调用so
python中使用ctypes模块可以在python中直接调用C/C++。
首先要将C/C++编译成动态库(.so),之后python中调用即可
特别注意:在调用C++函数需要在函数声明时,加入前缀extern “C” ,这是由于C++支持函数重载功能,在编译时会更改函数名。在函数声明时,前缀extern “C”则确保按C的方式编译。
一定要有函数输入输出类型的声明,int型不用转换,float和double类型需要进行转换,ctypes中的变量类型与C中对应如下:
| ctypes数据类型 | C数据类型 | 
|---|---|
| c_char | char | 
| c_short | short | 
| c_int | int | 
| c_long | long | 
| c_float | float | 
| c_double | double | 
| c_void_p | void | 
| c_uint8 | unsigned char | 
使用步骤:
1、编写c++代码
1  | 
  | 
2、将cpp编译成so
1  | g++ -shared -Wl,-soname,test -o test.so -fPIC test.cpp  | 
其中test为cpp的名称
3、使用python调用即可
1  | # -*- coding: utf-8 -*-  | 
注意看这里的convert_type
Python C/C++联合编程实战-Python CTypes访问C/C++动态链接库
windows版本:
需要:
- dll动态链接库、
 __declspec(dllexport)要导出函数名,才可以生成dll;extern "C"在C++代码里加这一行,编译成C的函数。这是因为python支持的是C语言,而C语言不支持重载,因此要把C++代码里写一个extern C,表示变成C语言名称,不要自己多加一些函数名这个意思(C++编译时,函数名会加上参数,因此可以区分出不一样的函数(重载))。- 库在系统目录或当前执行目录下(要把库放在哪边,因为执行脚本时要调用这个库)
 - 与python库查找路径无关 sys.path
 
举例:
在visual studio里新建项目,选择dll
然后新建一个cpp文件:
1  | 
  | 
然后选菜单的 “生成” —— “生成解决方案”,就会导出一个dll文件
注意,这里的dll是32位还是64位,要与后面python的32位或64位一致。比如我的python是64位(查看python是几位,通过在powershell里输入python得知)那么导出的dll就要是64位,否则会报 OSError: [WinError 193] %1 不是有效的 Win32 应用程序。 错误。
dll设置位64位:
选择 x64.
或者在visual studio的 侧边栏 解决方案 项目名 右键——属性——(右上)配置管理器——活动解决方案平台,选择x64.
新建一个python文件:
1  | from ctypes import *  | 
这里的 testctypes,是导出的 dll名称,这里不需要填 testctypes.dll后缀,只要写前面名称就好。
然后在windows里的powershell执行python文件。
注意,如果python文件的路径和dll路径不一致。有三种解决方法:
- 复制dll到python文件所在目录;
 - python文件里写上相对路径,比如:
lib = CDLL("x64/Debug/testctypes") - dll文件导出时写相对路径:visual studio的侧边栏 解决方案 项目名 右键——属性——链接器——输出文件,比如把
$(OutDir)$(TargetName)$(TargetExt)改成..\$(TargetName)$(TargetExt)就是上一级目录(比如python文件在上一级目录下) 
Linux版本:
- so动态链接库 64位(链接库 也叫 目标程序),目标程序位数要和平台相关
 - 代码字符集 utf-8(在windows的visual studio是gbk编码),
 g++ -fPIC -shared -o $@ $< -finput-charset='gbk'编译出一个动态链接库 文件。比如:g++ -fPIC -shared -o a a.cpp- -fPIC:使得动态链接库位置无关?
 - -shared:对应的动态链接库版本
 - -finput-charset=’gbk’ :指定的输出格式
 
- so库在 
/usr/lib或者环境变量路径 - 如果要在当前路径调用库,
export LD_LIBRARY_PATH ./要把库的路径export进库path里。(windows就不需要,windows找库路径时,会去找当前路径下有没有这个库,而linux只会找LD_LIBRARY_PATH和/usr/lib里有没有这个库) 
这里cpp文件是(比如 a.cc):
1  | 
  | 
这里就不像windows里的写法那样,需要 __declspec(dllexport) , 这里不需要。
另一种写法:通过 宏 来判断是linux系统或者windows系统。一次到位,就不用对不同系统写不同cpp代码了。
1  | 
  | 
执行g++ -fPIC -shared -o a a.cc 或者 g++ -fPIC -shared -o a a.cpp 或者 g++ -fPIC -shared -o a.so a.cc
ldd a.so 看一看。
python文件是(比如a.py):
1  | import ctypes  | 
执行 python a.py
关于路径问题,可以把每次编译完了之后的so文件,都复制到 /usr/lib路径下,就没有路径问题了(写一个shell每次自动复制过去,或者写一个makefile),或者export进lib path里 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/mnt/k/c_practice 。
写一个makefile:
1  | a.so:a.cc  | 
这里 $@ 指的 a.so, $< 指的 a.cc。
然后命令行 make 
mac版本
- 与linux基本一致,但不支持代码GBK
 - 演示用g++
 - VS代码格式改为utf-8
 
改成utf-8的方法:
- 用notepad++打开cpp文件,菜单栏——编码——utf-8
 - 打开Visual Studio项目,菜单栏——文件——高级保存选项——Unicode(UTF-8 无签名)
 
ctypes类型对应
64位的程序,对指针的存放要特别关注。因为指针用64位(8个字节)地址,如果变成运行在32位系统,则会丢失一部分信息(只读写了32位,剩下没读写完)。
传递数字参数
- 传递float和int
 - c_int() c_float()
 - v=c_int(101)
 - print(v.value)
 
ctypes整数和浮点数类型参数传递代码示例和异常处理:
举例:
c++代码:
1  | 
  | 
make一下,生成 a.so。
python代码:
1  | from ctypes import *  | 
python a.py 输出:
1  | n C testctypes 101 99.099998 1  | 
ctypes类型对应
传参,要把python类型转成c语言支持的;返回值,要把c语言类型转成python支持的。
ctypes 定义了一些和C兼容的基本数据类型:
这里ctypes类型都看作函数,转换函数,把python类型转成c类型的转换函数。
| ctypes 类型 | C 类型 | Python 类型 | 
|---|---|---|
c_bool | 
_Bool | bool (1) | 
c_char | 
char | 单字符字节串对象(一个字节存放一个字符) | 
c_wchar | 
wchar_t | 单字符字符串(两个字节存放一个字符,宽字节,unsigned short) | 
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) | 字节串对象或 None | 
c_wchar_p | 
wchar_t* (NUL terminated) | 字符串或 None | 
c_void_p | 
void* | int 或 None | 
- 构造函数接受任何具有真值的对象。
 
所有这些类型都可以通过使用正确类型和值的可选初始值调用它们来创建:
from ctypes import *
1  | c_int()  | 
由于这些类型是可变的,它们的值也可以在以后更改:
1  | i = c_int(42)  | 
当给指针类型的对象 c_char_p, c_wchar_p 和 c_void_p 等赋值时,将改变它们所指向的 内存地址,而 不是 它们所指向的内存区域的 内容 (这是理所当然的,因为 Python 的 bytes 对象是不可变的):
1  | s = "Hello, World"  | 
但你要注意不能将它们传递给会改变指针所指内存的函数。如果你需要可改变的内存块,ctypes 提供了 create_string_buffer() 函数,它提供多种方式创建这种内存块。当前的内存块内容可以通过 raw 属性存取,如果你希望将它作为NUL结束的字符串,请使用 value 属性:
1  | from ctypes import *  | 
The create_string_buffer() function replaces the old c_buffer() function (which is still available as an alias). To create a mutable memory block containing unicode characters of the C type wchar_t, use the create_unicode_buffer() function.
chatgpt回答
以下回答来自chatgpt:
Python 可以通过 Cython、ctypes、SWIG 等方式与 C++ 进行交互。
- Cython 可以把 Python 代码编译为 C++ 代码,它的语法是 Python 的语法的扩展,可以直接调用 C++ 代码;
 - ctypes 可以直接加载并调用动态链接库,并且可以很容易地调用 C 函数;
 - SWIG 可以自动生成 Python 接口,方便地从 Python 中调用 C++ 代码。
 
- 使用Cython或SWIG:这些工具可以让你编写接口代码,它们会把这些代码编译成可以在Python环境下运行的代码。
 - 使用ctypes或cffi:这些库可以动态加载C++代码作为C函数,从而在Python代码中调用。
 
下面是一个使用ctypes的例子:
c++:(a.cc)
1  | // a.cc  | 
python:(a.py)
1  | # a.py  | 
把c++编译成 libexample.so:g++ -shared -fPIC -o libexample.so a.cc
然后执行代码:python a.py 。就能得到结果了。
这是一个简单的例子,它仅仅是说明了如何调用C++代码。请注意,这只是一种方法,具体应用取决于您的需求和使用场景。