ctype 为什么要指定参数类型
我想用Python来调用C++库。
这是我的C++库代码:
#include <stdio.h>
extern "C" {
int test_i(int i)
{
return i+1;
}
}
这是我的Python代码:
from ctypes import *
libc = cdll.LoadLibrary("cpp/libtest.so")
libc.test_f.argtypes = [c_int] # still works when comment
print libc.test_i(1)
我的问题是:如果我没有指定参数类型,它仍然可以工作!我在调用C/C++函数时必须指定argtypes
吗?
1 个回答
是的,你应该这么做,因为它之所以能工作,是因为ctypes的猜测。把int test_i(int i)
换成int test_i(char i)
,你就会遇到栈损坏的问题——因为Python这边给函数传递了4或8个字节,而C这边只读取了1个字节。根据不同的平台,这种情况可能会一开始没被发现,或者慢慢消耗掉你的栈,或者立即崩溃,甚至什么都不会发生——总之,这种代码风格是有问题的。
ctypes
可以进行基本的类型转换,但在很多情况下并没有帮助,因为Python的类型系统比C的简单得多。例如,Python这边无法判断你传的是1字节、2字节、4字节还是8字节的整数,因为Python的整数没有大小。Python只有双精度浮点数,而C这边还有单精度和半精度,甚至可能还有平台特定的精度。在Python这边,字符串和字节是明确分开的,而在C这边,处理Unicode通常很麻烦。你不清楚是要用wchar_t[]
还是char[]
,字符串要怎么结束,是否真的需要这些,以及如何处理Python字符串的不可变性。如果没有正确的argtypes
,你就无法轻松传递结构体和数组。
而且,别忘了指定restype
:
libc.test_f.restype = c_int
默认情况下,ctypes
假设所有函数都返回int
,无论你的平台是什么。根据调用约定,CPU寄存器可能会用来返回结果——在这种情况下,你不会受到影响,因为寄存器是否被读取没有区别。在另一种情况下,结果是通过栈传递的,这样你就麻烦了,因为栈的消耗不足(或者过多)会导致栈损坏,函数的RET
会弹出错误的地址,最好的情况下会引发SIGSEGV。在最坏的情况下,黑客可能会利用你的应用程序进行恶意操作。
总的来说,argtypes
和restype
是你的好帮手,因为它们可以保护你的代码不以不可预测的方式崩溃,它们是必须的,因为C和Python之间的差异实在太大了。如果没有这些差异,没人会强迫你使用它们,因为这就像是“禅与电池”的道理。