ctypes获取扩展模块中函数的返回值

Posted traditional

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ctypes获取扩展模块中函数的返回值相关的知识,希望对你有一定的参考价值。

ctypes获取返回值

我们前面已经看到了,通过ctypes像扩展模块中的函数传参时是没有问题的,但是我们如何拿到返回值呢?我们之前都是使用printf直接打印的,但是这样显然不行,我们肯定是要拿到返回值去做一些别的事情的。那么我们看看如何使用ctypes获取函数的返回值。

获取整型返回值

int test1(int a, int b)
{
    int c;
    c = a + b;
    return c;
}

void test2()
{

}

我们定义了两个函数,下面编译成dll文件,dll文件名叫做mmp.dll

from ctypes import *

lib = CDLL("./mmp.dll")
print(lib.test1(25, 33))  # 58
print(lib.test2())  # 125387149

我们看到对于test1的结果是正常的,但是对于test2来说即便返回的是void,在python中依旧会得到一个整型,但是这个结果肯定是不正确的。不过对于整型来说,是完全没有问题的。

当然我们后面还会遇到一个问题,这里提前说一下。扩展模块中的函数不要说返回void,即便返回一个char *,那么在python中得到的依旧是一个整型。这个是不同语言的类型不同造成的,正如我们传递参数一样,需要使用ctypes转化一下,那么在获取返回值的时候,也需要提前使用ctypes指定一下返回值到底是什么类型,只有这样才能拿到扩展模块中函数的正确的返回值。

获取字符数组返回值

#include <wchar.h>

char * test1()
{
    char *s = "hello satori";
    return s;
}

wchar_t * test2()
{   
    //遇到wchar_t的时候,一定要导入wchar.h头文件
    wchar_t *s = L"憨八嘎";
    return s;
}

我们定义了两个函数,一个返回char *,一个返回wchar_t *。

from ctypes import *

lib = CDLL("./mmp.dll")
# 我们看到神奇的事情发生了,我们在扩展模块中返回的是一个字符数组的首地址,我们希望拿到指向的字符串
# 然而python拿到的确是一个整型,而且一看感觉这像是一个地址。如果是地址的话那么从理论上讲是对的,返回地址、获取地址
print(lib.test1())  # 1801207808
# 但我们希望的是获取地址指向的字符数组,所以我们需要指定一下返回的类型
# 指定为c_char_p,告诉ctypes我们不要地址,而是要通过这个地址获取其指向的字符数组
lib.test1.restype = c_char_p
# 此时就没有问题了
print(lib.test1())  # b'hello satori'

# 同理对于unicode也是一样的,如果不指定类型,得到的依旧是一个整型
lib.test2.restype = c_wchar_p
print(lib.test2())  # 憨八嘎

因此我们就将python中的类型和C语言中的类型通过ctypes关联起来了,我们传参的时候需要转化,同理获取返回值的时候也要使用ctypes来声明一下类型。因为默认python调用扩展模块的函数返回的都是整型,至于返回的整型的值到底是什么?从哪里来的?我们不需要关心,你可以理解为地址、或者某块内存的脏数据,但是不管怎么样,结果肯定是不正确的(如果函数返回的就是整形除外)。因此我们需要提前声明一下返回值的类型。声明方式:

lib.CFunction.restype = ctypes类型

我们说lib就是ctypes调用dll或者so得到的扩展模块,而里面的函数就相当于是一个个的CFunction,然后设置内部的restype(返回值类型),就可以得到正确的返回值了。另外即便返回值设置的不对,比如:test1返回一个char *,但是我们将类型设置为c_float,调用的时候也不会报错而且得到的也是一个float,但是这个结果肯定是不对的。

from ctypes import *

lib = CDLL("./mmp.dll")
lib.test1.restype = c_char_p
print(lib.test1())  # b'hello satori'

# 设置为c_float
lib.test1.restype = c_float
# 获取了不知道从哪里来的脏数据
print(lib.test1())  # 2.5420596244190436e+20

# 另外ctypes调用还有一个特点
lib.test2.restype = c_wchar_p
print(lib.test2(123, c_float(1.35), c_wchar_p("呼呼呼")))  # 憨八嘎
# 我们看到test2是不需要参数的,如果我们传了那么就会忽略掉,依旧能得到正常的返回值
# 但是不要这么做,因为没准就出问题了,所以还是该传几个参数就传几个参数

获取浮点型返回值

下面我们来看看浮点类型的返回值怎么获取,当然方法和上面是一样的。

#include <math.h>

float test1(int a, int b)
{
    float c;
    c = sqrt(a * a + b * b);
    return c;
}
from ctypes import *

lib = CDLL("./mmp.dll")

# 得到的结果是一个整型,默认都是整型。
# 我们不知道这个整型是从哪里来的,就把它理解为地址吧,但是不管咋样,结果肯定是不对的
print(lib.test1(3, 4))  # 1084227584

# 我们需要指定返回值的类型,告诉ctypes返回的是一个float
lib.test1.restype = c_float
# 此时结果就是对的
print(lib.test1(3, 4))  # 5.0

# 如果指定为double呢?
lib.test1.restype = c_double
# 得到的结果也有问题,总之类型一定要匹配
print(lib.test1(3, 4))  # 5.356796015e-315

# 至于int就不用说了,因为默认就是int。所以和第一个结果是一样的
lib.test1.restype = c_int
print(lib.test1(3, 4))  # 1084227584

所以类型一定要匹配,该是什么类型就是什么类型。即便扩展模块中返回的是float,我们在python中通过ctypes也要指定为float,而不是指定为double,尽管都是浮点数并且double的精度还更高,但是结果依旧不是正确的。至于整型就不需要关心了,但即便如此,int、long也不要混用,而且也建议传参的时候进行转化。
技术图片

以上是关于ctypes获取扩展模块中函数的返回值的主要内容,如果未能解决你的问题,请参考以下文章

使用ctypes实现python类型和C语言类型之间的相互转化

ctypes给扩展模块中的函数传递回调函数

使用 ctypes 返回值

使用 -O 优化时 ctypes 返回错误值

python C语言扩展之简单扩展-使用ctypes访问C代码

Python Cookbook(第3版)中文版:15.1 使用ctypes访问C代码