Dynamically loading C++ components
-
I created a test application as a QML plugin, but I'm trying to figure out how to make the QPluginLoader load the main.qml file that is part of that plugin declaring the application UI into the predefined viewport object, any suggestions?
I'm loading the plugin application this way:
appsDir.setPath("R:\\workspace\\build-testapp_plug-Desktop_Qt_5_8_0_MSVC2015_64bit-Debug\\debug"); root = engine->rootObjects().first(); viewport = root->findChild<QObject*>("viewport"); if(viewport == nullptr) { qDebug() << "no viewport component"; throw; } QPluginLoader* loader(new QPluginLoader(appsDir.absoluteFilePath("testapp_plug"))); QObject* app(loader->instance()); if(app == nullptr) { qDebug() << "no app plugin instance: " << loader->errorString(); throw; } app->setProperty("parent", QVariant::fromValue<QObject*>(viewport));
-
@romsharkov Does the plugin contain a resource file with the qml files in it? In which case I would just load the resource file and then get the main.qml from that like so:
auto engine = new QQmlApplicationEngine; auto comp = new QQmlComponent(engine, QUrl("qrc:///main.qml"));
I may not be fully understanding what the problem is though. And I'm making assumptions you are loading from a resource.
Is there a particular problem or error that you are encountering?
-
@ambershark said in Dynamically loading C++ components:
@romsharkov Does the plugin contain a resource file with the qml files in it? In which case I would just load the resource file and then get the main.qml from that like so:
auto engine = new QQmlApplicationEngine; auto comp = new QQmlComponent(engine, QUrl("qrc:///main.qml"));
I believe that if I refer to qrc:/main.qml I will get the host-application's main.qml and not the plugin's main.qml and that's the problem.. the plugin is just another application that's dynamically embedded into the host application
Is the qrc of the plugin compiled right into the plugin itself? Can I somehow retrieve the plugin's main.qml to load it as a component into the viewport object? Or should I do it another way?
-
@romsharkov Do you have to call it main.qml?
-
@jsulm said in Dynamically loading C++ components:
@romsharkov Do you have to call it main.qml?
not really, but it should be incapsulated I guess? I mean what if I have multiple application plugins that have similar qml file names, this is very likely... they should all somehow be bound to their plugin's scope
-
@romsharkov Oh I see you are just confused on loading resources outside of your main application resources.
Here check out these docs:
http://doc.qt.io/qt-5/resources.htmlThat should explain everything, especially the section on loading resources from a library which is all a plugin really is.
-
I think I finally made it...
I ended up defining a plugin interface for 3rd party applications to be compatible with the host app. The interface requires the application to provide a list of all widgets (those are basically UI windows)
virtual QVector<adapter::Widget> widgets() const = 0;
and also the app is required to implement the QML getter, which returns the QML source code for one of the provided widgets on demand:
virtual QByteArray widget(int widgetId) const = 0;
The QML sources are placed in a qrc resource file and so compiled right into the application or plugin library if you will, the getter implementation looks like this:
QByteArray App_Plugin::widget(int widgetId) const { QFile src; switch(widgetId) { case Widget::BROWSER: src.setFileName(":/widgets/browser.qml"); break; case Widget::PLAYER: src.setFileName(":/widgets/player.qml"); break; } src.open(QIODevice::ReadOnly); return src.readAll(); }
When loading the application a new context is created and a widget's QML source code is retrieved to be displayed in the viewport component of the host client application.
//load application QPluginLoader* loader(new QPluginLoader(_appsDir.absoluteFilePath(appPlugName))); QObject* app(loader->instance()); if(app == nullptr) { //failed loading application throw; } auto appInterface = qobject_cast<adapter::AppInterface*>(app); if (appInterface == nullptr) { //application doesn't implement the required adapter interface throw; } //remember plugin loader for this application, we might want to unload it later _loaders.insert(name, loader); //here I can eventually create a separate engine for this application //to set api providers such as the NetworkAccessManager //to limit and monitor the applications capabilities //for obvious security sandboxing reasons. //create isolated application context QQmlContext* appContext(new QQmlContext(_engine)); QQmlComponent appComponent(_engine); //load a widget (QML application UI window) into the viewport //in this case just 0, which defines the "browser widget" for testing purposes appComponent.setData(appInterface->widget(0), QUrl()); QObject* obj(appComponent.create(appContext)); obj->setProperty("parent", QVariant::fromValue<QObject*>(_viewport));
I'll be experimenting with this basement, we'll see how it does.
P.S. It's like a web-browser, but cooler in the sense that it uses QML instead of HTML and allows the app's frontend to be written/optimized in C++ rather than just JavaScript.
-
This post is deleted!
-
Currently there are 2 major problems that I seem not to be able to solve on my own without your help:
1. Using internal types inside the 3rd party application
Requirement: A third party QML app that is loaded into the client QtQuick application must be able to define it's own, unexported C++ defined QML Types. Those types shall only be visible to the certain app and not any other app as apps should be isolated.
Problem: When I subclass the QQmlExtensionPlugin for the app's plugin library I override the virtual registerTypes method which is stated to define the types the plugin library carries, but's it's actually never called in the loading code I described above. Trying to import the plugin's types in one of the apps QML files the host app crashes with the error: :3 module "com.company.app" is not installed\n:
import com.company.app 1.0
When I try to register the types manually like this:
auto plugInterface = qobject_cast<QQmlExtensionPlugin*>(app); plugInterface->registerTypes("com.company.app");
it stops crashing, but the properties of the types are undefined, so this doesn't work either..
2. Using internal QML files inside the 3rd party application
Requirement: A third party QML app that is loaded into the client QtQuick application must be able to use it's own, unexported QML types defined in the QML files located in the qrc resources of the plugin library. Other applications should not be able to recognize those QML types.
Problem: Those qml files are not recognized returning the error: :11 MyType is not a type\n
-
@romsharkov For #1 I'm not sure. It's not something I've ever dealt with. I just started learning Qml a few weeks ago. I've used C++ Qt for 14 years or so but never touched Qml until recently.
For #2: This is weird.. If you have your qml files in your qrc it should be able to detect them. Once thing I noticed is when you form the url to load the "main" qml page it needs to be something like
qrc:///my.qml
. I noticed if I didn't do the URL like that it couldn't find my components. You can also use aliases in the qrc which can help it find the component.You'll probably need some googling or additional help on this though. I'm just not good enough with Qml yet to be much help on these problems.
-
I guess QML extension plugins are not quite the solution I was looking for cause... all plugins do is they provide types like a library (yep, now I got the "a plugin is just a library really") but seems like they cannot provide isolated environments...
Basically what I'm trying to implement here looks just like a regular web browser with the only exception that it loads a dynamic library containing QML and C++ stuff instead of HTML/CSS/JS, but the apps are really just web-pages and they must be executed inside an isolated sandbox.
The problem seems to be that extension libraries are not meant to provide QML types for just a particular context (sandbox), instead it defines the types globally, like if a web-app would define it's own types for the entire browser including all other web-apps and tabs which is, of course, preposterous and wrong.
One solution I just thought of is running each application in it's own QQmlEngine instance. Methods like QQmlEngine::importPlugin give me new hope, if it does what I assume it would do then isolation is possible through separate engines and each engine would have it's own types and state. I will try this and report back, but what I already fear is that the memory footprint of such an engine instance is significant (as I assume it runs a whole JavaScript engine + other stuff) and that running multiple applications at the same time might cause significant memory over-consumption.
-
@romsharkov This is fantastic. I would love to see the result of the work. I was trying to attempt something like this but never thought it would be possible. Can you put the both server and actual project files in github as an example. This is a nicest approach I saw since some time.
-
@romsharkov Yea you can check into the engine path, but the problem here is that Qml/Qt are full languages. It is hard to sandbox them. Basically you would have to write something to interpret the Qml like a web browser interprets the HTML. Then you could sandbox it.
But really this would only be to a degree. Look at web browsers they have security holes all the time that allow viruses and other bad stuff through.
The problem with plugins and libraries is you are allowing someone to run code on a system. It's hard to lock that down as I mentioned earlier.
Maybe look into the chroot jail that posix OSes do. That may put you on the right track. But even then as hard as it is, you can still bypass a chrooted jail. Sandboxing is incredibly tough to pull off. But it is doable. If it's something you really want to do then keep at it, you'll get there. :)
-
@ganeshkbhat Allowing to load and run 3rd C++ inside your main applications's process is very dangerous and we have not found any easy way to sandbox C++ so far, only just have a few ideas worth trying. I've described this issue here in a little more detail.