前面我们涉及了参数传递的几种类型,但是还没有获取函数的返回值。 我们通过ctypes来调用C语言的函数,取得它返回值。

在取得返回值时,我们要考虑返回值的空间要在哪边,假如是返回了一个指针,要考虑到指针的空间在哪里释放。 假如我们在C当中申请了一块空间,而我们在python当中没法释放它,就会出问题。

Ctypes获取返回值

  • 默认返回值都是int(就算函数返回设定的是void)
  • lib.CFunction.restype = c_char_p

我们在testctypesreturn.cpp中编写返回intchar*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

注意:返回指针需要定义静态变量,因为指针指向的是局部变量的地址,而局部变量在函数执行完后就被销毁,导致指针指向一个无效的地址。但是返回结构体对象,会先对该对象进行拷贝,所以可以直接返回。

© liangqi all right reserved,powered by Gitbook文件修订时间: 2019-05-29

results matching ""

    No results matching ""