python调用C++——ctypes

python调用C++——ctypes

python3调用cpp的方法——python调用so

csdn: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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
#include <string>
#include <cstdlib>
#include <vector>
#include <stdio.h>


class Test{
private:
double _calculate(int a, double b);
public:
double calculate(int a, double b, char c[], int * d, double * e, char ** f);
};

double Test::_calculate(int a, double b){
double res = a+b;
std::cout<<"res: "<<res<<std::endl;
return res;
}

double Test::calculate(int a, double b, char c[], int * d, double * e, char ** f){
std::cout<<"a: "<<a<<std::endl;
std::cout<<"b: "<<b<<std::endl;
std::cout<<"c: "<<c<<std::endl;
std::cout<<"d: "<<d[0]<<d[1]<<std::endl;
std::cout<<"e: "<<e[0]<<e[1]<<std::endl;
std::cout<<"f: "<<f[0]<<f[1]<<std::endl;
return this->_calculate(a, b);
}


// 封装C接口
extern "C"{
// 创建对象
Test* test_new(){
return new Test;
}
double my_calculate(Test* t, int a, double b, char c[], int * d, double * e, char ** f){
return t->calculate(a, b,c,d,e,f);
}
}

2、将cpp编译成so

1
g++ -shared -Wl,-soname,test -o test.so -fPIC test.cpp

其中test为cpp的名称

3、使用python调用即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# -*- coding: utf-8 -*-
import ctypes
# 指定动态链接库
lib = ctypes.cdll.LoadLibrary('./test.so')
#需要指定返回值的类型,默认是int
lib.my_calculate.restype = ctypes.c_double

class Test(object):
def __init__(self):
# 动态链接对象
self.obj = lib.test_new()

def calculate(self, a, b,c,d,e,f):
res = lib.my_calculate(self.obj, a, b,c,d,e,f)
return res

#将python类型转换成c类型,支持int, float,string的变量和数组的转换
def convert_type(input):
ctypes_map = {int:ctypes.c_int,
float:ctypes.c_double,
str:ctypes.c_char_p
}
input_type = type(input)
if input_type is list:
length = len(input)
if length==0:
print("convert type failed...input is "+input)
return null
else:
arr = (ctypes_map[type(input[0])] * length)()
for i in range(length):
arr[i] = bytes(input[i],encoding="utf-8") if (type(input[0]) is str) else input[i]
return arr
else:
if input_type in ctypes_map:
return ctypes_map[input_type](bytes(input,encoding="utf-8") if type(input) is str else input)
else:
print("convert type failed...input is "+input)
return null

if __name__ == '__main__':
t = Test()
A1 = 123;
A2 = 0.789;
A3 = "C789";
A4 = [456,789];
A5 = [0.123,0.456];
A6 = ["A123", "B456"];
print(t.calculate(convert_type(A1), convert_type(A2), convert_type(A3),convert_type(A4),convert_type(A5),convert_type(A6)))

注意看这里的convert_type

Python C/C++联合编程实战-Python CTypes访问C/C++动态链接库

b站: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
2
3
4
5
6
7
#include "pch.h"
#include <stdio.h>

extern "C" __declspec(dllexport) void TestCtypes()
{
printf("In C testctypes\n");
}

然后选菜单的 “生成” —— “生成解决方案”,就会导出一个dll文件

注意,这里的dll是32位还是64位,要与后面python的32位或64位一致。比如我的python是64位(查看python是几位,通过在powershell里输入python得知)那么导出的dll就要是64位,否则会报 OSError: [WinError 193] %1 不是有效的 Win32 应用程序。 错误。

dll设置位64位:

image-20230213124938001

选择 x64.

或者在visual studio的 侧边栏 解决方案 项目名 右键——属性——(右上)配置管理器——活动解决方案平台,选择x64.

新建一个python文件:

1
2
3
from ctypes import *
lib = CDLL("testctypes")
lib.TestCtypes()

这里的 testctypes,是导出的 dll名称,这里不需要填 testctypes.dll后缀,只要写前面名称就好。

然后在windows里的powershell执行python文件。

注意,如果python文件的路径和dll路径不一致。有三种解决方法:

  1. 复制dll到python文件所在目录;
  2. python文件里写上相对路径,比如:lib = CDLL("x64/Debug/testctypes")
  3. 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
2
3
4
5
6
#include <stdio.h>
extern "C"
void TestCtypes()
{
printf("In C testctypes\n");
}

