使用 ctypes 在 Python 中解码 C const char*

Posted

技术标签:

【中文标题】使用 ctypes 在 Python 中解码 C const char*【英文标题】:Decode C const char* in Python with ctypes 【发布时间】:2017-08-12 19:34:39 【问题描述】:

我在 Python 3 中使用 ctypes(导入为 c)来执行 C++ 共享库。使用以下方法将库加载到 python 中:

smpLib = c.cdll.LoadLibrary(os.getcwd()+os.sep+'libsmpDyn.so')

其中一个函数具有extern 'C' 声明const char* runSmpModel(...)。 python函数原型编码并运行为:

proto_SMP = c.CFUNCTYPE(c.c_char_p,...)
runSmpModel = proto_SMP(('runSmpModel',smpLib))
res = runSmpModel(...)

这一切都很好,但我无法解码 res 变量并获得由 C runSmpModel 函数传递的字符串。 res 的值显示为b'\xd0'(我使用的是ipython3)。我在网上找到的最佳解决方案 - res.decode('utf-8') 给了我错误:

UnicodeDecodeError: 'utf-8' codec can't decode byte 0xd0 in position 0: unexpected end of data

runSmpModel函数的const char*返回值来自于

std::string scenID = SMPLib::SMPModel::runModel(...);
return scenID.c_str();

在runModel内部,最终定义如下,其中scenName是输入字符串:

auto utcBuffId = newChars(500);
sprintf(utcBuffId, "%s_%u", scenName.c_str(), microSeconds); // catenate scenario name & time
uint64_t scenIdhash = (std::hash < std::string>() (utcBuffId)); // hash it

auto hshCode = newChars(100);
sprintf(hshCode, "%032llX", scenIdhash);
scenId = hshCode;

这个特定的res 的值应该是0000000000000000BBB00C6CA8B8872E。如何解码这个字符串?

经过大量进一步的测试,我发现问题在于从 C 函数传递的字符串的长度。如果字符串长度不超过 15 个字符,则没有问题,但如果它是 16 个或更长 - 没有骰子。对于一个最小工作示例,C 代码是:

extern "C" 
  const char* testMeSO()
  
    string scenarioID = "abcdefghijklmnop";
    return scenarioID.c_str();
  

而python代码是(smpLib的定义如上图):

proto_TST = c.CFUNCTYPE(c.c_char_p)
testMeSO = proto_TST(('testMeSO',smpLib))
res = testMeSO()
print("Scenario ID: %s"%res.decode('utf-8'))

这会导致解码错误,除非从 C 函数中的 scenarioID 变量中删除任何字符。所以问题似乎是“Python如何使用ctypes读取超过15个字符的Cchar*

【问题讨论】:

一个字节不能突然变成 16。你遇到了不同的问题。 该调用返回了一个指向以b'\xd0\x00' 开头的缓冲区的指针。如果你真的需要帮助,你将不得不为函数提供文档——它做了什么,参数是什么,当它失败时它的行为,等等。 需要所有这些信息听起来并不合理——只需要有关此返回值的信息。我已经编辑了问题,添加了这些信息。 你说得对,我们不需要所有这些信息。我们需要的是minimal reproducible example。编写一个等效的简化函数,仅返回该格式的字符串(几行 C),将其包装在 Python ctypes 中并重现您的问题。发布 C 和 Python 代码。 【参考方案1】:

经过几天的调试和测试,我终于可以使用@Petesh on this SO post 发布的第二个解决方案。我不明白为什么 ctypes 显然将从 C 传递的 char * 值限制为 15 个字符(+终止 = 256 位?)。

基本上,解决方案是向 C 函数传递一个额外的 char * buff 缓冲区,该缓冲区已经使用 ctypes.create_string_buffer(32*16) 创建,以及一个值为 32*16 的 unsigned int buffsize。然后,在 C 函数中执行scenarioID.copy(buff,buffsize)。对python原型函数进行了明显的修改。

【讨论】:

以上是关于使用 ctypes 在 Python 中解码 C const char*的主要内容,如果未能解决你的问题,请参考以下文章

使用ctype在python中调用c

Python ctypes 模块

Python 外部函数调用库ctypes简介

在 Python/ctypes 中的结构内取消引用 C 函数指针

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

python3使用ctypes在windows中访问C和C++动态链接库函数示例