From 820da15d2077de6f269582a2ee2c872473f7b77e Mon Sep 17 00:00:00 2001 From: Paul Knopf Date: Tue, 11 Sep 2018 14:29:56 -0400 Subject: [PATCH] Added support for c++ to main entry point. Closes #13 --- samples/hosting/.gitignore | 6 + samples/hosting/native/NativeHost.pro | 13 ++ samples/hosting/native/Page1Form.ui.qml | 18 ++ samples/hosting/native/Page2Form.ui.qml | 18 ++ samples/hosting/native/main.cpp | 40 ++++ samples/hosting/native/main.qml | 49 +++++ samples/hosting/native/qml.qrc | 9 + samples/hosting/native/qtquickcontrols2.conf | 6 + samples/hosting/net/NetHost.csproj | 13 ++ samples/hosting/net/NetHost.sln | 48 +++++ samples/hosting/net/Program.cs | 25 +++ src/native/QmlNet/Hosting.pri | 12 ++ src/native/QmlNet/Hosting/CoreHost.cpp | 200 ++++++++++++++++++ src/native/QmlNet/Hosting/CoreHost.h | 44 ++++ src/native/QmlNet/Hosting/coreclrhost.h | 80 +++++++ .../QmlNet/QmlNet/qml/NetTestHelper.cpp | 2 +- .../QmlNet/QmlNet/qml/QGuiApplication.cpp | 45 ++-- .../QmlNet/QmlNet/qml/QGuiApplication.h | 3 +- .../QmlNet/qml/QQmlApplicationEngine.cpp | 27 ++- .../QmlNet/QmlNet/qml/QQmlApplicationEngine.h | 4 +- src/net/Qml.Net/Host.cs | 41 ++++ src/net/Qml.Net/QGuiApplication.cs | 25 ++- src/net/Qml.Net/QQmlApplicationEngine.cs | 25 ++- 23 files changed, 720 insertions(+), 33 deletions(-) create mode 100644 samples/hosting/.gitignore create mode 100644 samples/hosting/native/NativeHost.pro create mode 100644 samples/hosting/native/Page1Form.ui.qml create mode 100644 samples/hosting/native/Page2Form.ui.qml create mode 100644 samples/hosting/native/main.cpp create mode 100644 samples/hosting/native/main.qml create mode 100644 samples/hosting/native/qml.qrc create mode 100644 samples/hosting/native/qtquickcontrols2.conf create mode 100644 samples/hosting/net/NetHost.csproj create mode 100644 samples/hosting/net/NetHost.sln create mode 100644 samples/hosting/net/Program.cs create mode 100644 src/native/QmlNet/Hosting.pri create mode 100644 src/native/QmlNet/Hosting/CoreHost.cpp create mode 100644 src/native/QmlNet/Hosting/CoreHost.h create mode 100644 src/native/QmlNet/Hosting/coreclrhost.h create mode 100644 src/net/Qml.Net/Host.cs diff --git a/samples/hosting/.gitignore b/samples/hosting/.gitignore new file mode 100644 index 00000000..2bfbd334 --- /dev/null +++ b/samples/hosting/.gitignore @@ -0,0 +1,6 @@ +build-*/ +*.pro.user +net-output/ +net/.idea/ +net/obj/ +net/.vs/ \ No newline at end of file diff --git a/samples/hosting/native/NativeHost.pro b/samples/hosting/native/NativeHost.pro new file mode 100644 index 00000000..fbcd5aab --- /dev/null +++ b/samples/hosting/native/NativeHost.pro @@ -0,0 +1,13 @@ +QT += quick +CONFIG += c++11 + +DEFINES += QT_DEPRECATED_WARNINGS + +SOURCES += \ + main.cpp + +RESOURCES += qml.qrc + +DEFINES += "NET_ROOT=\"\\\"$$PWD/../net-output\\\"\"" + +include (../../../src/native/QmlNet/Hosting.pri) diff --git a/samples/hosting/native/Page1Form.ui.qml b/samples/hosting/native/Page1Form.ui.qml new file mode 100644 index 00000000..d30db12b --- /dev/null +++ b/samples/hosting/native/Page1Form.ui.qml @@ -0,0 +1,18 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.2 + +Page { + width: 600 + height: 400 + + header: Label { + text: qsTr("Page 1") + font.pixelSize: Qt.application.font.pixelSize * 2 + padding: 10 + } + + Label { + text: qsTr("You are on Page 1.") + anchors.centerIn: parent + } +} diff --git a/samples/hosting/native/Page2Form.ui.qml b/samples/hosting/native/Page2Form.ui.qml new file mode 100644 index 00000000..d99e3182 --- /dev/null +++ b/samples/hosting/native/Page2Form.ui.qml @@ -0,0 +1,18 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.2 + +Page { + width: 600 + height: 400 + + header: Label { + text: qsTr("Page 2") + font.pixelSize: Qt.application.font.pixelSize * 2 + padding: 10 + } + + Label { + text: qsTr("You are on Page 2.") + anchors.centerIn: parent + } +} diff --git a/samples/hosting/native/main.cpp b/samples/hosting/native/main.cpp new file mode 100644 index 00000000..90a29309 --- /dev/null +++ b/samples/hosting/native/main.cpp @@ -0,0 +1,40 @@ +#include +#include +#include +#include +#include +#include + +static int runCallback(QGuiApplication* app, QQmlApplicationEngine* engine) +{ + engine->load(QUrl(QStringLiteral("qrc:/main.qml"))); + if (engine->rootObjects().isEmpty()) + return -1; + + return app->exec(); +} + +int main(int argc, char *argv[]) +{ + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QGuiApplication app(argc, argv); + + QQmlApplicationEngine engine; + + QString netDll = NET_ROOT; + netDll.append(QDir::separator()); + netDll.append("NetHost.dll"); + + CoreHost::RunContext runContext; + runContext.hostFxrContext = CoreHost::findHostFxr(); + runContext.managedExe = netDll; + // NOTE: You may set entry point to the current executable if + // the .NET runtime is deployed side-by-side. + runContext.entryPoint = runContext.hostFxrContext.dotnetRoot; + runContext.entryPoint.append(CORECLR_DOTNET_EXE_NAME); + + return CoreHost::run(app, + engine, + runCallback, + runContext); +} diff --git a/samples/hosting/native/main.qml b/samples/hosting/native/main.qml new file mode 100644 index 00000000..f1d9c40a --- /dev/null +++ b/samples/hosting/native/main.qml @@ -0,0 +1,49 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import test 1.0 + +ApplicationWindow { + visible: true + width: 640 + height: 480 + title: qsTr("Tabs") + + SwipeView { + id: swipeView + anchors.fill: parent + currentIndex: tabBar.currentIndex + + Page1Form { + } + + Page2Form { + } + } + + footer: TabBar { + id: tabBar + currentIndex: swipeView.currentIndex + + TabButton { + text: qsTr("Page 1") + } + TabButton { + text: qsTr("Page 2") + } + } + + Timer { + interval: 500; running: true; repeat: true + onTriggered: { + test.testMethod() + } + } + + TestObject { + id: test + Component.onCompleted: { + // Call .NET + test.testMethod() + } + } +} diff --git a/samples/hosting/native/qml.qrc b/samples/hosting/native/qml.qrc new file mode 100644 index 00000000..61176f13 --- /dev/null +++ b/samples/hosting/native/qml.qrc @@ -0,0 +1,9 @@ + + + main.qml + Page1Form.ui.qml + Page2Form.ui.qml + main.qml + qtquickcontrols2.conf + + diff --git a/samples/hosting/native/qtquickcontrols2.conf b/samples/hosting/native/qtquickcontrols2.conf new file mode 100644 index 00000000..75b2cb8f --- /dev/null +++ b/samples/hosting/native/qtquickcontrols2.conf @@ -0,0 +1,6 @@ +; This file can be edited to change the style of the application +; Read "Qt Quick Controls 2 Configuration File" for details: +; http://doc.qt.io/qt-5/qtquickcontrols2-configuration.html + +[Controls] +Style=Default diff --git a/samples/hosting/net/NetHost.csproj b/samples/hosting/net/NetHost.csproj new file mode 100644 index 00000000..690a6b45 --- /dev/null +++ b/samples/hosting/net/NetHost.csproj @@ -0,0 +1,13 @@ + + + + Exe + netcoreapp2.1 + $(MSBuildProjectDirectory)/../net-output + + + + + + + diff --git a/samples/hosting/net/NetHost.sln b/samples/hosting/net/NetHost.sln new file mode 100644 index 00000000..bbc2eaa7 --- /dev/null +++ b/samples/hosting/net/NetHost.sln @@ -0,0 +1,48 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26124.0 +MinimumVisualStudioVersion = 15.0.26124.0 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetHost", "NetHost.csproj", "{A787B77B-F87B-44A1-AD3D-38E21A92A84A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Qml.Net", "..\..\..\src\net\Qml.Net\Qml.Net.csproj", "{7258C1D8-8508-4ED9-8F89-5AE2EAEBED9D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A787B77B-F87B-44A1-AD3D-38E21A92A84A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A787B77B-F87B-44A1-AD3D-38E21A92A84A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A787B77B-F87B-44A1-AD3D-38E21A92A84A}.Debug|x64.ActiveCfg = Debug|Any CPU + {A787B77B-F87B-44A1-AD3D-38E21A92A84A}.Debug|x64.Build.0 = Debug|Any CPU + {A787B77B-F87B-44A1-AD3D-38E21A92A84A}.Debug|x86.ActiveCfg = Debug|Any CPU + {A787B77B-F87B-44A1-AD3D-38E21A92A84A}.Debug|x86.Build.0 = Debug|Any CPU + {A787B77B-F87B-44A1-AD3D-38E21A92A84A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A787B77B-F87B-44A1-AD3D-38E21A92A84A}.Release|Any CPU.Build.0 = Release|Any CPU + {A787B77B-F87B-44A1-AD3D-38E21A92A84A}.Release|x64.ActiveCfg = Release|Any CPU + {A787B77B-F87B-44A1-AD3D-38E21A92A84A}.Release|x64.Build.0 = Release|Any CPU + {A787B77B-F87B-44A1-AD3D-38E21A92A84A}.Release|x86.ActiveCfg = Release|Any CPU + {A787B77B-F87B-44A1-AD3D-38E21A92A84A}.Release|x86.Build.0 = Release|Any CPU + {7258C1D8-8508-4ED9-8F89-5AE2EAEBED9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7258C1D8-8508-4ED9-8F89-5AE2EAEBED9D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7258C1D8-8508-4ED9-8F89-5AE2EAEBED9D}.Debug|x64.ActiveCfg = Debug|Any CPU + {7258C1D8-8508-4ED9-8F89-5AE2EAEBED9D}.Debug|x64.Build.0 = Debug|Any CPU + {7258C1D8-8508-4ED9-8F89-5AE2EAEBED9D}.Debug|x86.ActiveCfg = Debug|Any CPU + {7258C1D8-8508-4ED9-8F89-5AE2EAEBED9D}.Debug|x86.Build.0 = Debug|Any CPU + {7258C1D8-8508-4ED9-8F89-5AE2EAEBED9D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7258C1D8-8508-4ED9-8F89-5AE2EAEBED9D}.Release|Any CPU.Build.0 = Release|Any CPU + {7258C1D8-8508-4ED9-8F89-5AE2EAEBED9D}.Release|x64.ActiveCfg = Release|Any CPU + {7258C1D8-8508-4ED9-8F89-5AE2EAEBED9D}.Release|x64.Build.0 = Release|Any CPU + {7258C1D8-8508-4ED9-8F89-5AE2EAEBED9D}.Release|x86.ActiveCfg = Release|Any CPU + {7258C1D8-8508-4ED9-8F89-5AE2EAEBED9D}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/samples/hosting/net/Program.cs b/samples/hosting/net/Program.cs new file mode 100644 index 00000000..c6ac310b --- /dev/null +++ b/samples/hosting/net/Program.cs @@ -0,0 +1,25 @@ +using System; +using Qml.Net; + +namespace NetHost +{ + class Program + { + public class TestObject + { + public void TestMethod() + { + Console.WriteLine("test method"); + } + } + + static int Main(string[] _) + { + return Host.Run(_, (args, app, engine, runCallback) => + { + QQmlApplicationEngine.RegisterType("test"); + return runCallback(); + }); + } + } +} diff --git a/src/native/QmlNet/Hosting.pri b/src/native/QmlNet/Hosting.pri new file mode 100644 index 00000000..ebe3cb0b --- /dev/null +++ b/src/native/QmlNet/Hosting.pri @@ -0,0 +1,12 @@ +INCLUDEPATH += $$PWD + +HEADERS += $$PWD/Hosting/coreclrhost.h \ + $$PWD/Hosting/CoreHost.h + +SOURCES += \ + $$PWD/Hosting/CoreHost.cpp + +unix { + LIBS += -ldl +} + diff --git a/src/native/QmlNet/Hosting/CoreHost.cpp b/src/native/QmlNet/Hosting/CoreHost.cpp new file mode 100644 index 00000000..9f86a4c8 --- /dev/null +++ b/src/native/QmlNet/Hosting/CoreHost.cpp @@ -0,0 +1,200 @@ +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#endif + +#ifdef __APPLE__ +#define HOSTFXR_DLL_NAME "libhostfxr.dylib" +#elif _WIN32 +#define HOSTFXR_DLL_NAME "hostfxr.dll" +#else +#define HOSTFXR_DLL_NAME "libhostfxr.so" +#endif + +QList CoreHost::getPotientialDotnetRoots() +{ + QList result; +#ifdef _WIN32 + result.push_back("C:\\Program Files\\dotnet"); +#else + result.push_back("/usr/local/share/dotnet"); + result.push_back("/usr/share/dotnet"); + result.push_back("/opt/dotnet"); +#endif + + QByteArray dotnetRoot = qgetenv("DOTNET_ROOT"); + if(!dotnetRoot.isEmpty()) { + // We are overriding the roots, forcing ourselves to look in a particular spot. + result.clear(); + result.push_back(dotnetRoot); + } + + return result; +} + +CoreHost::HostFxrContext CoreHost::findHostFxr() +{ + HostFxrContext result; + result.success = false; + + QList roots = getPotientialDotnetRoots(); + + for(QString root : roots) { + qDebug("looking for %s in root %s", HOSTFXR_DLL_NAME, qPrintable(root)); + if(!root.endsWith(QDir::separator())) { + root.append(QDir::separator()); + } + + QFileInfo rootInfo(root); + if(!rootInfo.exists()) { + qDebug("%s doesn't exist", qPrintable(rootInfo.path())); + continue; + } + + QString host = root; + host.append("host"); + host.append(QDir::separator()); + QFileInfo hostInfo(host); + if(!hostInfo.exists()) { + qDebug("%s doesn't exist", qPrintable(host)); + continue; + } + + QString fxr = host; + fxr.append("fxr"); + fxr.append(QDir::separator()); + QFileInfo fxrInfo(fxr); + if(!fxrInfo.exists()) { + qDebug("%s doesn't exist.", qPrintable(fxr)); + continue; + } + + QString currentFxrLib; + QVersionNumber currentFxrLibVersion; + + QDir fxrDir = fxrInfo.dir(); + fxrDir.setFilter(QDir::Dirs | QDir::NoDot | QDir::NoDotDot); + QDirIterator it(fxrDir, QDirIterator::Subdirectories); + while(it.hasNext()) { + QString fxrVersion = it.next(); + QFileInfo fxrVersionInfo(fxrVersion); + + fxrVersion.append(QDir::separator()); + fxrVersion.append(HOSTFXR_DLL_NAME); + + QFileInfo fxrLibInfo(fxrVersion); + if(!fxrLibInfo.exists()) { + qDebug("%s doesn't exist", qPrintable(fxrLibInfo.absoluteFilePath())); + continue; + } + + QVersionNumber version = QVersionNumber::fromString(fxrVersionInfo.fileName()); + if(currentFxrLibVersion.isNull() || version > currentFxrLibVersion) { + qDebug("found potentional file %s with version %s", qPrintable(fxrLibInfo.absoluteFilePath()), qPrintable((version.toString()))); + currentFxrLibVersion = version; + currentFxrLib = fxrLibInfo.absoluteFilePath(); + } else { + qDebug("ignore file %s with version %s", qPrintable(fxrLibInfo.absoluteFilePath()), qPrintable((version.toString()))); + } + } + + if(!currentFxrLib.isEmpty()) { + qDebug("returning hostfx lib: %s", qPrintable(currentFxrLib)); + result.success = true; + result.hostFxrLib = currentFxrLib; + result.dotnetRoot = root; + return result; + } + } + + return result; +} + +int CoreHost::run(QGuiApplication& app, QQmlApplicationEngine& engine, runCallback runCallback, RunContext runContext) +{ + QList execArgs; + execArgs.push_back(runContext.entryPoint); + execArgs.push_back("exec"); + execArgs.push_back(runContext.managedExe); + + QString appPtr; + appPtr.sprintf("%llu", (quintptr)&app); + QString enginePtr; + enginePtr.sprintf("%llu", (quintptr)&engine); + QString callbackPtr; + callbackPtr.sprintf("%llu", (quintptr)runCallback); + + execArgs.push_back(appPtr); + execArgs.push_back(enginePtr); + execArgs.push_back(callbackPtr); + + for (QString arg : runContext.args) { + execArgs.push_back(arg); + } + + std::vector hostFxrArgs; + +#ifdef _WIN32 + + for (QString arg : execArgs) { + hostFxrArgs.push_back(arg.utf16()); + } + +#else + + QList execArgs8bit; + for (QString arg : execArgs) { + execArgs8bit.push_back(arg.toLocal8Bit()); + } + for (QByteArray arg : execArgs8bit) { + hostFxrArgs.push_back(arg); + } + +#endif + + hostfxr_main_ptr hostfxr_main = nullptr; + +#ifdef _WIN32 + + HMODULE dll = LoadLibraryA(qPrintable(runContext.hostFxrContext.hostFxrLib)); + if(dll == nullptr) { + qCritical("Couldn't load lib at %s", qPrintable(runContext.hostFxrContext.hostFxrLib)); + return LoadHostFxrResult::Failed; + } + + hostfxr_main = reinterpret_cast(GetProcAddress(dll, "hostfxr_main")); + +#else + + void* dll = dlopen(qPrintable(runContext.hostFxrContext.hostFxrLib), RTLD_NOW | RTLD_LOCAL); + if(dll == nullptr) { + qCritical("Couldn't load lib at %s", qPrintable(runContext.hostFxrContext.hostFxrLib)); + return LoadHostFxrResult::Failed; + } + + hostfxr_main = reinterpret_cast(dlsym(dll, "hostfxr_main")); + +#endif + + if(hostfxr_main == nullptr) { + qCritical("Couldn't load 'hostfxr_main' from %s", qPrintable(runContext.hostFxrContext.hostFxrLib)); + return -1; + } + + int result = hostfxr_main(static_cast(hostFxrArgs.size()), &hostFxrArgs[0]); + +#ifdef _WIN32 + FreeLibrary(dll); +#else + dlclose(dll); +#endif + + return result; +} diff --git a/src/native/QmlNet/Hosting/CoreHost.h b/src/native/QmlNet/Hosting/CoreHost.h new file mode 100644 index 00000000..1ee83c15 --- /dev/null +++ b/src/native/QmlNet/Hosting/CoreHost.h @@ -0,0 +1,44 @@ +#ifndef COREHOST_H +#define COREHOST_H + +#include +#include +#include +#include + +#ifdef _WIN32 +#define CORECLR_DOTNET_EXE_NAME "dotnet.exe" +#else +#define CORECLR_DOTNET_EXE_NAME "dotnet" +#endif + +class CoreHost : public QObject +{ + Q_OBJECT +public: + struct HostFxrContext { + bool success; + QString dotnetRoot; + QString hostFxrLib; + }; + struct RunContext { + HostFxrContext hostFxrContext; + QString entryPoint; + QString managedExe; + QList args; + }; + + enum LoadHostFxrResult + { + Loaded, + AlreadyLoaded, + Failed + }; + typedef int (*runCallback)(QGuiApplication* app, QQmlApplicationEngine* engine); + + static QList getPotientialDotnetRoots(); + static HostFxrContext findHostFxr(); + static int run(QGuiApplication& app, QQmlApplicationEngine& engine, runCallback runCallback, RunContext runContext); +}; + +#endif diff --git a/src/native/QmlNet/Hosting/coreclrhost.h b/src/native/QmlNet/Hosting/coreclrhost.h new file mode 100644 index 00000000..1392fe68 --- /dev/null +++ b/src/native/QmlNet/Hosting/coreclrhost.h @@ -0,0 +1,80 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// +// APIs for hosting CoreCLR +// + +#ifndef CORECLR_HOST_H +#define CORECLR_HOST_H + +#ifdef _WIN32 +typedef unsigned short CORECLR_CHAR_TYPE; +#else +typedef char CORECLR_CHAR_TYPE; +#endif + +// For each hosting API, we define a function prototype and a function pointer +// The prototype is useful for implicit linking against the dynamic coreclr +// library and the pointer for explicit dynamic loading (dlopen, LoadLibrary) +#define CORECLR_HOSTING_API(function, ...) \ + extern "C" int function(__VA_ARGS__); \ + typedef int (*function##_ptr)(__VA_ARGS__) + +CORECLR_HOSTING_API(coreclr_initialize, + const char* exePath, + const char* appDomainFriendlyName, + int propertyCount, + const char** propertyKeys, + const char** propertyValues, + void** hostHandle, + unsigned int* domainId); + +CORECLR_HOSTING_API(coreclr_shutdown, + void* hostHandle, + unsigned int domainId); + +CORECLR_HOSTING_API(coreclr_shutdown_2, + void* hostHandle, + unsigned int domainId, + int* latchedExitCode); + +CORECLR_HOSTING_API(coreclr_create_delegate, + void* hostHandle, + unsigned int domainId, + const char* entryPointAssemblyName, + const char* entryPointTypeName, + const char* entryPointMethodName, + void** delegate); + +CORECLR_HOSTING_API(coreclr_execute_assembly, + void* hostHandle, + unsigned int domainId, + int argc, + const char** argv, + const char* managedAssemblyPath, + unsigned int* exitCode); + +// hostfxr +CORECLR_HOSTING_API(hostfxr_get_native_search_directories, + const int argc, + const char* argv[], + char buffer[], + int buffer_size, + int* required_buffer_size); + +CORECLR_HOSTING_API(hostfxr_main_startupinfo, + const int argc, + const char* argv[], + const char* host_path, + const char* dotnet_root, + const char* app_path); + +CORECLR_HOSTING_API(hostfxr_main, + const int argc, + const CORECLR_CHAR_TYPE* argv[]); + +#undef CORECLR_HOSTING_API + +#endif // CORECLR_HOST_H diff --git a/src/native/QmlNet/QmlNet/qml/NetTestHelper.cpp b/src/native/QmlNet/QmlNet/qml/NetTestHelper.cpp index 39023de3..764d1b16 100644 --- a/src/native/QmlNet/QmlNet/qml/NetTestHelper.cpp +++ b/src/native/QmlNet/QmlNet/qml/NetTestHelper.cpp @@ -5,7 +5,7 @@ extern "C" { Q_DECL_EXPORT void net_test_helper_runQml(QQmlApplicationEngineContainer* qmlEngineContainer, LPWSTR qml) { - QQmlComponent component(qmlEngineContainer->qmlEngine.data()); + QQmlComponent component(qmlEngineContainer->qmlEngine); QString qmlString = QString::fromUtf16((const char16_t*)qml); component.setData(qmlString.toUtf8(), QUrl()); QObject *object = component.create(); diff --git a/src/native/QmlNet/QmlNet/qml/QGuiApplication.cpp b/src/native/QmlNet/QmlNet/qml/QGuiApplication.cpp index 720a0b57..f1af9b11 100644 --- a/src/native/QmlNet/QmlNet/qml/QGuiApplication.cpp +++ b/src/native/QmlNet/QmlNet/qml/QGuiApplication.cpp @@ -14,27 +14,32 @@ void GuiThreadContextTriggerCallback::trigger() { extern "C" { -Q_DECL_EXPORT QGuiApplicationContainer* qguiapplication_create(NetVariantListContainer* argsContainer) { - +Q_DECL_EXPORT QGuiApplicationContainer* qguiapplication_create(NetVariantListContainer* argsContainer, QGuiApplication* existingApp) { QGuiApplicationContainer* result = new QGuiApplicationContainer(); - // Build our args - if(argsContainer != nullptr) { - QSharedPointer args = argsContainer->list; - for(int x = 0; x < args->count(); x++) { - QByteArray arg = args->get(x)->getString().toLatin1(); - result->args.append(arg); - char* cstr = nullptr; - cstr = new char [arg.size()+1]; - strcpy(cstr, arg.data()); - result->argsPointer.push_back(cstr); - } - result->argCount = result->args.size(); + if (existingApp != nullptr) { + result->ownsGuiApp = false; + result->guiApp = existingApp; } else { - result->argCount = 0; + result->ownsGuiApp = true; + // Build our args + if(argsContainer != nullptr) { + QSharedPointer args = argsContainer->list; + for(int x = 0; x < args->count(); x++) { + QByteArray arg = args->get(x)->getString().toLatin1(); + result->args.append(arg); + char* cstr = nullptr; + cstr = new char [arg.size()+1]; + strcpy(cstr, arg.data()); + result->argsPointer.push_back(cstr); + } + result->argCount = result->args.size(); + } else { + result->argCount = 0; + } + result->guiApp = new QGuiApplication(result->argCount, &result->argsPointer[0], 0); } - result->guiApp = QSharedPointer(new QGuiApplication(result->argCount, &result->argsPointer[0], 0)); result->callback = QSharedPointer(new GuiThreadContextTriggerCallback()); return result; @@ -44,6 +49,10 @@ Q_DECL_EXPORT void qguiapplication_destroy(QGuiApplicationContainer* container) for (auto i : container->argsPointer) { delete i; } + container->callback.clear(); + if(container->ownsGuiApp) { + delete container->guiApp; + } delete container; } @@ -63,4 +72,8 @@ Q_DECL_EXPORT void qguiapplication_exit(QGuiApplicationContainer* container, int container->guiApp->exit(returnCode); } +Q_DECL_EXPORT QGuiApplication* qguiapplication_internalPointer(QGuiApplicationContainer* container) { + return container->guiApp; +} + } diff --git a/src/native/QmlNet/QmlNet/qml/QGuiApplication.h b/src/native/QmlNet/QmlNet/qml/QGuiApplication.h index cce7e739..e0ea6902 100644 --- a/src/native/QmlNet/QmlNet/qml/QGuiApplication.h +++ b/src/native/QmlNet/QmlNet/qml/QGuiApplication.h @@ -20,7 +20,8 @@ struct QGuiApplicationContainer { int argCount; QList args; std::vector argsPointer; - QSharedPointer guiApp; + QGuiApplication* guiApp; + bool ownsGuiApp; QSharedPointer callback; }; diff --git a/src/native/QmlNet/QmlNet/qml/QQmlApplicationEngine.cpp b/src/native/QmlNet/QmlNet/qml/QQmlApplicationEngine.cpp index 3050a2d4..e065c06d 100644 --- a/src/native/QmlNet/QmlNet/qml/QQmlApplicationEngine.cpp +++ b/src/native/QmlNet/QmlNet/qml/QQmlApplicationEngine.cpp @@ -12,19 +12,34 @@ static int netValueTypeNumber = 0; extern "C" { -Q_DECL_EXPORT QQmlApplicationEngineContainer* qqmlapplicationengine_create() { - QSharedPointer qmlEngine = QSharedPointer(new QQmlApplicationEngine()); +Q_DECL_EXPORT QQmlApplicationEngineContainer* qqmlapplicationengine_create(QQmlApplicationEngine* existingEngine) { + bool ownsEngine = true; + QQmlApplicationEngine* engine = nullptr; - QV4::ExecutionEngine* v4Engine = QQmlEnginePrivate::getV4Engine(qmlEngine.data()); + if (existingEngine != nullptr) { + engine = existingEngine; + ownsEngine = false; + } else { + engine = new QQmlApplicationEngine(); + ownsEngine = true; + } + + QV4::ExecutionEngine* v4Engine = QQmlEnginePrivate::getV4Engine(engine); QV4::Scope scope(v4Engine); QV4::ScopedObject net(scope, v4Engine->memoryManager->allocObject()); v4Engine->globalObject->defineDefaultProperty("Net", net);; - return new QQmlApplicationEngineContainer{qmlEngine}; + return new QQmlApplicationEngineContainer{ + engine, + ownsEngine + }; } Q_DECL_EXPORT void qqmlapplicationengine_destroy(QQmlApplicationEngineContainer* container) { + if(container->ownsEngine) { + delete container->qmlEngine; + } delete container; } @@ -179,4 +194,8 @@ Q_DECL_EXPORT void qqmlapplicationengine_addImportPath(QQmlApplicationEngineCont container->qmlEngine->addImportPath(pathString); } +Q_DECL_EXPORT QQmlApplicationEngine* qqmlapplicationengine_internalPointer(QQmlApplicationEngineContainer* container) { + return container->qmlEngine; +} + } diff --git a/src/native/QmlNet/QmlNet/qml/QQmlApplicationEngine.h b/src/native/QmlNet/QmlNet/qml/QQmlApplicationEngine.h index 96f40bc8..88a10671 100644 --- a/src/native/QmlNet/QmlNet/qml/QQmlApplicationEngine.h +++ b/src/native/QmlNet/QmlNet/qml/QQmlApplicationEngine.h @@ -3,10 +3,10 @@ #include #include -#include struct QQmlApplicationEngineContainer { - QSharedPointer qmlEngine; + QQmlApplicationEngine* qmlEngine; + bool ownsEngine; }; #endif // NET_QQMLAPPLICATIONENGINE_H diff --git a/src/net/Qml.Net/Host.cs b/src/net/Qml.Net/Host.cs new file mode 100644 index 00000000..c0e1b053 --- /dev/null +++ b/src/net/Qml.Net/Host.cs @@ -0,0 +1,41 @@ +using System; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Qml.Net +{ + public static class Host + { + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int NativeRunCallbackDelegate(IntPtr app, IntPtr engine); + + public delegate int NetRunCallbackDelegate(); + + public static int Run(string[] args, Func action) + { + if (args.Length < 3) + { + throw new Exception("Args is invalid, must contain three entries which are pointers to native types."); + } + + var appPtr = new IntPtr((long)ulong.Parse(args[0])); + var enginePtr = new IntPtr((long)ulong.Parse(args[1])); + var callbackPtr = new IntPtr((long)ulong.Parse(args[2])); + + using (var app = new QGuiApplication(appPtr)) + { + using (var engine = new QQmlApplicationEngine(enginePtr)) + { + var runCallback = new NetRunCallbackDelegate(() => + { + var callback = Marshal.GetDelegateForFunctionPointer(callbackPtr); + // ReSharper disable AccessToDisposedClosure + return callback.Invoke(app.InternalPointer, engine.InternalPointer); + // ReSharper restore AccessToDisposedClosure + }); + return action(args.Skip(2).ToArray(), app, engine, runCallback); + } + } + } + } +} \ No newline at end of file diff --git a/src/net/Qml.Net/QGuiApplication.cs b/src/net/Qml.Net/QGuiApplication.cs index 167b470e..b59af33b 100644 --- a/src/net/Qml.Net/QGuiApplication.cs +++ b/src/net/Qml.Net/QGuiApplication.cs @@ -33,6 +33,18 @@ namespace Qml.Net SynchronizationContext.SetSynchronizationContext(new QtSynchronizationContext(this)); } + internal QGuiApplication(IntPtr existingApp) + :base(CreateFromExisting(existingApp)) + { + TriggerDelegate triggerDelegate = Trigger; + _triggerHandle = GCHandle.Alloc(triggerDelegate); + + Interop.QGuiApplication.AddTriggerCallback(Handle, Marshal.GetFunctionPointerForDelegate(triggerDelegate)); + + _oldSynchronizationContext = SynchronizationContext.Current; + SynchronizationContext.SetSynchronizationContext(new QtSynchronizationContext(this)); + } + public int Exec() { return Interop.QGuiApplication.Exec(Handle); @@ -62,6 +74,8 @@ namespace Qml.Net Interop.QGuiApplication.RequestTrigger(Handle); } + internal IntPtr InternalPointer => Interop.QGuiApplication.InternalPointer(Handle); + private void Trigger() { Action action; @@ -79,6 +93,11 @@ namespace Qml.Net _triggerHandle.Free(); } + private static IntPtr CreateFromExisting(IntPtr app) + { + return Interop.QGuiApplication.Create(IntPtr.Zero, app); + } + private static IntPtr Create(List args) { if (args == null) @@ -101,7 +120,7 @@ namespace Qml.Net strings.Add(variant); } } - return Interop.QGuiApplication.Create(strings.Handle); + return Interop.QGuiApplication.Create(strings.Handle, IntPtr.Zero); } } @@ -127,7 +146,7 @@ namespace Qml.Net internal interface IQGuiApplicationInterop { [NativeSymbol(Entrypoint = "qguiapplication_create")] - IntPtr Create(IntPtr args); + IntPtr Create(IntPtr args, IntPtr existingApp); [NativeSymbol(Entrypoint = "qguiapplication_destroy")] void Destroy(IntPtr app); @@ -139,5 +158,7 @@ namespace Qml.Net void RequestTrigger(IntPtr app); [NativeSymbol(Entrypoint = "qguiapplication_exit")] void Exit(IntPtr app, int returnCode); + [NativeSymbol(Entrypoint = "qguiapplication_internalPointer")] + IntPtr InternalPointer(IntPtr app); } } \ No newline at end of file diff --git a/src/net/Qml.Net/QQmlApplicationEngine.cs b/src/net/Qml.Net/QQmlApplicationEngine.cs index 76fab75a..bc2f9c35 100644 --- a/src/net/Qml.Net/QQmlApplicationEngine.cs +++ b/src/net/Qml.Net/QQmlApplicationEngine.cs @@ -10,7 +10,13 @@ namespace Qml.Net public sealed class QQmlApplicationEngine : BaseDisposable { public QQmlApplicationEngine() - :base(Interop.QQmlApplicationEngine.Create()) + :base(Interop.QQmlApplicationEngine.Create(IntPtr.Zero)) + { + + } + + internal QQmlApplicationEngine(IntPtr existingEngine) + :base(Interop.QQmlApplicationEngine.Create(existingEngine)) { } @@ -24,6 +30,13 @@ namespace Qml.Net { Interop.QQmlApplicationEngine.LoadData(Handle, data); } + + public void AddImportPath(string path) + { + Interop.QQmlApplicationEngine.AddImportPath(Handle, path); + } + + internal IntPtr InternalPointer => Interop.QQmlApplicationEngine.InternalPointer(Handle); public static int RegisterType(string uri, int versionMajor = 1, int versionMinor = 0) { @@ -49,11 +62,6 @@ namespace Qml.Net InteropBehaviors.RegisterQmlInteropBehavior(new MvvmQmlInteropBehavior(), false); } - public void AddImportPath(string path) - { - Interop.QQmlApplicationEngine.AddImportPath(Handle, path); - } - protected override void DisposeUnmanaged(IntPtr ptr) { Interop.QQmlApplicationEngine.Destroy(ptr); @@ -63,7 +71,7 @@ namespace Qml.Net internal interface IQQmlApplicationEngine { [NativeSymbol(Entrypoint = "qqmlapplicationengine_create")] - IntPtr Create(); + IntPtr Create(IntPtr existingEngine); [NativeSymbol(Entrypoint = "qqmlapplicationengine_destroy")] void Destroy(IntPtr engine); @@ -78,5 +86,8 @@ namespace Qml.Net [NativeSymbol(Entrypoint = "qqmlapplicationengine_addImportPath")] void AddImportPath(IntPtr engine, [MarshalAs(UnmanagedType.LPWStr), CallerFree]string path); + + [NativeSymbol(Entrypoint = "qqmlapplicationengine_internalPointer")] + IntPtr InternalPointer(IntPtr app); } } \ No newline at end of file