ctypes类型对应

前面我们实现了一个简单的实例,现在我们将之前的示例复杂化一点。

实际情况中我们的函数需要传递参数,接收返回值,这就需要我们能够将python的类型与C语言类型进行相互转换。下表是python官网中提供的类型转换函数。

设置

在python当中不能单独地传一个字符(可以通过对string的下标访问渠道),C中和整形相关的类型全部转成int。 对于c_charc_wchar分别对应python的1-character bytes object1-character string,前者存放一个ASCII字符,后者存放一个utf-32

除了上述基础类型外,我们还可以给ctypes传回调函数数组结构体等。

传递数字参数

我们通过c_intc_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

字符串的传递

有两种字符串stringbyte,分别用c_wchar_p()c_char_p()进行传递。

stringbyte都是只读类型,在内部不可修改。如果想传一个字符串由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类型而非bytetestctypes.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`
© liangqi all right reserved,powered by Gitbook文件修订时间: 2019-06-04

results matching ""

    No results matching ""