Python 3 ctypes动态链接库DLL字符串只读取第一个字母原因分析
Why only the first letter of the string is printed when using DLL with ctypes in Python 3?
问题描述
C++代码:
#include <stdio.h>
extern "C" {
__declspec(dllexport) void printString(char*);
}
void printString(char* str)
{
printf(str);
}
Python代码:
import ctypes
DLL_PATH = 'C:./Test2.dll'
lib = ctypes.cdll.LoadLibrary(DLL_PATH)
lib.printString('abc')
上述C++代码中printString()
将接收到的字符串直接打印出来;Python通过ctypes加载此C++代码的动态链接库调用printString()
。
Python 2下运行输出
abc
而Python 3下运行则仅输出第一个字母
a
原因分析
想必很多人都知道这是Python 2和3内部不同的字符串编码方式造成的,这里深入分析一下。
当执行lib.printString('abc')
时,函数调用传递的参数实际是一个内存地址(或者说是指针),而这个内存地址就是'abc'
在内存中的起始位置。
printf(str)
则是接收到一个内存地址,然后以此内存地址开始,逐byte地以char格式输出内存中的值,直到碰到值为char格式的\0
,即内存值为0x00
为止。
那么'abc'
在Python 2和Python 3下内存值为多少呢?这里'abc'
一定是以某种方式被编码为数值写入内存的,那编码方式是什么呢?在Python官方文档中就有描述。
Python 2在howto-unicode中介绍道
For example, Python’s default encoding is the ‘ascii’ encoding.
即Python 2下'abc'
被编码为97 98 99 0
。
Python 2在howto-unicode中介绍道
Since Python 3.0, the language features a str type that contain Unicode characters, meaning any string created using "unicode rocks!", 'unicode rocks!', or the triple-quoted string syntax is stored as Unicode.
就是Unicode了,这样'abc'
就被编码为97 0 98 0 99 0 0
。(Unicode编码每个字符占2个byte)
如果你还对此有疑惑的话,不妨跑个示例代码看看。
示例
C++代码:
#include <stdio.h>
extern "C"
{
__declspec(dllexport) void readString(char*, int);
}
void readString(char* str, int length)
{
for (int i = 0; i <= length; i++)
{
printf("%d ", *(str + i));
}
}
Python代码:
import ctypes
DLL_PATH = './Test.dll'
lib = ctypes.cdll.LoadLibrary(DLL_PATH)
lib.readString('abc', 4)
# lib.readString('abc', 7)
上述C++代码中readString()
接收字符串起始地址,以及需要打印的内存byte数据个数,按顺序输出内存值;Python中readString()
则传递字符串'abc'
的起始地址,分别指定长度为4(Python 2)和长度为7(Python 3)。
Python 2下输出
97 98 99 0
即'abc'
的ASCII编码。
Python 3下输出
97 0 98 0 99 0 0
即'abc'
的Unicode编码。
原因:Unicode中'a'
由两个字节97 0
表示,C++读取字符串时以遇到的第一个0
作为字符串结尾,导致当'abc'
传入时,C++认为字符串为97 0
(即'a'
),而不是97 0 98 0 99 0 0
。
解决方法
知道原理了就很简单了,就是让'abc'
的内存值为ASCII编码值,而不是Unicode编码值;所以只需要在Python中调用C++函数时将字符串手动编码为ASCII。
修改后的Python代码为:
import ctypes
DLL_PATH = 'C:./Test2.dll'
lib = ctypes.cdll.LoadLibrary(DLL_PATH)
lib.printString('abc'.encode('ascii'))
这样Python 3下也能得到正确结果
abc
另一种解决方法思路:上述解决方法是在Python端消灭Unicode,当然还有一种方法就是在C++端消灭ASCII,统一使用Unicode,限于时间关系,我这里就不作出尝试了。
感谢!