前面我们涉及了参数传递的几种类型,但是还没有获取函数的返回值。
我们通过ctypes
来调用C语言的函数,取得它返回值。
在取得返回值时,我们要考虑返回值的空间要在哪边,假如是返回了一个指针,要考虑到指针的空间在哪里释放。 假如我们在C当中申请了一块空间,而我们在python当中没法释放它,就会出问题。
Ctypes获取返回值
- 默认返回值都是
int
(就算函数返回设定的是void) - lib.CFunction.restype = c_char_p
我们在testctypesreturn.cpp
中编写返回int
、char*
和wchar_t
的函数
testctypesreturn.cpp
#include <stdio.h>
#ifdef __cplusplus //如果是C++
#define XEXT extern "C"
#else
#define XEXT
#endif // __cplusplus
#ifdef _WIN32 //包含win32和win64
#define XLIB XEXT __declspec(dllexport)
#else
#define XLIB XEXT
#endif
XLIB int TestReturnInt()
{
return 101;
}
XLIB const char* TestReturnChar()
{
return "TestReturnChar String";
}
XLIB const wchar_t* TestReturnWChar()
{
return L"TestReturnWChar String";
}
testctypes.py
代码如下
testctypes.py
print("Test Ctypes Return")
from ctypes import *
try:
lib = CDLL("testctypesreturn")
# 设定返回值类型
# int 是默认值
print("TestReturnInt = ", lib.TestReturnInt())
lib.TestReturnChar.restype = c_char_p # 除int外,都要指定返回类型
re = lib.TestReturnChar()
print(type(re)) # 返回的直接是bytes
print("TestReturnChar = ", lib.TestReturnChar())
lib.TestReturnWChar.restype = c_wchar_p
re = lib.TestReturnWChar()
print(type(re)) # 返回的直接是string
print("TestReturnWChar = ", lib.TestReturnWChar())
except Exception as ex:
print("TestCtypestring error ",ex)
# 等待用户输入,防止程序直接退出
input()
运行结果如下
Test Ctypes Return
TestReturnInt = 101
<class 'bytes'>
TestReturnChar = b'TestReturnChar String'
<class 'str'>
TestReturnWChar = TestReturnWChar String
Ctypes传递和返回指针
ctypes传递指针的方式如下
lib.Function.argtypes = (POINTER(c_float),) lib.Function.restype = (POINTER(c_void_p),)
其中括号内的参数可以指定多个,它其实是一个元组(所以要加逗号)。
- POINTER()返回一个类型
- pointer()返回实例
- byref(x[,offset]) 获取指针
下面的例子,通过传递一个指针参数,在C语言当中来改变这个值,然后再在Python当中打印。 python没有提供接口来对返回的指针进行修改(但实际测试是可以更改的),我们可以返回结构体指针来传给下一个参数。
我们创建一个testctypespointer.cpp
文件(之后的代码省略相关跨平台的宏定义),
testctypespointer.cpp
XLIB int* TestPointer(float *f1)
{
static int re = 1001;
*f1 = 99.9f;
printf("In C++ TestPointer re = %d\n", re);
return &re;
}
修改之前的testctypes.py
代码,如下
testctypes.py
print("Test Ctypes Pointer")
from ctypes import *
try:
lib = CDLL("testctypespointer")
# 参数类型 要传递元组
lib.TestPointer.argtypes = (POINTER(c_float),)
lib.TestPointer.restype = POINTER(c_int)
f1 = c_float(88.8)
print("begin f1 = ",f1)
# byref返回指针
re = lib.TestPointer(byref(f1))
print("end f1 = ",f1)
print("return = ",re)
print("return type = ",type(re))
# re.contents 就是指针指向的内容
re.contents.value = 666
print("In Python TestPointer re =", re.contents.value)
# 在C语言中再调用一次
lib.TestPointer(byref(f1))
except Exception as ex:
print("TestCtypestring error ",ex)
# 等待用户输入,防止程序直接退出
input()
运行的结果如下
Test Ctypes Pointer
begin f1 = c_float(88.80000305175781)
In C++ TestPointer re = 1001
end f1 = c_float(99.9000015258789)
return = <__main__.LP_c_long object at 0x000001E2B4202BC8>
return type = <class '__main__.LP_c_long'>
In Python TestPointer re = 666
In C++ TestPointer re = 666
从结果可以看出,python中的f1在C中被修改,C中返回的`re`变量在python中也能被修改
CTypes传递数组
通过CType来给C语言传递一个数组(python的list
)。
CTypes要返回一个数组还存在一些问题,通过CTypes返回一个数组,那么这个数组的空间由谁来管理?
返回给Python,空间由谁来释放?
一般如果想用CTypes从C来返回一个数组给Python,可以通过指针的方式来传递。
因为传递数组涉及到效率问题,就算传给python,也可能涉及到复制的问题。
python内部的数组传递只是把引用计数加1,而如果C语言的数组过来,肯定是要复制的,所以开销非常大,一般也是通过指针的方式。
传递数组语法如下
(c_int*10)(1,2,3,4,5,6,7,8,9,10)
Ctypes数组是固定长度的,需要指定长度。 一般CTypes传递数组的方式如下
a = [1,2,3,4,5,6,7,8,9.10]
(c_int*10)(*a)
或则
TenArr = c_int*10 # TenArr是一个类型
a = TenArr(*a)
下面测试通过CTypes像C传递数组
编写C函数如下
XLIB void TestArray(int *arr, int size)
{
printf("=========In C++ ===============\n");
for (int i = 0; i < size; i++)
{
printf("%d ", arr[i]);
}
}
testctypes.py
print("Test Ctypes Array")
from ctypes import *
try:
lib = CDLL("testctypesarray")
# 传递一个参数类型 c_int*10
arr = [1,2,3,4,5,6,7,8,9,10]
TenArrType = c_int*len(arr)
# *把list中每个元素当作位置参数等同于 TenArrType(1,2,3,4,5,...)
carr = TenArrType(*arr)
lib.TestArray.argtypes = (TenArrType,)
lib.TestArray(carr,len(arr))
except Exception as ex:
print("testctyeps error",ex)
# 等待用户输入,程序不退出
input()
运行结果如下
Test Ctypes Array
=========In C++ ===============
1 2 3 4 5 6 7 8 9 10
CTypes传递和返回结构体
CTypes给C函数传递结构体对象指针和数组
结构体之间存在格式转化的问题。涉及到内存对齐和普通的字节大小问题。 对于复杂的应用建议做扩展包自己定义结构体。
在python当中定义一个类,统一继承一个Structure类型。它是专门为结构体做参数传递的。
它里面有一个固定的成员_fields_
,一系列结构体元素都在里面定义。_fields_
是一个list,list里面的元素是一个元组,用来定义结构体成员。
class Pos(Structure):
_fields_ = [("x",c_int),("y",c_int)]
我们通过代码来演示
编写C函数如下
struct Pos
{
int x;
int y;
};
XLIB void TestStruct(Pos pos)
{
printf("\nIn c++ TestStruct \n");
printf("pos x = %d,y = %d\n",pos.x, pos.y);
}
python代码如下
print("Test Ctypes struct")
from ctypes import *
class Pos(Structure):
_fields_ = [("x",c_int),("y",c_int)]
try:
lib = CDLL("testctypesstruct")
pos1 = Pos(11,22)
lib.TestStruct.argtypes = (Pos,)
lib.TestStruct(pos1)
except Exception as ex:
print("testctyeps error",ex)
# 等待用户输入,程序不退出
input()
运行结果如下,可以看到python的结构体成功传给了C
Test Ctypes struct
In c++ TestStruct
pos x = 11,y = 22
定义结构体的目的有时候是为了调用已有的函数,而已有函数的参数可能为结构体指针。
下面我们改进代码,来通过python像C来传递指针。改进的C函数如下
XLIB void TestStruct(Pos pos1, Pos* pos2)
{
printf("\nIn c++ TestStruct \n");
printf("pos1 x = %d,y = %d\n",pos1.x, pos1.y);
printf("pos2 x = %d,y = %d\n", pos2->x, pos2->y);
}
python代码更改如下
print("Test Ctypes struct")
from ctypes import *
class Pos(Structure):
_fields_ = [("x",c_int),("y",c_int)]
try:
lib = CDLL("testctypesstruct")
pos1 = Pos(11,22)
lib.TestStruct.argtypes = (Pos,POINTER(pos))
pos2 = Pos(33,44)
lib.TestStruct(pos1,byref(pos2))
except Exception as ex:
print("testctyeps error",ex)
# 等待用户输入,程序不退出
input()
运行结果如下
Test Ctypes struct
In c++ TestStruct
pos1 x = 11,y = 22
pos2 x = 33,y = 44
除了传递结构体对象和结构体指针之外,我们再来传递结构体数组
更改C函数
XLIB void TestStruct(Pos pos1, Pos* pos2,Pos *pos3,int size)
{
printf("\nIn c++ TestStruct \n");
printf("pos1 x = %d,y = %d\n",pos1.x, pos1.y);
printf("pos2 x = %d,y = %d\n", pos2->x, pos2->y);
printf("pos3 =");
for (int i = 0; i < size; i++)
{
printf("(%d,%d)", pos3[i].x, pos3[i].y);
}
}
更改python代码
print("Test Ctypes struct")
from ctypes import *
class Pos(Structure):
_fields_ = [("x",c_int),("y",c_int)]
try:
lib = CDLL("testctypesstruct")
pos1 = Pos(11,22)
lib.TestStruct.argtypes = (Pos,POINTER(Pos))
pos2 = Pos(33,44)
#传递结构体数组
pos3 = [Pos(1,1),Pos(2,2),Pos(3,3)]
PosType = Pos*len(pos3)
lib.TestStruct(pos1,byref(pos2),PosType(*pos3),len(pos3))
except Exception as ex:
print("testctyeps error",ex)
# 等待用户输入,程序不退出
input()
运行结果如下
Test Ctypes struct
In c++ TestStruct
pos1 x = 11,y = 22
pos2 x = 33,y = 44
pos3 =(1,1)(2,2)(3,3)
注意Python代码第行,我们没有显示指定参数类型,代码还是可以正常运行。 若要显示指定,如下:
lib.TestStruct.argtypes = (Pos,POINTER(Pos),PosType,c_int)
CTypes获取C函数返回结构体对象和指针
下面的代码展示通过CTypes从C获取结构体对象和指针
C函数如下
XLIB Pos GetPos()
{
Pos pos;
pos.x = 101;
pos.y = 102;
return pos;
}
XLIB Pos* TestStruct(Pos pos1, Pos* pos2,Pos *pos3,int size)
{
printf("\nIn c++ TestStruct \n");
printf("pos1 x = %d,y = %d\n",pos1.x, pos1.y);
printf("pos2 x = %d,y = %d\n", pos2->x, pos2->y);
printf("pos3 =");
for (int i = 0; i < size; i++)
{
printf("(%d,%d)", pos3[i].x, pos3[i].y);
}
printf("\n");
static Pos re;
re.x = 88;
re.y = 99;
//不返回局部变量
return &re;
}
python代码如下
print("Test Ctypes struct")
from ctypes import *
class Pos(Structure):
_fields_ = [("x",c_int),("y",c_int)]
try:
lib = CDLL("testctypesstruct")
pos1 = Pos(11,22)
pos2 = Pos(33,44)
pos3 = [Pos(1,1),Pos(2,2),Pos(3,3)]
PosType = Pos*len(pos3)
# 返回结构体指针
lib.TestStruct.restype = POINTER(Pos);
repos = lib.TestStruct(pos1,byref(pos2),PosType(*pos3),len(pos3))
print(repos)
print(type(repos))
print(repos.contents.x, repos.contents.y)
# 返回结构体对象
lib.GetPos.restype = Pos
repos2 = lib.GetPos()
print("GetPos = ",repos2.x, repos2.y)
except Exception as ex:
print("testctyeps error",ex)
# 等待用户输入,程序不退出
input()
运行结果如下
Test Ctypes struct
In c++ TestStruct
pos1 x = 11,y = 22
pos2 x = 33,y = 44
pos3 =(1,1)(2,2)(3,3)
<__main__.LP_Pos object at 0x0000023B24862E48>
<class '__main__.LP_Pos'>
88 99
GetPos = 101 102
注意:返回指针需要定义静态变量,因为指针指向的是局部变量的地址,而局部变量在函数执行完后就被销毁,导致指针指向一个无效的地址。但是返回结构体对象,会先对该对象进行拷贝,所以可以直接返回。