将 Qt5 ctypes 参数传递给 Python 3 中的 C++ Qt5 函数?

问题描述 投票:0回答:1

首先是我的平台信息:

$ 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 调用的 - 这太棒了!然而:

  • C++
    inString
    的第一个参数是一个 QString 引用,具有 NULL 指针值 - 即使我在调用中指定了一个 (Python) 字符串
  • 第二个参数是 bool - 我在 Python 调用中指定为
    False
    - 最终得到的值为 18(我认为这在 C++ 中不是“错误”)

因此,Python 脚本中指定的预期函数参数最终被 C++ 函数错误地接收......

那么,我怎样才能通过

ctypes
从 Python 3 正确调用这个 C++ 函数呢?或者有比
ctypes
更好/更简单的方法吗?

c++ python-3.x qt5 ctypes
1个回答
0
投票

不完全是所提出问题的答案;然而,在寻找答案时,我发现了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错误的这个答案中:没有调用的匹配函数

© www.soinside.com 2019 - 2024. All rights reserved.