我正在尝试为Python构建一个DLL,它使用Qt5库;下面的示例定义了一个函数,并且大多数情况下一切都有效 - 除了我无法使方法 QString::toLocal8Bit 可用于 Python。
我的平台信息:
$ 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
这里是
QtTestLib.cpp
#include <pybind11/pybind11.h> // pacman -S mingw-w64-x86_64-pybind11
#include <QStringList>
#include <iostream>
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;
}
namespace py = pybind11;
// "The module name (example) is given as the first macro argument (it should not be in quotes)."
PYBIND11_MODULE(QtTestLib, m) {
m.doc() = "pybind11 example module (myStringTesterFunc)"; // optional module docstring
m.def("myStringTesterFunc", &myStringTesterFunc, "A function that tests QString");
py::class_<QString>(m, "QString")
.def(py::init<const char*>())
//.def("toLocal8Bit", &QString::toLocal8Bit)
;
}
编译:
g++ -g -Wall -Wextra -O -pedantic -shared -std=c++11 -fPIC $(\python3 -m pybind11 --includes) QtTestLib.cpp -o QtTestLib$(python3-config --extension-suffix) -I/mingw64/include/QtCore -I/mingw64/include/python3.11 $(python3-config --ldflags) /mingw64/bin/Qt5Core.dll $(python3-config --libs)
这编译得很好,对我来说生成了一个
QtTestLib.cp311-mingw_x86_64.pyd
文件。
这是
QtTestLib.py
:
import QtTestLib
print(QtTestLib.myStringTesterFunc)
print(QtTestLib.QString)
qstringret = QtTestLib.myStringTesterFunc(QtTestLib.QString("Hello world"), True)
print(qstringret)
print(qstringret.toLocal8Bit())
当我运行 Python 脚本时,我得到:
$ python3 QtTestLib.py
<built-in method myStringTesterFunc of PyCapsule object at 0x000001fbd1539d70>
<class 'QtTestLib.QString'>
<QtTestLib.QString object at 0x000001fbd15818b0>
Traceback (most recent call last):
File "C:/msys64/tmp/QtTestLib.py", line 18, in <module>
print(qstringret.toLocal8Bit())
^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'QtTestLib.QString' object has no attribute 'toLocal8Bit'
我们可以看到有一个返回的对象;然而,发布的 .cpp 代码不会“导出”到Local8Bit,因此 Python 看到“对象没有属性”也就不足为奇了。
现在,取消 .cpp 代码中这一行的注释:
.def("toLocal8Bit", &QString::toLocal8Bit)
并再次尝试用 g++ 重新编译——我得到:
$ g++ -g -Wall -Wextra -O -pedantic -shared -std=c++11 -fPIC $(\python3 -m pybind11 --includes) QtTestLib.cpp -o QtTestLib$(python3-config --extension-suffix) -I/mingw64/include/QtCore -I/mingw64/include/python3.11 $(python3-config --ldflags) /mingw64/bin/Qt5Core.dll $(python3-config --libs)
QtTestLib.cpp: In function 'void pybind11_init_QtTestLib(pybind11::module_&)':
QtTestLib.cpp:54:9: error: no matching function for call to 'pybind11::class_<QString>::def(const char [12], <unresolved overloaded function type>)'
52 | py::class_<QString>(m, "QString")
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
53 | .def(py::init<const char*>())
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
54 | .def("toLocal8Bit", &QString::toLocal8Bit)
| ~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from QtTestLib.cpp:21:
C:/msys64/mingw64/lib/python3.11/site-packages/pybind11/include/pybind11/pybind11.h:1574:13: note: candidate: 'template<class Func, class ... Extra> pybind11::class_<type_, options>& pybind11::class_<type_, options>::de (const char*, Func&&, const Extra& ...) [with Extra = Func; type_ = QString; options = {}]'
1574 | class_ &def(const char *name_, Func &&f, const Extra &...extra) {
| ^~~
C:/msys64/mingw64/lib/python3.11/site-packages/pybind11/include/pybind11/pybind11.h:1574:13: note: template argument deduction/substitution faileC:
QtTestLib.cpp:54:9: note: couldn't deduce template parameter 'Func'
52 | py::class_<QString>(m, "QString")
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
53 | .def(py::init<const char*>())
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
54 | .def("toLocal8Bit", &QString::toLocal8Bit)
| ~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
C:/msys64/mingw64/lib/python3.11/site-packages/pybind11/include/pybind11/pybind11.h:1599:13: note: candidate: 'template<class T, class ... Extra, typename stC::enable_if<T::op_enable_if_hook, int>::type <anonymous> > pybind11::class_<type_, options>& pybind11::class_<type_, options>::def(const T&, const Extra& ...) [with Extra = T; typename stC::enable_if<T::op_enable_if_hook, int>::type <anonymous> = {Extra ...}; type_ = QString; options = {}]'
1599 | class_ &def(const T &op, const Extra &...extra) {
| ^~~
C:/msys64/mingw64/lib/python3.11/site-packages/pybind11/include/pybind11/pybind11.h:1599:13: note: template argument deduction/substitution faileC:
C:/msys64/mingw64/lib/python3.11/site-packages/pybind11/include/pybind11/pybind11.h:1598:95: error: 'op_enable_if_hook' is not a member of 'char [12]'
1598 | template <typename T, typename... Extra, detail::enable_if_t<T::op_enable_if_hook, int> = 0>
| ^
C:/msys64/mingw64/lib/python3.11/site-packages/pybind11/include/pybind11/pybind11.h:1611:13: note: candidate: 'template<class ... Args, class ... Extra> pybind11::class_<type_, options>& pybind11::class_<type_, options>::def(const pybind11::detail::initimpl::constructor<Args ...>&, const Extra& ...) [with Args = {Args ...}; Extra = {Extra ...}; type_ = QString; options = {}]'
1611 | class_ &def(const detail::initimpl::constructor<Args...> &init, const Extra &...extra) {
| ^~~
C:/msys64/mingw64/lib/python3.11/site-packages/pybind11/include/pybind11/pybind11.h:1611:13: note: template argument deduction/substitution faileC:
QtTestLib.cpp:54:9: note: mismatched types 'const pybind11::detail::initimpl::constructor<Args ...>' and 'const char [12]'
52 | py::class_<QString>(m, "QString")
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
53 | .def(py::init<const char*>())
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
54 | .def("toLocal8Bit", &QString::toLocal8Bit)
| ~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
C:/msys64/mingw64/lib/python3.11/site-packages/pybind11/include/pybind11/pybind11.h:1618:13: note: candidate: 'template<class ... Args, class ... Extra> pybind11::class_<type_, options>& pybind11::class_<type_, options>::def(const pybind11::detail::initimpl::alias_constructor<Args ...>&, const Extra& ...) [with Args = {Args ...}; Extra = {Extra ...}; type_ = QString; options = {}]'
1618 | class_ &def(const detail::initimpl::alias_constructor<Args...> &init, const Extra &...extra) {
| ^~~
C:/msys64/mingw64/lib/python3.11/site-packages/pybind11/include/pybind11/pybind11.h:1618:13: note: template argument deduction/substitution faileC:
QtTestLib.cpp:54:9: note: mismatched types 'const pybind11::detail::initimpl::alias_constructor<Args ...>' and 'const char [12]'
52 | py::class_<QString>(m, "QString")
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
53 | .def(py::init<const char*>())
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
54 | .def("toLocal8Bit", &QString::toLocal8Bit)
| ~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
C:/msys64/mingw64/lib/python3.11/site-packages/pybind11/include/pybind11/pybind11.h:1625:13: note: candidate: 'template<class ... Args, class ... Extra> pybind11::class_<type_, options>& pybind11::class_<type_, options>::def(pybind11::detail::initimpl::factory<Args ...>&&, const Extra& ...) [with Args = {Args ...}; Extra = {Extra ...}; type_ = QString; options = {}]'
1625 | class_ &def(detail::initimpl::factory<Args...> &&init, const Extra &...extra) {
| ^~~
C:/msys64/mingw64/lib/python3.11/site-packages/pybind11/include/pybind11/pybind11.h:1625:13: note: template argument deduction/substitution faileC:
QtTestLib.cpp:54:9: note: mismatched types 'pybind11::detail::initimpl::factory<Args ...>' and 'const char [12]'
52 | py::class_<QString>(m, "QString")
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
53 | .def(py::init<const char*>())
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
54 | .def("toLocal8Bit", &QString::toLocal8Bit)
| ~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
C:/msys64/mingw64/lib/python3.11/site-packages/pybind11/include/pybind11/pybind11.h:1631:13: note: candidate: 'template<class ... Args, class ... Extra> pybind11::class_<type_, options>& pybind11::class_<type_, options>::def(pybind11::detail::initimpl::pickle_factory<Args ...>&&, const Extra& ...) [with Args = {Args ...}; Extra = {Extra ...}; type_ = QString; options = {}]'
1631 | class_ &def(detail::initimpl::pickle_factory<Args...> &&pf, const Extra &...extra) {
| ^~~
C:/msys64/mingw64/lib/python3.11/site-packages/pybind11/include/pybind11/pybind11.h:1631:13: note: template argument deduction/substitution faileC:
QtTestLib.cpp:54:9: note: mismatched types 'pybind11::detail::initimpl::pickle_factory<Args ...>' and 'const char [12]'
52 | py::class_<QString>(m, "QString")
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
53 | .def(py::init<const char*>())
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
54 | .def("toLocal8Bit", &QString::toLocal8Bit)
| ~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
那么,为什么会出现这个错误,我该如何修复它,这样我就可以在 Python 中使用 QString.toLocal8Bit 了?
是的 - 虽然我无法回答所提出的问题,但经过一番反复试验后,我找到了一个可行的解决方案。
首先,看
error: no matching function for call to 'pybind11::class_<QString>::def(const char [12], <unresolved overloaded function type>)'
,我意识到那里的[12]将“toLocal8Bit”的大小引用为\0
中的C字符串(加上.def("toLocal8Bit", &QString::toLocal8Bit)
C字符串终止符)。所以这似乎是调用 QString::toLocal8Bit
的问题......
所以我查看了我的
/mingw64/include/QtCore/qstring.h
,在那里我可以看到这个:
//...
#if defined(Q_COMPILER_REF_QUALIFIERS) && !defined(QT_COMPILING_QSTRING_COMPAT_CPP) && !defined(Q_CLANG_QDOC)
//...
Q_REQUIRED_RESULT QByteArray toLocal8Bit() const &
{ return toLocal8Bit_helper(isNull() ? nullptr : constData(), size()); }
Q_REQUIRED_RESULT QByteArray toLocal8Bit() &&
{ return toLocal8Bit_helper(isNull() ? nullptr : constData(), size()); }
#else
//...
Q_REQUIRED_RESULT QByteArray toLocal8Bit() const;
//...
static QByteArray toLocal8Bit_helper(const QChar *data, int size);
//...
Q_REQUIRED_RESULT QByteArray toLocal8Bit() const;
//...
无论如何,即使可能是,它看起来也不像“普通”C++ 方法,这可能会让模板引擎感到困惑。
然后我在同一个标题中看到了这个:
//...
const ushort *utf16() const;
//...
...所以我尝试了这个而不是 def for
toLocal8Bit()
:
//...
.def("utf16", &QString::utf16)
//...
实际上编译得很好,但是Python代码中的
print(qstringret.utf16())
打印出82,这不是我期望的字符串。
最后我尝试公开 toStdString - 最后它成功了;所以这是固定代码,
QtTestLib.cpp
:
#include <pybind11/pybind11.h>
#include <QStringList>
#include <iostream>
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;
}
namespace py = pybind11;
// "The module name (example) is given as the first macro argument (it should not be in quotes)."
PYBIND11_MODULE(QtTestLib, m) {
m.doc() = "pybind11 example module (myStringTesterFunc)"; // optional module docstring
m.def("myStringTesterFunc", &myStringTesterFunc, "A function that tests QString");
py::class_<QString>(m, "QString")
.def(py::init<const char*>())
.def("toStdString", &QString::toStdString) // passes - and works like a string in Python!
;
}
// 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;;
}
编译
QtTestLib.cp311-mingw_x86_64.pyd
:
g++ -g -Wall -Wextra -O -pedantic -shared -std=c++11 -fPIC $(\python3 -m pybind11 --includes) QtTestLib.cpp -o QtTestLib$(python3-config --extension-suffix) -I/mingw64/include/QtCore -I/mingw64/include/python3.11 $(python3-config --ldflags) /mingw64/bin/Qt5Core.dll $(python3-config --libs)
Python 脚本
QtTestLib.py
:
import QtTestLib
print(QtTestLib.myStringTesterFunc)
print(QtTestLib.QString)
qstringret = QtTestLib.myStringTesterFunc(QtTestLib.QString("Hello world"), True)
print(qstringret)
print(qstringret.toStdString())
运行Python脚本:
$ python3 QtTestLib.py
<built-in method myStringTesterFunc of PyCapsule object at 0x0000028326a99d70>
<class 'QtTestLib.QString'>
<QtTestLib.QString object at 0x0000028326ae18b0>
Result: Hello world - Special
嗯,那太好了 - 很高兴看到这个工作......