这里就不像windows里的写法那样,需要 __declspec(dllexport) , 这里不需要。

另一种写法:通过 来判断是linux系统或者windows系统。一次到位,就不用对不同系统写不同cpp代码了。

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>

#ifdef _WIN32 // win32 win64
#define XIB __declspec(dllexport)
#else // MAC linux
#define XIB
#endif

extern "C" XIB
void TestCtypes()
{
printf("In C testctypes\n");
}

执行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
2
3
4
import ctypes
lib=ctypes.CDLL('./a.so')

lib.TestCtypes()

执行 python a.py

关于路径问题,可以把每次编译完了之后的so文件,都复制到 /usr/lib路径下,就没有路径问题了(写一个shell每次自动复制过去,或者写一个makefile),或者export进lib path里 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/mnt/k/c_practice

写一个makefile:

1
2
3
a.so:a.cc
g++ -fPIC -shared -o $@ $<
cp $@ /usr/lib

这里 $@ 指的 a.so$< 指的 a.cc

然后命令行 make

mac版本

  • 与linux基本一致,但不支持代码GBK
  • 演示用g++
  • VS代码格式改为utf-8

改成utf-8的方法:

  1. 用notepad++打开cpp文件,菜单栏——编码——utf-8
  2. 打开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
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>

#ifdef _WIN32 // win32 win64
#define XIB __declspec(dllexport)
#else // MAC linux
#define XIB
#endif

extern "C" XIB
void numbers(int x, float y, bool b)
{
printf("In C testctypes %d %f %d\n",x, y, b);
}

make一下,生成 a.so。

python代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
from ctypes import *
lib = CDLL('./a.so')

try:
lib.numbers(101, c_float(99.1), True) # 99.1是python里的float型,要传给c里的函数,要转成c的格式(通过c_float()函数转)

p2 = c_int(102)
print(p2)
print(type(p2))
print(p2.value)
lib.numbers(p2,c_float(99.1),True)
except Exception as ex:
print("error", ex)

python a.py 输出:

1
2
3
4
5
n C testctypes 101 99.099998 1
c_int(102)
<class 'ctypes.c_int'>
102
In C testctypes 102 99.099998 1

ctypes类型对应

传参,要把python类型转成c语言支持的;返回值,要把c语言类型转成python支持的。

https://docs.python.org/zh-cn/3/library/ctypes.html

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
  1. 构造函数接受任何具有真值的对象。

所有这些类型都可以通过使用正确类型和值的可选初始值调用它们来创建:

from ctypes import *

1
2
3
4
5
6
7
>>> c_int()
c_long(0)
>>> c_wchar_p("Hello, World")
c_wchar_p(140018365411392)
>>> c_ushort(-3)
c_ushort(65533)
>>>

由于这些类型是可变的,它们的值也可以在以后更改:

1
2
3
4
5
6
7
8
9
>>> i = c_int(42)
>>> print(i)
c_long(42)
>>> print(i.value)
42
>>> i.value = -99
>>> print(i.value)
-99
>>>

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> 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) # the memory location has changed
c_wchar_p(139966783348904)
>>> print(c_s.value)
Hi, there
>>> print(s) # first object is unchanged
Hello, World
>>>

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> from ctypes import *
>>> 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'
>>> p = create_string_buffer(b"Hello") # create a buffer containing a NUL terminated string
>>> 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'
>>>

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++ 代码。
  1. 使用Cython或SWIG:这些工具可以让你编写接口代码,它们会把这些代码编译成可以在Python环境下运行的代码。
  2. 使用ctypes或cffi:这些库可以动态加载C++代码作为C函数,从而在Python代码中调用。

下面是一个使用ctypes的例子:

c++:(a.cc)

1
2
3
4
5
6
7
8
9
10
11
12
13
// a.cc
# include <iostream>

extern "C" {
int add(int a, int b) {
return a + b;
}
}

int main() {
return 0;
}

python:(a.py)

1
2
3
4
5
6
7
8
9
10
11
# a.py

import ctypes

# Load the shared library
lib = ctypes.CDLL('./libexample.so')

# Call the add function in the C++ code
result = lib.add(3, 5)
print(result) # 8

把c++编译成 libexample.so:g++ -shared -fPIC -o libexample.so a.cc

然后执行代码:python a.py 。就能得到结果了。

这是一个简单的例子,它仅仅是说明了如何调用C++代码。请注意,这只是一种方法,具体应用取决于您的需求和使用场景。