First of all, my platform info:
$ 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
I want to build some C++ code that uses Qt5 as a shared object (.dll), which I can then load in Python 3, and eventually, possibly use it together with PyQt5.
So, at first, I thought I’d get this working via ctypes
, and came up with the example code below. I can build the C++ code, call it from Python via ctypes, but the Python script crashes as I seemingly cannot pass a QString from Python to C++ code.
Here is my example – first, this is 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 // /q/61294630
#ifdef _WIN32
# define API extern "C" __declspec(dllexport)
#else
# define API extern "C"
#endif
#ifndef UNICODE // /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;;
}
If I build the .exe from this source file, using the g++
given in the comments, it seems to work fine:
$ 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
The compilation of the .dll also proceeds fine, with no errors/warnings:
$ g++ -std=gnu++11 -g -Wall -Wextra -O -pedantic -shared QtTestLib.cpp -o QtTestLib.dll -I/mingw64/include/QtCore /mingw64/bin/Qt5Core.dll
$
Now I want to test this in Python 3 – here is QtTestLib.py
:
from ctypes import *
mylib = cdll.LoadLibrary("./QtTestLib.dll")
datastr = "Hello World from .py tester"
ret = mylib.myStringTesterFunc(datastr, False)
print(f"{ret=}")
If I just run this Python script, it just exits:
$ python3 QtTestLib.py
$
Since the script exits without writing anything out, in spite of me commanding to print()
, I can tell something is wrong – so I fire up gdb
:
$ gdb --args python3 QtTestLib.py
GNU gdb (GDB) 14.2
...
(gdb) r
Starting program: C:msys64mingw64binpython3.exe QtTestLib.py
...
Thread 1 received signal SIGSEGV, Segmentation fault.
0x00007fff59fd6225 in ?? () from D:msys64mingw64binQt5Core.dll
(gdb) bt
#0 0x00007fff59fd6225 in ?? () from D:msys64mingw64binQt5Core.dll
#1 0x00007fff803413df in myStringTesterFunc (inString=..., specProcess=18)
at D:/msys64/mingw64/include/QtCore/qchar.h:55
#2 0x00007fff8c334cc1 in ?? () from D:msys64mingw64binlibffi-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
So, the first thing is, the C++ myStringTesterFunc()
function did indeed get called from Python3 via ctypes – which is great! However:
- The first argument of the C++
inString
which is a QString reference, has a NULL pointer value – even if I specified a (Python) string in the call - The second argument, which is bool – and I specified as
False
in the Python call, – ended up having a value of 18 (which I don’t think is “falsey” in C++)
So, the intended function arguments as specified in Python script, ended up being received wrongly by the C++ function …
So, how can I properly call this C++ function from Python 3 via ctypes
? Or is there a better/easier approach than ctypes
?