Using WinRT/CPP with QT 6.x.x.
-
Is there any steps needed to enable the usage of WinRT/CPP functions within Qt 6.x.x?
I've tried adding "c++17", "await", and "bigobj" to CONFIG and "-lwindowsapp" to win32:LIBS but i've been getting errors on the "wait_for" function.
Would like to use the Windows.Gaming.Input functions to be able to have better controller compatibility than using Direct Input (which I know is legacy code.)
Any help is appreciated!
-
ok, got it working!
Added "-lOLEAUT32" to win32:LIBS and added the following to the separate QThreads run function (whenever i used them) and the main function:winrt::uninit_apartment(); winrt::init_apartment();
So to condense all of this into one post (for those who are also trying to do what im doing):
.pro:CONFIG += c++20 QMAKE_CXXFLAGS += /await:strict win32:LIBS += -lwindowsapp -lOLEAUT32
main.cpp:
#include <winrt/base.h> int main(int argc, char *argv[]) { QApplication a(argc, argv); winrt::uninit_apartment(); winrt::init_apartment(); return a.exec(); }
QThread derived classes:
#include <winrt/base.h> class DerivedThread : public QThread { public: DerivedThread (QThread* parent = nullptr) : QThread(parent) {} public: void run(){ winrt::uninit_apartment(); winrt::init_apartment(); exec(); } };
Those alone should get everything up and running to be able to use functions (aside from specific things like things requiring other things [looking at you collections...])
Now to create some qobject classes and get test out actual inputs!
Thank you all for your help!Oh! Almost forgot this tidbit, make sure your build tools/sdk's are up to date!
EDIT: Looks like derived threads dont need to have the unint/init_appartment functions.
-
Hi, I've tested some WinRT/CPP stuff using Qt 6.3.0 MSVC2019 16.11.13.
For a starter project, create a new empty vanilla widgets app. Use qmake not cmake for this example, an RSS feed reader for showing the most recent titles of Qt Forum posts.To make it work with WinRT/CPP, we have to add some stuff, first add these lines in the .pro file after CONFIG += c++17 :
QMAKE_CXXFLAGS += "/Zc:__cplusplus" QMAKE_CXXFLAGS += "/await" QMAKE_CXXFLAGS += "/Zc:twoPhase-"
then change main.cpp to this:
int main(int argc, char *argv[]) { winrt::init_apartment(); // <--- add this line QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); }
then change mainwindow.h to look like this:
#pragma once #include <winrt/Windows.Foundation.Collections.h> #include <winrt/Windows.Web.Syndication.h> using namespace winrt; using namespace Windows::Foundation; using namespace Windows::Web::Syndication; #include <QMainWindow> QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); private: Ui::MainWindow *ui; };
then finally the mainwindow.cpp file:
#include "mainwindow.h" #include "ui_mainwindow.h" #include "qtimer.h" #include "qdebug.h" #pragma comment(lib, "windowsapp") // get recent forum posts IAsyncAction ProcessFeedAsync() { Uri rssFeedUri{ L"https://forum.qt.io/recent.rss" }; SyndicationClient syndicationClient; SyndicationFeed syndicationFeed{ co_await syndicationClient.RetrieveFeedAsync(rssFeedUri) }; for (SyndicationItem const& syndicationItem : syndicationFeed.Items()) { qDebug() << syndicationItem.Title().Text(); } } MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); if (ProcessFeedAsync().wait_for(std::chrono::seconds(3)) == AsyncStatus::Completed) ProcessFeedAsync().get(); else qDebug() << "Sorry no answer within 3 seconds"; QTimer::singleShot(10,[] { qApp->quit(); }); } MainWindow::~MainWindow() { delete ui; }
this should get you a head start of interfacing Qt with WinRT/CPP. Good luck!
-
Hi, forgot to mention, in the Application Output in Qt Creator, before the Qt Forum post titles appear, you'll see this infamous line:
QWindowsContext: OleInitialize() failed: "COM error 0xffffffff80010106 RPC_E_CHANGED_MODE (Unknown error 0x080010106)
This is a harmless warning (Qt Python users know it quite well I believe) due to WinRT/CPP and Qt fighting over who owns COM. I haven;t solved it yet but there are plenty of Google hits on it :-)
Edit: tested today: found a workaround to the fight over COM: change main.cpp to this:
#include "mainwindow.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); winrt::uninit_apartment(); winrt::init_apartment(); MainWindow w; w.show(); return a.exec(); }
Normally if you let QApplication have the first call the COM then WinRT/CPP later crashes, but by first resetting/uniniting WinRT/CPP it seems to make it happy. And no more "... failed: "COM error ... " warning messages :-)
-
@hskoglund The warning is related to COM apartments (sort of a sandboxes in which COM operates). COM should be initialized in each thread it is used in. There are two modes - single and multithreaded. In single threaded mode an apartment is created just for that thread. In multithreaded mode every thread of a process that initializes COM in multithreaded mode shares a common apartment.
You can initialize COM multiple times on a single thread. The following calls are just refcounted but do nothing else. There's a restriction though that on a single thread COM can only be initialized in one mode at a time (which makes sense).The problem here is that for whatever reason Qt initializes COM internally and does so in single threaded mode :( The default mode for winrt
init_apartment
function is multithreaded and thus you get a warning.So the "trick" above just shuts down COM initialized by Qt in single threaded mode and reinitializes it, this time in multithreaded mode.
If you're ok with single threaded mode you can just skip the
winrt::init_apartment()
call altogether. Qt already initializes it. You can also explicitly call it aswinrt::init_apartment(winrt::apartment_type::single_threaded);
, but that's just gonna bump the refcount.Having said that, winrt is designed to work asynchronously on multiple threads, so running it in single threaded mode COM is not a good idea and thus the above workaround is needed. It would be nice if Qt exposed that as a setting somehow, but oh well.
Btw. if you want to be forward looking you should compile your code in C++20 mode and use
/await:strict
switch variant instead. This is a compiler mode conformant to the coroutines as standardized and will become the default in the future versions of the compiler. You might as well start using it already to avoid rewrites in the future. The/await
switch in C++17 mode implements coroutines as they were in the technical spec before standardization, and thus are a legacy non-conformant Microsoft's version. -
Thanks for the help.
I was able to compile it but apparently i could not used the "/await:strict" compiler switch. Something to do with experimental coroutines? Probably old libraries.What I have done is the following:
CONFIG += c++20 QMAKE_CXXFLAGS += "/Zc:__cplusplus" "/Zc:twoPhase-" DEFINES += _SILENCE_CLANG_COROUTINE_MESSAGE #this is needed as apparently the coroutine include is experimental/unsupported? win32:LIBS += -lwindowsapp
Apparently it might be the Qt's intellisense that is stating that there's "no member named 'wait_for' in namespace winrt::impl".
Now, trying to actually use the functions is not working right...
Apparently "a function that returns 'auto' cannot be used before it is defined" is happening.And trying to use this example, is resulting in a bit of errors...
I'll see about running some msvc build tools updates...
-
I'm on the latest VS2022 and Windows SDK and that example, slightly modified, compiles ok:
.pro
QT += core gui widgets CONFIG += c++20 QMAKE_CXXFLAGS += /await:strict SOURCES += main.cpp LIBS += -lwindowsapp
main.cpp
#include <winrt/Windows.Foundation.Collections.h> #include <winrt/Windows.Gaming.Input.h> using namespace winrt; using namespace Windows::Gaming::Input; int main() { std::vector<RawGameController> myRawGameControllers; for (auto const& rawGameController : RawGameController::RawGameControllers()) { auto it{ std::find(begin(myRawGameControllers), end(myRawGameControllers), rawGameController) }; if (it == end(myRawGameControllers)) { myRawGameControllers.push_back(rawGameController); } } }
Note that I'm not using any coroutines here, so the await switch is not necessary in this case.
Also no devices are found for me, but I think you need to have a winrt window (or any window) for that. For console apps the older XInput api is recommended instead. -
Using VS 2019, though i believe i've updated my libraries...
Here's the errors:
And here's the compilers that im using for MSVC with Qt 6.3.0:
I had removed an old sdk (and apparently only installed the win 11 sdk XP) but the win 11 sdk says it's version 10.0.22000.0, gonna install the "latest" win 10 sdk, which it's version is 10.0.20348.0.
-
Hi Chris, thanks for the explanation of the COM fighting, indeed it would be easier if Qt allowed different COM apartment flavors.
I also tried that sample, I installed VS2019 16.11.13 and on Windows 10 21H2 Build 19044.1645. I have not installed any special SDK, just Visual Studio and Qt 6.3.0. It copied it the C++/WinRT example verbatim (but I hat to remove that line with "..." :-)
it compiled cleanly for me, I reused the mainwindow.cpp from my sample above and replaced it with this:#include "mainwindow.h" #include "ui_mainwindow.h" #include "qtimer.h" #include "qdebug.h" #pragma comment(lib, "windowsapp") #include <winrt/Windows.Foundation.Collections.h> #include <concrt.h> #include <winrt/Windows.Gaming.Input.h> using namespace winrt; using namespace Windows::Gaming::Input; MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); std::vector<RawGameController> myRawGameControllers; concurrency::critical_section myLock{}; for (auto const& rawGameController : RawGameController::RawGameControllers()) { // Test whether the raw game controller is already in myRawGameControllers; if it isn't, add it. concurrency::critical_section::scoped_lock lock{ myLock }; auto it{ std::find(begin(myRawGameControllers), end(myRawGameControllers), rawGameController) }; if (it == end(myRawGameControllers)) { // This code assumes that you're interested in all raw game controllers. myRawGameControllers.push_back(rawGameController); } } QTimer::singleShot(10,[] { qApp->quit(); }); } MainWindow::~MainWindow() { delete ui; }
So to make it compile. check that you have updated your Visual Studio 2019.
And also I have no controllers to test with. Perhaps you have any Game Controller stuff or example using a mouse or keyboard we can try? -
@alatnet Make sure you have
#include <winrt/Windows.Foundation.Collections.h>
. The example is missing it and that's where the begin/end functions are. -
@Chris-Kawa Yes, nice catch!
(I happened to have that #include from the sample above, in mainwindow.h, that's why it compiled ok for me.) -
yep, that got me compiling with no issues!
Added "#include <winrt/Windows.Foundation.Collections.h>".Now, i have a controller connected, via bluetooth (ps4 controller), and it's outputting some strange stuff when i use:
for (RawGameController c : myRawGameControllers){ qDebug() << "- " << c.DisplayName(); }
which is:
onecoreuap\xbox\devices\api\winrt\pnpapiwrapper.cpp(385)\Windows.Gaming.Input.dll!00007FFC5E04974A: (caller: 00007FFC5E04D528) ReturnHr(1) tid(1784) 8685C003 onecoreuap\xbox\devices\api\winrt\pnpapiwrapper.cpp(385)\Windows.Gaming.Input.dll!00007FFC5E04974A: (caller: 00007FFC5E04D528) ReturnHr(2) tid(1784) 8685C003 onecoreuap\xbox\devices\api\winrt\pnpapiwrapper.cpp(385)\Windows.Gaming.Input.dll!00007FFC5E04974A: (caller: 00007FFC5E04D528) ReturnHr(3) tid(1784) 8685C003 onecoreuap\xbox\devices\api\winrt\pnpapiwrapper.cpp(385)\Windows.Gaming.Input.dll!00007FFC5E04974A: (caller: 00007FFC5E04D528) ReturnHr(4) tid(1784) 8685C003 onecoreuap\xbox\devices\api\winrt\pnpapiwrapper.cpp(385)\Windows.Gaming.Input.dll!00007FFC5E04974A: (caller: 00007FFC5E04D528) ReturnHr(5) tid(1784) 8685C003 ... mincore\com\oleaut32\dispatch\ups.cpp(2122)\OLEAUT32.dll!00007FFC6A509DD6: (caller: 00007FFC6A5091E9) ReturnHr(1) tid(5f44) 8002801D Library not registered.
Note: i am doing test code with it being in the main function and after the QApplication a(argc, argv);.
Edit: did test it in my direct input code where i stuck it in a section of code that queried a list of devices, same thing. -
Hi, haven't got any game controllers so I cannot test, but it looks like you're getting exceptions thrown. Just guessing but maybe it's a permissions thing, perhaps you need to run as an Administrator and make sure you've enabled developer mode.
Edit: update: also see this StackOverflow post
-
ok, got it working!
Added "-lOLEAUT32" to win32:LIBS and added the following to the separate QThreads run function (whenever i used them) and the main function:winrt::uninit_apartment(); winrt::init_apartment();
So to condense all of this into one post (for those who are also trying to do what im doing):
.pro:CONFIG += c++20 QMAKE_CXXFLAGS += /await:strict win32:LIBS += -lwindowsapp -lOLEAUT32
main.cpp:
#include <winrt/base.h> int main(int argc, char *argv[]) { QApplication a(argc, argv); winrt::uninit_apartment(); winrt::init_apartment(); return a.exec(); }
QThread derived classes:
#include <winrt/base.h> class DerivedThread : public QThread { public: DerivedThread (QThread* parent = nullptr) : QThread(parent) {} public: void run(){ winrt::uninit_apartment(); winrt::init_apartment(); exec(); } };
Those alone should get everything up and running to be able to use functions (aside from specific things like things requiring other things [looking at you collections...])
Now to create some qobject classes and get test out actual inputs!
Thank you all for your help!Oh! Almost forgot this tidbit, make sure your build tools/sdk's are up to date!
EDIT: Looks like derived threads dont need to have the unint/init_appartment functions.