Dynamically loading C++ components
-
Working on an app that is loading "sub-applications" from a server gathering QML sources to be executed. It does this for security isolation reasons, as the sub-apps are 3rd party. The sources contain the JavaScript application logic which calls host app's API out of the QML.
The logic complexity and amount of data the sub-app could potentially face is expected to increase which would obviously lower the overall performance and increase memory consumption. I'm wondering whether it's possible to dynamically load logic components written in C++ besides the QML of the sub-app for increased performance?
The app is cross platform by the way.
-
@romsharkov Sounds like plug-in: http://doc.qt.io/qt-5/plugins-howto.html
-
@jsulm said in Dynamically loading C++ components:
@romsharkov Sounds like plug-in: http://doc.qt.io/qt-5/plugins-howto.html
Can we store a plugin written in C++ on the server and pass it over to the client QtQuick app (which previously didn't know anything about this plugin) to be run in the context of that certain sub-app?
-
@romsharkov The client app would have to support the plugin in some fashion to do that. It doesn't necessarily need to know about it but it would have to support loading it.
So you would need to publish some sort of API or documentation on interfaces into the plugin system for the 3rd party app developers to use.
-
Qt plugins are just shared objects / dynamic-link libraries with a convenience API. Don't know what exactly you're doing there, but allowing your host application to be linked against 3rd party shared objects might undermine your security concept.
-
@Wieland said in Dynamically loading C++ components:
Qt plugins are just shared objects / dynamic-link libraries with a convenience API. Don't know what exactly you're doing there, but allowing your host application to be linked against 3rd party shared objects might undermine your security concept.
Well, I actually want to put business logic (because JavaScript is kinda slow thus not suited for every kind of problem) into a C++ component that is bundled together with the QML presentation layer.
The QML together with the C++ business logic component are fetched from the application server and executed in an isolated context in the host QtQuick application. So the C++ plugin is made available for this particular QML context only.
Is this actually possible and secure?
-
@romsharkov It's definitely possible, and it's as secure as you make it.
The security will rely on how secure your "isolated context" is. If that is a good "chroot jail" type of scenario then it should be pretty secure.
But of course 3rd party apps can do anything they want so malicious coders could find a way depending on how good your "jail" security is.
-
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. :)