调用从 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 ¶m )
由于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'
我正在尝试使用 ctypes 调用 C++ 函数。函数声明看起来像
std::string some_function( const std::string ¶m )
由于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'