首先是我的平台信息:
$ 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 [Version 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 作为共享对象 (.dll) 的 C++ 代码,然后我可以将其加载到 Python 3 中,并最终可能与 PyQt5 一起使用。
所以,一开始,我以为我可以通过
ctypes
来完成这个工作,并想出了下面的示例代码。我可以构建 C++ 代码,通过 ctypes 从 Python 调用它,但 Python 脚本崩溃,因为我似乎无法将 QString 从 Python 传递到 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
// For Windows compatibility // https://stackoverflow.com/q/61294630
#ifdef _WIN32
# define API extern "C" __declspec(dllexport)
#else
# define API extern "C"
#endif
#ifndef UNICODE // https://stackoverflow.com/q/13977388
#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;;
}
如果我使用注释中给出的
g++
从此源文件构建.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 3 中测试它 - 这是
QtTestLib.py
:
from ctypes import *
mylib = cdll.LoadLibrary("./QtTestLib.dll")
datastr = "Hello World from .py tester"
ret = mylib.myStringTesterFunc(datastr, False)
print(f"{ret=}")
如果我只是运行这个 Python 脚本,它就会退出:
$ python3 QtTestLib.py
$
由于脚本退出时没有写出任何内容,尽管我命令
print()
,但我可以看出有些问题 - 所以我启动了gdb
:
$ gdb --args python3 QtTestLib.py
GNU gdb (GDB) 14.2
...
(gdb) r
Starting program: C:\msys64\mingw64\bin\python3.exe 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
#2 0x00007fff8c334cc1 in ?? () from C:\msys64\mingw64\bin\libffi-8.dll
...
(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
所以,第一件事是,C++
myStringTesterFunc()
函数确实是通过 ctypes 从 Python3 调用的 - 这太棒了!然而:
inString
的第一个参数是一个 QString 引用,具有 NULL 指针值 - 即使我在调用中指定了一个 (Python) 字符串False
- 最终得到的值为 18(我认为这在 C++ 中不是“错误”)因此,Python 脚本中指定的预期函数参数最终被 C++ 函数错误地接收......
那么,我怎样才能通过
ctypes
从 Python 3 正确调用这个 C++ 函数呢?或者有比ctypes
更好/更简单的方法吗?
不完全是所提出问题的答案;然而,在寻找答案时,我发现了Export of C++ classes for Python,它指出了一个解决方案 - 我引用:
Python 的 ctypes 仅支持 C 语言功能。因此无法使用 ctypes 从 Python 直接访问类和其他 C++ 对象。但是,有一些方法可以访问它们。 [...]
更快的方法
幸运的是我遇到了一个替代方案 - pybind11。它是一个相对较小的库,与 Boost::Python 执行相同的操作,并且仅包含头文件。它有很好的文档并且易于使用。它被称为 pybind11,因为它最初是为了支持 C++11,但它目前支持 C++14 和实验性的 C++17,但我将它用于 C++17 项目,到目前为止没有发现任何问题.
所以,我尝试将 OP 中的示例重新修改为
pybind11
- 终于让它工作了;工作代码发布在Pybind11错误的这个答案中:没有调用的匹配函数