ctypes类型对应
前面我们实现了一个简单的实例,现在我们将之前的示例复杂化一点。
实际情况中我们的函数需要传递参数,接收返回值,这就需要我们能够将python的类型与C语言类型进行相互转换。下表是python官网中提供的类型转换函数。
在python当中不能单独地传一个字符(可以通过对string的下标访问渠道),C中和整形相关的类型全部转成int
。
对于c_char
和c_wchar
分别对应python的1-character bytes object
和1-character string
,前者存放一个ASCII
字符,后者存放一个utf-32
。
除了上述基础类型外,我们还可以给ctypes传回调函数、数组、结构体等。
传递数字参数
我们通过c_int
和c_float
传递float和int,c_int()返回一个类对象,该类的value
成员为所传的值
v = c_int(101)
print(v.value)
下面的代码中我们在TestCtypesNumber
函数中打印该函数的参数,并在python代码中调用
!FILENAME testctypesnumber.cpp
#include <stdio.h>
#ifdef _WIN32 //包含win32和win64
#define XLIB __declspec(dllexport)
#else
#define XLIB
#endif
extern "C" XLIB void TestCtypesNumber(int x,float y,bool isNum)
{
printf("In C TestCtypesNumber makefile %d %f %d\n",x,y,isNum);
if (isNum){
printf("true");
}
else {
printf("false");
}
}
testctypesnumber.py
print("Test Ctypes Number")
from ctypes import *
lib = CDLL("testctypesnumber")
lib.TestCtypesNumber(101, 99.1, True)
# 等待用户输入,防止程序直接退出
input()
我们将python的代码和dll放在一个路径下面,在VS中将项目属性——>链接器——>常规——>输出文件设置成和testctypesnumber.cpp
同一目录。
为了能在vs中调试dll文件,我们进入到项目属性——>调试——>命令,选择浏览,找到安装的python.exe
所在路径,命令参数填写testctypesnumber.py
现在我们在VS中运行代码,发现程序直接退出,说明python代码中发生了异常。为了能显示错误,我们要捕获异常。更改python代码如下
testctypesnumber.py
print("Test Ctypes Number")
from ctypes import *
lib = CDLL("testctypesnumber")
try:
lib.TestCtypesNumber(101, 99.1, True)
except Exception as ex:
print("TestCtypeNumber error ",ex)
# 等待用户输入,防止程序直接退出
input()
再次运行代码,可以看到打印的错误如下
TestCtypeNumber error argument 2: <class 'TypeError'>: Don't know how to convert parameter 2
可以看出,int
类型可以直接转换(不用调用c_int()
),而float
类型是不能直接转换的。我们更改一下代码
testctypesnumber.py
# ...
lib.TestCtypesNumber(101, c_float(99.1), True)
# ...
再次运行,程序可以正常输出结果
In C TestCtypesNumber makefile 101 99.099998 1
true
字符串的传递
有两种字符串string
和byte
,分别用c_wchar_p()
和c_char_p()
进行传递。
string
和byte
都是只读类型,在内部不可修改。如果想传一个字符串由C语言进行修改,可以用create_string_buffer
创建一个可修改的字符串的缓冲,传给C语言,再由C语言进行写入。
为了让我们的代码在C和C++中均能编译通过,我们定义宏XEXT
。定义一个函数TestStringA
来测试byte
的传递
testctypesstring.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
// c_char_p 普通字符
XLIB void TestStringA(const char* str, int size)
{
printf("TestStringA : %s (%d)\n", str, size);
}
更改我们的python代码如下
testctypes.py
print("Test Ctypes String")
from ctypes import *
try:
lib = CDLL("testctypesstring")
str1 = "test string in python"
lib.TestStringA(str1, len(str1))
except Exception as ex:
print("TestCtypestring error ",ex)
# 等待用户输入,防止程序直接退出
input()
在VS中调试执行testctypesstring.cpp
代码,发现程序报错并且立刻退出,无法定位程序错误在哪一行。
我们在控制台中执行testctypes.py
代码,发现lib.TestStringA(str1, len(str1))
这一行代码报错
IndentationError: unindent does not match any outer indentation level
提示是缩进的错误!然而代码明明是对齐的。这里有一个技巧,在Notepad++中可以显示出所有字符: 视图—>显示符号—>显示空格与制表符。发现是空格和Tab键混用了。
将缩进全部改用Tab后,再次在命令行中运行程序,发现传给TestStringA
函数的是string
类型而非byte
将testctypes.py
第5行代码改为str1 = b"test string in python"
进行转换,程序正常执行。
上面测试的是普通字符,为了测试宽字符,我们在testctypesstring.cpp
中再添加函数TestStringW
// c_wchar_p 宽字符
XLIB void TestStringW(const wchar_t* str, int size)
{
//宽字符前要加L进行转换
wprintf(L"TestStringW : %s (%d)\n", str, size);
}
在testctypes.py
中增加下面代码
!FILENAME testctypes.py
# try语句块里
wstr = "wide string in python"
lib.TestStringW(wstr,len(wstr))
运行程序,输出如下
Test Ctypes String
TestStringA : test string in python (21)
TestStringW : wide string in python (21)
普通字符与宽字符均不需要显示转换
Python传给C的字符串在C中是可以修改的,但是建议不要这么做,这会造成语法的不一致(应为string类型在python中不可更改)。
如果我们需要修改,可以通过create_string_buffer()
函数。
buf = create_string_buffer(100)
我们在testctypesstring.cpp
中再添加函数TestStringBuffer
// 传递可修改的buf
XLIB void TestStringBuffer(char* str, int size)
{
str[0] = '@';
str[1] = '#';
str[2] = '\0';//字符串结束标识
printf("TestStringBuffer : %s (%d)\n", str, size);
}
在testctypes.py
中增添如下代码
!FILENAME testctypes.py
# try语句块里
buf = create_string_buffer(10)
lib.TestStringBuffer(buf)
print("in python buf = ",buf.raw, buf.value, len(buf), sep = '\n')
运行代码结果如下
TestStringBuffer : @# (1447823194)
in python buf =
b'@#\x00\x00\x00\x00\x00\x00\x00\x00'
b'@#'
10
如有有些字符串需要在C中算完了再传给python,可以使用`create_string_buffer`