如何在Python3中通过ctypes向C++ Qt5函数传递参数?
问题:Qt5 C++ DLL与Python ctypes交互时参数传递错误导致崩溃
平台信息
$ for ic in "$(cmd //c ver)" "uname -s" "python3 --version" "g++ --version"; do echo "$ic:" $(echo "$(${ic})" | head -1); done 2>/dev/null Microsoft Windows [版本 10.0.19045.4355]: uname -s: MINGW64_NT-10.0-19045 python3 --version: Python 3.11.9 g++ --version: g++.exe (Rev6, Built by MSYS2 project) 13.2.0 $ python3 -c 'from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR; print("Qt: v", QT_VERSION_STR, "\tPyQt: v", PYQT_VERSION_STR)' Qt: v 5.15.12 PyQt: v 5.15.10
需求是构建Qt5的C共享库(.dll),在Python中加载调用,最终结合PyQt5使用。尝试用ctypes实现时,C代码可正常编译为.exe和.dll,但Python调用时崩溃,参数传递完全错误:
- 传入的Python字符串无法正确映射为C++的
QString&参数,出现空指针 - Python的
False被解析为C++中的18,参数栈错位
原代码与问题重现
C++代码(QtTestLib.cpp)
// compile with: // for .exe: // g++ -std=gnu++11 -g -Wall -Wextra -O -pedantic QtTestLib.cpp -o QtTestLib.exe -I/mingw64/include/QtCore /mingw64/bin/Qt5Core.dll // for lib: // g++ -std=gnu++11 -g -Wall -Wextra -O -pedantic -shared QtTestLib.cpp -o QtTestLib.dll -I/mingw64/include/QtCore /mingw64/bin/Qt5Core.dll #ifdef _WIN32 # define API extern "C" __declspec(dllexport) #else # define API extern "C" #endif #ifndef UNICODE #define UNICODE #define UNICODE_WAS_UNDEFINED #endif #include <QStringList> #include <iostream> API QString myStringTesterFunc(const QString &inString, bool specProcess) { QString ret_str = "Result: "; ret_str.append(QString("%1").arg(inString)); ret_str.append(QString("%1").arg(" - ")); if (specProcess) { ret_str.append(QString("%1").arg("Special")); } else { ret_str.append(QString("%1").arg("Regular")); } return ret_str; } // tester: int main(__attribute__((unused)) int argc, __attribute__((unused)) char *argv[]) { QString test_str = "->Main tester<-"; QString out_str = myStringTesterFunc(test_str, true); std::wcout << "Got: " << reinterpret_cast<const wchar_t *>(out_str.utf16()) << std::endl;; }
编译与测试结果
编译为exe可正常运行:
$ g++ -std=gnu++11 -g -Wall -Wextra -O -pedantic QtTestLib.cpp -o QtTestLib.exe -I/mingw64/include/QtCore /mingw64/bin/Qt5Core.dll $ ./QtTestLib.exe Got: Result: ->Main tester<- - Special
编译.dll无报错:
$ g++ -std=gnu++11 -g -Wall -Wextra -O -pedantic -shared QtTestLib.cpp -o QtTestLib.dll -I/mingw64/include/QtCore /mingw64/bin/Qt5Core.dll
Python测试脚本(QtTestLib.py)
from ctypes import * mylib = cdll.LoadLibrary("./QtTestLib.dll") datastr = "Hello World from .py tester" ret = mylib.myStringTesterFunc(datastr, False) print(f"{ret=}")
调试信息
gdb调试显示参数传递完全错误:
$ gdb --args python3 QtTestLib.py ... Thread 1 received signal SIGSEGV, Segmentation fault. 0x00007fff59fd6225 in ?? () from C:\msys64\mingw64\bin\Qt5Core.dll (gdb) bt #0 0x00007fff59fd6225 in ?? () from C:\msys64\mingw64\bin\Qt5Core.dll #1 0x00007fff803413df in myStringTesterFunc (inString=..., specProcess=18) at C:/msys64/mingw64/include/QtCore/qchar.h:55 ... (gdb) frame 1 #1 0x00007fff803413df in myStringTesterFunc (inString=..., specProcess=18) at C:/msys64/mingw64/include/QtCore/qchar.h:55 55 Q_DECL_CONSTEXPR inline ushort unicode() const noexcept { return ushort(uchar(ch)); } (gdb) p inString $1 = (const QString &) <error reading variable: Cannot access memory at address 0x0> (gdb) p specProcess $2 = 18
解决方案
方案1:修改C++接口为C兼容类型(适配ctypes)
ctypes只能处理C标准类型,无法直接识别C的QString类。需要将导出接口改为C兼容的宽字符串类型,在C内部完成QString的转换:
修改后的C++代码
// compile with: // for .exe: // g++ -std=gnu++11 -g -Wall -Wextra -O -pedantic QtTestLib.cpp -o QtTestLib.exe -I/mingw64/include/QtCore /mingw64/bin/Qt5Core.dll // for lib: // g++ -std=gnu++11 -g -Wall -Wextra -O -pedantic -shared QtTestLib.cpp -o QtTestLib.dll -I/mingw64/include/QtCore /mingw64/bin/Qt5Core.dll #ifdef _WIN32 # define API extern "C" __declspec(dllexport) #else # define API extern "C" #endif #ifndef UNICODE #define UNICODE #define UNICODE_WAS_UNDEFINED #endif #include <QString> #include <iostream> #include <cstdlib> #include <cstring> // 内部逻辑函数(保持原Qt处理逻辑) QString myStringTesterFuncImpl(const QString &inString, bool specProcess) { QString ret_str = "Result: "; ret_str.append(QString("%1").arg(inString)); ret_str.append(" - "); ret_str.append(specProcess ? "Special" : "Regular"); return ret_str; } // 导出的C兼容接口 API wchar_t* myStringTesterFunc(const wchar_t* inStr, bool specProcess) { if (!inStr) return nullptr; QString inString(inStr); QString result = myStringTesterFuncImpl(inString, specProcess); // 分配内存存储结果,由Python端释放 const wchar_t* utf16 = result.utf16(); size_t len = wcslen(utf16) + 1; wchar_t* ptr = (wchar_t*)malloc(len * sizeof(wchar_t)); if (ptr) { wcscpy(ptr, utf16); } return ptr; } // 测试main函数 int main(__attribute__((unused)) int argc, __attribute__((unused)) char *argv[]) { QString test_str = "->Main tester<-"; QString out_str = myStringTesterFuncImpl(test_str, true); std::wcout << "Got: " << reinterpret_cast<const wchar_t *>(out_str.utf16()) << std::endl;; }
修改后的Python脚本
from ctypes import * # 加载DLL mylib = cdll.LoadLibrary("./QtTestLib.dll") # 加载系统库用于内存释放 libc = cdll.msvcrt # 显式指定函数参数和返回类型 mylib.myStringTesterFunc.argtypes = [c_wchar_p, c_bool] mylib.myStringTesterFunc.restype = POINTER(c_wchar) datastr = "Hello World from .py tester" ret_ptr = mylib.myStringTesterFunc(datastr, False) # 转换为Python字符串 ret = wstring_at(ret_ptr) print(f"{ret=}") # 释放C++分配的内存 libc.free(ret_ptr)
方案2:使用PyBind11(更简便的C++/Python绑定方案)
如果不想手动处理类型转换,PyBind11可以直接绑定Qt类型,自动完成QString与Python字符串的转换:
步骤1:编写绑定代码(QtTestLib_bind.cpp)
#include <pybind11/pybind11.h> #include <pybind11/qt.h> #include <QString> namespace py = pybind11; QString myStringTesterFunc(const QString &inString, bool specProcess) { QString ret_str = "Result: "; ret_str.append(QString("%1").arg(inString)); ret_str.append(" - "); ret_str.append(specProcess ? "Special" : "Regular"); return ret_str; } PYBIND11_MODULE(QtTestLib, m) { m.def("myStringTesterFunc", &myStringTesterFunc, py::arg("inString"), py::arg("specProcess"), "测试Qt字符串处理函数"); }
步骤2:编译为Python扩展模块
g++ -std=c++11 -shared -fPIC `python3 -m pybind11 --includes` \ -I/mingw64/include/QtCore /mingw64/bin/Qt5Core.dll \ QtTestLib_bind.cpp -o QtTestLib.pyd
步骤3:Python中直接使用
import QtTestLib datastr = "Hello World from .py tester" ret = QtTestLib.myStringTesterFunc(datastr, False) print(f"{ret=}")
错误原因说明
- QString类型不兼容:
ctypes无法识别C++的QString类,直接传递Python字符串会被当做char*,但函数期望的是QString对象的引用,导致内存地址错误。 - bool参数栈错位:
ctypes默认将Python bool转换为C的int(4字节),而C++的bool是1字节,调用约定不匹配导致参数栈错位,第二个参数值被错误解析。
内容的提问来源于stack exchange,提问作者sdbbs
相关产品推荐
相关产品推荐

