C++14编写Python扩展:如何将wstring传递给Python回调函数?
好的,咱们来一步步拆解这个问题——你的思路方向是对的,但有几个关键细节需要调整和明确,确保跨语言的字符串传递不出问题:
一、回调函数的合理定义
首先,Python端的回调函数应该接收**str类型**(也就是Python 3的原生Unicode字符串),而不是原始字节串。这样用户在Python里处理非英文字符时不需要额外解码,体验更顺畅。比如Python端的回调可以写成:
def my_callback(text: str): print(f"Received text: {text}")
在C++层,你需要把转换后的UTF-8字符串包装成Python的str对象再传递给回调,而不是直接传字节串。
二、wstring转UTF-8的关键细节
C++的wstring底层是wchar_t,而wchar_t的宽度是平台相关的:
- Windows下是16位(对应UTF-16编码)
- Unix-like系统(Linux/macOS)下是32位(对应UTF-32编码)
所以转换逻辑需要区分平台,才能正确把wstring转成标准的UTF-8字符串。这里用C++14支持的std::wstring_convert来实现:
#include <codecvt> #include <string> #include <stdexcept> #ifdef _WIN32 // Windows下:UTF-16(wstring)转UTF-8 std::string wstring_to_utf8(const std::wstring& wstr) { std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter; try { return converter.to_bytes(wstr); } catch (const std::range_error& e) { throw std::runtime_error("Invalid UTF-16 sequence in wstring"); } } #else // Unix-like下:UTF-32(wstring)转UTF-8 std::string wstring_to_utf8(const std::wstring& wstr) { std::wstring_convert<std::codecvt_utf8<wchar_t>> converter; try { return converter.to_bytes(wstr); } catch (const std::range_error& e) { throw std::runtime_error("Invalid UTF-32 sequence in wstring"); } } #endif
三、C++层调用回调的完整流程
- 保存用户传入的回调:模块初始化时要妥善保存Python回调对象,注意管理引用计数:
#include <Python.h> static PyObject* g_user_callback = nullptr; // 模块提供的设置回调的API static PyObject* set_callback(PyObject* self, PyObject* args) { PyObject* callback; if (!PyArg_ParseTuple(args, "O", &callback)) { return nullptr; } if (!PyCallable_Check(callback)) { PyErr_SetString(PyExc_TypeError, "Argument must be a callable function"); return nullptr; } // 释放旧回调(如果存在),增加新回调的引用计数 Py_XDECREF(g_user_callback); Py_INCREF(callback); g_user_callback = callback; Py_RETURN_NONE; }
- 读取文件并调用回调:读取得到
wstring后,转成UTF-8再包装成Pythonstr,然后调用回调:
// 读取文本文件为wstring的示例实现(你可以替换成自己的读取逻辑) std::wstring read_file_as_wstring(const char* filename) { std::wifstream file(filename); if (!file.is_open()) { throw std::runtime_error("Failed to open file"); } return std::wstring(std::istreambuf_iterator<wchar_t>(file), {}); } // 模块提供的处理文件并触发回调的API static PyObject* process_text_file(PyObject* self, PyObject* args) { const char* filename; if (!PyArg_ParseTuple(args, "s", &filename)) { return nullptr; } try { std::wstring file_content = read_file_as_wstring(filename); std::string utf8_content = wstring_to_utf8(file_content); // 检查回调是否已设置 if (!g_user_callback) { PyErr_SetString(PyExc_RuntimeError, "Callback not set. Call set_callback first."); return nullptr; } // 将UTF-8字符串转为Python str对象 PyObject* py_text = PyUnicode_FromString(utf8_content.c_str()); if (!py_text) { return nullptr; } // 调用回调函数 PyObject* result = PyObject_CallObject(g_user_callback, PyTuple_Pack(1, py_text)); Py_DECREF(py_text); if (!result) { // 回调执行出错,Python已自动设置异常 return nullptr; } Py_DECREF(result); } catch (const std::runtime_error& e) { PyErr_SetString(PyExc_RuntimeError, e.what()); return nullptr; } Py_RETURN_NONE; }
- 模块注册:最后把方法注册到扩展模块中:
static PyMethodDef ExtensionMethods[] = { {"set_callback", set_callback, METH_VARARGS, "Set the callback function to receive text"}, {"process_file", process_text_file, METH_VARARGS, "Read a text file and pass content to the callback"}, {nullptr, nullptr, 0, nullptr} // 哨兵元素 }; static struct PyModuleDef my_extension_module = { PyModuleDef_HEAD_INIT, "my_extension", // 模块名称 "C++ extension for processing text files with Python callback", // 模块文档 -1, ExtensionMethods }; PyMODINIT_FUNC PyInit_my_extension(void) { return PyModule_Create(&my_extension_module); }
几个核心注意事项
- 引用计数必须严格管理:操作Python对象时,
Py_INCREF/Py_DECREF/Py_XDECREF一个都不能少,否则会导致内存泄漏或Python解释器崩溃。 - 异常处理要到位:不管是文件读取失败还是字符串转换出错,都要把C++异常转为Python异常,让Python端能捕获和处理。
- 平台兼容性:一定要区分
wchar_t的平台差异,否则在跨平台部署时会出现乱码。
内容的提问来源于stack exchange,提问作者NonStatic
相关产品推荐
相关产品推荐

