调用从 Python 接受和 returns std::string 的 C++ 函数

Calling C++ function which accepts and returns std::string from Python

我正在尝试使用 ctypes 调用 C++ 函数。函数声明看起来像

std::string some_function( const std::string &param )

由于Python不能直接与C++交互,我为此函数创建了一个C-wrapper如下

const char *my_function( const char *param )
{
  std::string result = some_function( std::string( param ) );
  char *ret = new char[result.length() + 1];
  strcpy(ret, result.c_str());
  return ret;
}

和Python包装器

def my_function(param):
    func = lib.my_function
    func.argtypes = [ctypes.c_char_p]
    func.restype = ctypes.c_char_p
    result = func(ctypes.c_char_p(param))
    return result

但是当我尝试调用 Python 包装器时,将一些字节作为参数传递,例如

my_function(b'GP\x00\x01\xe6\x10\x00\x00\x01\x01\x00\x00\x00\x1ex\xcb\xa16l\xf1\xbfp\xe6\xaa\xc89\x81\xdd?')

失败并出现以下错误

terminate called after throwing an instance of 'std::length_error'
  what():  basic_string::_M_create

我的 C 和 C++ 不是很好。你能帮我弄清楚如何创建正确的包装器吗?谢谢。

由于您的数据包含嵌入的空值,c_char_p 将不起作用,因为它假定返回的 char* 是 null-terminated 并将数据转换为找到的第一个空值 bytes 对象。使用的 std::string 在仅传递 char* 时也会做出该假设,因此它也需要数据长度。

管理一个空内容的数据缓冲区,必须传入输入数据的大小,返回输出数据的大小。您还必须设法释放在 C++ 代码中分配的数据。

下面的代码演示了一切:

test.cpp - 使用 Microsoft Visual Studio.

使用 cl /EHsc /W4 /LD test.cpp 编译
#include <string>
#include <string.h>

// Windows DLLs need functions to be explicitly exported
#ifdef _WIN32
#   define API __declspec(dllexport)
#else
#   define API
#endif

std::string some_function(const std::string& param) {
    return param + std::string("some\x00thing", 10);
}

// pLength is used as an input/output parameter.
// Pass in input length and get output length back.
extern "C" API
const char* my_function(const char* param, size_t* pLength) {
    auto result = some_function(std::string(param, *pLength));
    auto reslen = result.length();
    auto res = new char[reslen];
    // Use memcpy to handle embedded nulls
    memcpy_s(res, reslen, result.data(), reslen);
    *pLength = reslen;
    return res;
}

extern "C" API
void my_free(const char* param) {
    delete [] param;
}

test.py

import ctypes as ct

dll = ct.CDLL('./test')

# Note: A restype of ct.c_char_p is converted to a bytes object
#       and access to the returned C pointer is lost.  Use
#       POINTER(c_char) instead to retain access and allow it
#       to be freed later.
dll.my_function.argtypes = ct.c_char_p, ct.POINTER(ct.c_size_t)
dll.my_function.restype = ct.POINTER(ct.c_char)
dll.my_free.argtypes = ct.c_char_p,
dll.my_free.restype = None

def my_function(param):
    # wrap size in a ctypes object so it can be passed by reference
    size = ct.c_size_t(len(param))
    # save the returned pointer
    p = dll.my_function(param, ct.byref(size))
    # slice pointer to convert to an explicitly-sized bytes object
    result = p[:size.value]
    # now the pointer can be freed...
    dll.my_free(p)
    # and the bytes object returned
    return result

result = my_function(b'data\x00more')
print(result)

输出:

b'data\x00moresome\x00thing'