aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMiguel Costa <miguel.costa@qt.io>2023-05-17 17:55:58 +0200
committerMiguel Costa <miguel.costa@qt.io>2023-06-12 11:03:29 +0000
commit36f17aa80578620338705905b5a73166faf92307 (patch)
tree393d09b81fd182c165e7780bbf6bf66812294738
parent5a1753affbc82caf6a6c55c6a7a56d63f465d87b (diff)
Implement native/managed adapter
The adapter module provides low-level interoperability services for native applications to be able to call into managed code. These services include, among other things, obtaining function pointers to call into .NET methods, subscribing to .NET events, and passing native types as implementations of .NET interfaces. Change-Id: Ia7c0173a46ea3035bfe3e252f4006884f39223ad Reviewed-by: Joerg Bornemann <joerg.bornemann@qt.io>
-rw-r--r--include/qdotnetadapter.h301
-rw-r--r--include/qdotnetref.h109
-rw-r--r--src/Qt.DotNet.Adapter/Adapter.Delegates.cs137
-rw-r--r--src/Qt.DotNet.Adapter/Adapter.Events.cs97
-rw-r--r--src/Qt.DotNet.Adapter/Adapter.Methods.cs183
-rw-r--r--src/Qt.DotNet.Adapter/Adapter.Objects.cs155
-rw-r--r--src/Qt.DotNet.Adapter/Adapter.Test.cs139
-rw-r--r--src/Qt.DotNet.Adapter/Adapter.cs93
-rw-r--r--src/Qt.DotNet.Adapter/CodeGenerator.cs643
-rw-r--r--src/Qt.DotNet.Adapter/EventRelay.cs79
-rw-r--r--src/Qt.DotNet.Adapter/InterfaceProxy.cs102
-rw-r--r--src/Qt.DotNet.Adapter/ObjectMarshaler.cs56
-rw-r--r--src/Qt.DotNet.Adapter/Parameter.cs127
-rw-r--r--src/Qt.DotNet.Adapter/Qt.DotNet.Adapter.csproj28
-rw-r--r--src/Qt.DotNet.Adapter/StringMarshaler.cs47
15 files changed, 2296 insertions, 0 deletions
diff --git a/include/qdotnetadapter.h b/include/qdotnetadapter.h
new file mode 100644
index 0000000..2191ecb
--- /dev/null
+++ b/include/qdotnetadapter.h
@@ -0,0 +1,301 @@
+/***************************************************************************************************
+ Copyright (C) 2023 The Qt Company Ltd.
+ SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+***************************************************************************************************/
+
+#pragma once
+
+#include "qdotnethost.h"
+
+#ifdef __GNUC__
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wconversion"
+#endif
+#include <QCoreApplication>
+#include <QDir>
+#include <QList>
+#include <QMutexLocker>
+#include <QString>
+#ifdef __GNUC__
+# pragma GCC diagnostic pop
+#endif
+
+class QDotNetRef;
+
+class QDotNetAdapter final
+{
+private:
+ QDotNetAdapter() = default;
+ QDotNetAdapter(const QDotNetAdapter &) = delete;
+ QDotNetAdapter(QDotNetAdapter &&) = delete;
+ QDotNetAdapter &operator=(const QDotNetAdapter &) = delete;
+ QDotNetAdapter &operator=(QDotNetAdapter &&) = delete;
+
+ ~QDotNetAdapter()
+ {
+ defaultHost.unload();
+ }
+
+ static void init()
+ {
+ if (instance().isValid())
+ return;
+ init(QDir(QCoreApplication::applicationDirPath())
+ .filePath(defaultDllName), defaultAssemblyName, defaultTypeName);
+ }
+
+public:
+ static void init(QDotNetHost *externalHost)
+ {
+ if (instance().isValid())
+ return;
+ init(QDir(QCoreApplication::applicationDirPath())
+ .filePath(defaultDllName), defaultAssemblyName, defaultTypeName, externalHost);
+ }
+
+ static void init(const QString &assemblyPath, const QString &assemblyName,
+ const QString &typeName, QDotNetHost *externalHost = nullptr)
+ {
+ if (instance().isValid())
+ return;
+
+ const QString typeFullName = QString("%1, %2").arg(typeName, assemblyName);
+ const QString delegateName = QString("%1+Delegates+%3, %2")
+ .arg(typeName, assemblyName, "%1");
+
+ QDotNetHost *host = nullptr;
+ if (externalHost != nullptr)
+ host = externalHost;
+ else
+ host = &instance().defaultHost;
+ if (!host->load()) {
+ qCritical() << "QDotNetAdapter: error loading host";
+ return;
+ }
+
+#define QDOTNETADAPTER_DELEGATE(d) \
+ instance().d, assemblyPath, typeFullName, \
+ QString(#d).sliced(2), \
+ delegateName.arg(QString(#d).sliced(2))
+
+ host->resolveFunction(QDOTNETADAPTER_DELEGATE(fnLoadAssembly));
+ host->resolveFunction(QDOTNETADAPTER_DELEGATE(fnResolveStaticMethod));
+ host->resolveFunction(QDOTNETADAPTER_DELEGATE(fnResolveConstructor));
+ host->resolveFunction(QDOTNETADAPTER_DELEGATE(fnResolveInstanceMethod));
+ host->resolveFunction(QDOTNETADAPTER_DELEGATE(fnResolveSafeMethod));
+ host->resolveFunction(QDOTNETADAPTER_DELEGATE(fnAddEventHandler));
+ host->resolveFunction(QDOTNETADAPTER_DELEGATE(fnRemoveEventHandler));
+ host->resolveFunction(QDOTNETADAPTER_DELEGATE(fnRemoveAllEventHandlers));
+ host->resolveFunction(QDOTNETADAPTER_DELEGATE(fnAddObjectRef));
+ host->resolveFunction(QDOTNETADAPTER_DELEGATE(fnFreeDelegateRef));
+ host->resolveFunction(QDOTNETADAPTER_DELEGATE(fnFreeObjectRef));
+ host->resolveFunction(QDOTNETADAPTER_DELEGATE(fnFreeTypeRef));
+ host->resolveFunction(QDOTNETADAPTER_DELEGATE(fnAddInterfaceProxy));
+ host->resolveFunction(QDOTNETADAPTER_DELEGATE(fnSetInterfaceMethod));
+ host->resolveFunction(QDOTNETADAPTER_DELEGATE(fnStats));
+ host->resolveFunction(QDOTNETADAPTER_DELEGATE(fnGetObject));
+
+#undef QDOTNETADAPTER_DELEGATE
+
+ instance().host = host;
+ }
+
+ static QDotNetAdapter &instance()
+ {
+ static QDotNetAdapter adapter;
+ return adapter;
+ }
+
+ bool isValid() const { return host != nullptr; }
+
+public:
+ bool loadAssembly(const QString &assemblyName) const
+ {
+ init();
+ return fnLoadAssembly(assemblyName);
+ }
+
+ void *resolveStaticMethod(const QString &typeName, const QString &methodName,
+ const QList<QDotNetParameter> &params) const
+ {
+ init();
+ if (typeName.isEmpty() || methodName.isEmpty())
+ return nullptr;
+ return fnResolveStaticMethod(typeName, methodName,
+ static_cast<qint32>(params.size()), params);
+ }
+
+ void *resolveConstructor(const QList<QDotNetParameter> &params) const
+ {
+ init();
+ return fnResolveConstructor(static_cast<qint32>(params.size()), params);
+ }
+
+ void *resolveInstanceMethod(const QDotNetRef &objectRef, const QString &methodName,
+ const QList<QDotNetParameter> &params) const
+ {
+ init();
+ if (QtDotNet::isNull(objectRef) || methodName.isEmpty())
+ return nullptr;
+ return fnResolveInstanceMethod(
+ objectRef, methodName, static_cast<qint32>(params.size()), params);
+ }
+
+ using EventCallback = void(QDOTNETFUNCTION_CALLTYPE *)(void *, void *, void *, void *);
+
+ void *resolveSafeMethod(void *funcPtr, const QList<QDotNetParameter> &params) const
+ {
+ init();
+ if (!funcPtr)
+ return nullptr;
+ return fnResolveSafeMethod(
+ funcPtr, static_cast<qint32>(params.size()), params);
+ }
+
+ void addEventHandler(const QDotNetRef &eventSource, const QString &eventName,
+ void *context, EventCallback eventCallback) const
+ {
+ init();
+ if (QtDotNet::isNull(eventSource) || eventName.isEmpty() || !eventCallback)
+ return;
+ fnAddEventHandler(eventSource, eventName, context, eventCallback);
+ }
+
+ void addEventHandler(QDotNetRef &eventSource, const QString &eventName,
+ EventCallback eventCallback) const
+ {
+ init();
+ if (QtDotNet::isNull(eventSource) || eventName.isEmpty() || !eventCallback)
+ return;
+ fnAddEventHandler(eventSource, eventName, &eventSource, eventCallback);
+ }
+
+ void removeEventHandler(const QDotNetRef &eventSource, const QString &eventName,
+ void *context) const
+ {
+ init();
+ if (QtDotNet::isNull(eventSource) || eventName.isEmpty())
+ return;
+ fnRemoveEventHandler(eventSource, eventName, context);
+ }
+
+ void removeEventHandler(QDotNetRef &eventSource, const QString &eventName) const
+ {
+ init();
+ if (QtDotNet::isNull(eventSource) || eventName.isEmpty())
+ return;
+ fnRemoveEventHandler(eventSource, eventName, &eventSource);
+ }
+
+ void removeAllEventHandlers(const QDotNetRef &eventSource) const
+ {
+ init();
+ if (QtDotNet::isNull(eventSource))
+ return;
+ fnRemoveAllEventHandlers(eventSource);
+ }
+
+ void *addObjectRef(const QDotNetRef &objectRef, bool weakRef = false) const
+ {
+ init();
+ if (QtDotNet::isNull(objectRef))
+ return nullptr;
+ return fnAddObjectRef(objectRef, weakRef);
+ }
+
+ void freeDelegateRef(void *delegateRef) const
+ {
+ init();
+ if (!delegateRef)
+ return;
+ fnFreeDelegateRef(delegateRef);
+ }
+
+ void freeObjectRef(const QDotNetRef &objectRef) const
+ {
+ init();
+ if (QtDotNet::isNull(objectRef))
+ return;
+ fnFreeObjectRef(objectRef);
+ }
+
+ void freeTypeRef(const QString &typeName) const
+ {
+ init();
+ if (typeName.isEmpty())
+ return;
+ fnFreeTypeRef(typeName);
+ }
+
+ void *addInterfaceProxy(const QString &interfaceName) const
+ {
+ init();
+ if (interfaceName.isEmpty())
+ return nullptr;
+ return fnAddInterfaceProxy(interfaceName);
+ }
+
+ void setInterfaceMethod(const QDotNetRef &obj, const QString &methodName,
+ const QList<QDotNetParameter> &params, void *callback, void *cleanUp, void *context) const
+ {
+ init();
+ if (QtDotNet::isNull(obj) || methodName.isEmpty() || !callback)
+ return;
+ return fnSetInterfaceMethod(obj, methodName, static_cast<qint32>(params.size()), params,
+ callback, cleanUp, context);
+ }
+
+ struct Stats
+ {
+ qint32 refCount;
+ qint32 staticCount;
+ qint32 eventCount;
+ bool allZero() const
+ {
+ return refCount == 0
+ && staticCount == 0
+ && eventCount == 0;
+ }
+ };
+
+ Stats stats() const
+ {
+ Stats s{ };
+ init();
+ fnStats(&s.refCount, &s.staticCount, &s.eventCount);
+ return s;
+ }
+
+ void *object(const QDotNetRef &obj, const QString &path)
+ {
+ init();
+ return fnGetObject(obj, path);
+ }
+
+private:
+ QDotNetHost defaultHost;
+ mutable QDotNetHost *host = nullptr;
+ mutable QDotNetFunction<bool, QString> fnLoadAssembly;
+ mutable QDotNetFunction<void *, QString, QString, qint32, QList<QDotNetParameter>>
+ fnResolveStaticMethod;
+ mutable QDotNetFunction<void *, qint32, QList<QDotNetParameter>> fnResolveConstructor;
+ mutable QDotNetFunction<void *, QDotNetRef, QString, qint32, QList<QDotNetParameter>>
+ fnResolveInstanceMethod;
+ mutable QDotNetFunction<void *, void *, qint32, QList<QDotNetParameter>> fnResolveSafeMethod;
+ mutable QDotNetFunction<void, QDotNetRef, QString, void *, EventCallback> fnAddEventHandler;
+ mutable QDotNetFunction<void, QDotNetRef, QString, void *> fnRemoveEventHandler;
+ mutable QDotNetFunction<void, QDotNetRef> fnRemoveAllEventHandlers;
+ mutable QDotNetFunction<void *, QDotNetRef, bool> fnAddObjectRef;
+ mutable QDotNetFunction<void, void *> fnFreeDelegateRef;
+ mutable QDotNetFunction<void, QDotNetRef> fnFreeObjectRef;
+ mutable QDotNetFunction<void, QString> fnFreeTypeRef;
+ mutable QDotNetFunction<void *, QString> fnAddInterfaceProxy;
+ mutable QDotNetFunction<void, QDotNetRef, QString, qint32, QList<QDotNetParameter>,
+ void *, void *, void *> fnSetInterfaceMethod;
+ mutable QDotNetFunction<void, qint32 *, qint32 *, qint32 *> fnStats;
+ mutable QDotNetFunction<void *, QDotNetRef, QString> fnGetObject;
+
+ static inline const QString defaultDllName = QLatin1String("Qt.DotNet.Adapter.dll");
+ static inline const QString defaultAssemblyName = QLatin1String("Qt.DotNet.Adapter");
+ static inline const QString defaultTypeName = QLatin1String("Qt.DotNet.Adapter");
+};
diff --git a/include/qdotnetref.h b/include/qdotnetref.h
new file mode 100644
index 0000000..fff2e06
--- /dev/null
+++ b/include/qdotnetref.h
@@ -0,0 +1,109 @@
+/***************************************************************************************************
+ Copyright (C) 2023 The Qt Company Ltd.
+ SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+***************************************************************************************************/
+
+#pragma once
+
+#include "qdotnetadapter.h"
+
+class QDotNetRef
+{
+public:
+ static inline const QString &FullyQualifiedTypeName = QStringLiteral("System.Object");
+
+ const void *gcHandle() const { return objectRef; }
+ bool isValid() const { return gcHandle() != nullptr; }
+
+ template<typename T, std::enable_if_t<std::is_base_of_v<QDotNetRef, T>, bool> = true>
+ T cast(bool copy = false)
+ {
+ T newObj(nullptr);
+ if (copy)
+ newObj.copyFrom(*this);
+ else
+ newObj.moveFrom(*this);
+ return newObj;
+ }
+
+ QDotNetRef(const void *objectRef = nullptr)
+ : objectRef(objectRef)
+ {}
+
+ QDotNetRef(const QDotNetRef &cpySrc)
+ {
+ copyFrom(cpySrc);
+ }
+
+ QDotNetRef(QDotNetRef &&movSrc) noexcept
+ {
+ moveFrom(movSrc);
+ }
+
+ virtual ~QDotNetRef()
+ {
+ freeObjectRef();
+ }
+
+ QDotNetRef &operator=(const QDotNetRef &cpySrc)
+ {
+ return copyFrom(cpySrc);
+ }
+
+ QDotNetRef &operator=(QDotNetRef &&movSrc) noexcept
+ {
+ return moveFrom(movSrc);
+ }
+
+ template<typename T, std::enable_if_t<std::is_base_of_v<QDotNetRef, T>, bool> = true>
+ class Null
+ {};
+
+protected:
+ static QDotNetAdapter &adapter() { return QDotNetAdapter::instance(); }
+
+ void attach(const void *objectRef)
+ {
+ this->objectRef = objectRef;
+ }
+
+ QDotNetRef &copyFrom(const QDotNetRef &that)
+ {
+ freeObjectRef();
+ if (that.isValid())
+ objectRef = adapter().addObjectRef(that);
+ return *this;
+ }
+
+ QDotNetRef &moveFrom(QDotNetRef &that)
+ {
+ freeObjectRef();
+ objectRef = that.objectRef;
+ that.objectRef = nullptr;
+ return *this;
+ }
+
+private:
+ void freeObjectRef()
+ {
+ if (!isValid())
+ return;
+ adapter().freeObjectRef(*this);
+ objectRef = nullptr;
+ }
+
+ const void *objectRef = nullptr;
+};
+
+template<typename T>
+struct QDotNetOutbound<QDotNetRef::Null<T>, std::enable_if_t<std::is_base_of_v<QDotNetRef, T>>>
+{
+ using SourceType = nullptr_t;
+ using OutboundType = const void*;
+ static inline const QDotNetParameter Parameter =
+ QDotNetParameter(QDotNetTypeOf<T>::TypeName, QDotNetTypeOf<T>::MarshalAs);
+ static OutboundType convert(SourceType dotNetObj)
+ {
+ return nullptr;
+ }
+};
diff --git a/src/Qt.DotNet.Adapter/Adapter.Delegates.cs b/src/Qt.DotNet.Adapter/Adapter.Delegates.cs
new file mode 100644
index 0000000..c3f2b9d
--- /dev/null
+++ b/src/Qt.DotNet.Adapter/Adapter.Delegates.cs
@@ -0,0 +1,137 @@
+/***************************************************************************************************
+ Copyright (C) 2023 The Qt Company Ltd.
+ SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+***************************************************************************************************/
+
+using System.Runtime.InteropServices;
+
+namespace Qt.DotNet
+{
+ public partial class Adapter
+ {
+ /// <summary>
+ /// Delegate types for Adapter public functions.
+ /// </summary>
+ public static class Delegates
+ {
+ [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+ public delegate bool LoadAssembly(
+ [MarshalAs(UnmanagedType.LPWStr)]
+ [In] string assemblyName);
+
+ [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+ public delegate IntPtr ResolveStaticMethod(
+ [MarshalAs(UnmanagedType.LPWStr)]
+ [In] string targetType,
+ [MarshalAs(UnmanagedType.LPWStr)]
+ [In] string methodName,
+ [In] int parameterCount,
+ [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)]
+ [In] Parameter[] parameters);
+
+ [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+ public delegate IntPtr ResolveConstructor(
+ [In] int parameterCount,
+ [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)]
+ [In] Parameter[] parameters);
+
+ [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+ public delegate IntPtr ResolveInstanceMethod(
+ [In] IntPtr objRefPtr,
+ [MarshalAs(UnmanagedType.LPWStr)]
+ [In] string methodName,
+ [In] int parameterCount,
+ [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)]
+ [In] Parameter[] parameters);
+
+ [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+ public delegate IntPtr ResolveSafeMethod(
+ [In] IntPtr funcPtr,
+ [In] int parameterCount,
+ [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]
+ [In] Parameter[] parameters);
+
+ [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+ public delegate void NativeEventHandler(
+ [In] IntPtr context,
+ [MarshalAs(UnmanagedType.LPWStr)]
+ [In] string eventName,
+ [MarshalAs(UnmanagedType.CustomMarshaler,
+ MarshalTypeRef = typeof(ObjectMarshaler))]
+ [In] object sender,
+ [MarshalAs(UnmanagedType.CustomMarshaler,
+ MarshalTypeRef = typeof(ObjectMarshaler))]
+ [In] object args);
+
+ [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+ public delegate void AddEventHandler(
+ [In] IntPtr objRefPtr,
+ [MarshalAs(UnmanagedType.LPWStr)]
+ [In] string eventName,
+ [In] IntPtr context,
+ [In] NativeEventHandler eventHandler);
+
+ [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+ public delegate void RemoveEventHandler(
+ [In] IntPtr objRefPtr,
+ [MarshalAs(UnmanagedType.LPWStr)]
+ [In] string eventName,
+ [In] IntPtr context);
+
+ [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+ public delegate void RemoveAllEventHandlers(
+ [In] IntPtr objRefPtr);
+
+ [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+ public delegate IntPtr AddObjectRef(
+ [In] IntPtr objRefPtr,
+ [In] bool weakRef);
+
+ [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+ public delegate void FreeDelegateRef(
+ [In] IntPtr delRefPtr);
+
+ [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+ public delegate void FreeObjectRef(
+ [In] IntPtr objRefPtr);
+
+ [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+ public delegate void FreeTypeRef(
+ [MarshalAs(UnmanagedType.LPWStr)]
+ [In] string typeName);
+
+ [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+ [return: MarshalAs(UnmanagedType.CustomMarshaler,
+ MarshalTypeRef = typeof(ObjectMarshaler))]
+ public delegate InterfaceProxy AddInterfaceProxy(
+ [MarshalAs(UnmanagedType.LPWStr)]
+ [In] string interfaceName);
+
+ [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+ public delegate void SetInterfaceMethod(
+ [MarshalAs(UnmanagedType.CustomMarshaler,
+ MarshalTypeRef = typeof(ObjectMarshaler))]
+ [In] InterfaceProxy proxy,
+ [MarshalAs(UnmanagedType.LPWStr)]
+ [In] string methodName,
+ [In] int parameterCount,
+ [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)]
+ [In] Parameter[] parameters,
+ [In] IntPtr callbackPtr,
+ [In] IntPtr cleanUpPtr,
+ [In] IntPtr context);
+
+ [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+ public delegate IntPtr GetObject
+ ([In] IntPtr objRefPtr, [MarshalAs(UnmanagedType.LPWStr)][In] string path);
+
+#if DEBUG || TESTS
+ [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+ public delegate void Stats(
+ [Out] out int refCount,
+ [Out] out int staticCount,
+ [Out] out int eventCount);
+#endif
+ }
+ }
+}
diff --git a/src/Qt.DotNet.Adapter/Adapter.Events.cs b/src/Qt.DotNet.Adapter/Adapter.Events.cs
new file mode 100644
index 0000000..777121d
--- /dev/null
+++ b/src/Qt.DotNet.Adapter/Adapter.Events.cs
@@ -0,0 +1,97 @@
+/***************************************************************************************************
+ Copyright (C) 2023 The Qt Company Ltd.
+ SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+***************************************************************************************************/
+
+namespace Qt.DotNet
+{
+ public partial class Adapter
+ {
+ /// <summary>
+ /// Subscribe to notifications of given event from given object
+ /// </summary>
+ /// <param name="objRefPtr">Native reference to target object.</param>
+ /// <param name="eventName">Name of event</param>
+ /// <param name="context">Opaque pointer to context data</param>
+ /// <param name="eventHandler">Pointer to callback function</param>
+ /// <exception cref="ArgumentException"></exception>
+ public static void AddEventHandler(
+ IntPtr objRefPtr,
+ string eventName,
+ IntPtr context,
+ Delegates.NativeEventHandler eventHandler)
+ {
+#if DEBUG
+ // Compile-time signature check of delegate vs. method
+ _ = new Delegates.AddEventHandler(AddEventHandler);
+#endif
+ var objRef = GetObjectRefFromPtr(objRefPtr);
+ if (objRef == null)
+ throw new ArgumentException("Invalid object reference", nameof(objRefPtr));
+ var eventInfo = objRef.Target.GetType().GetEvent(eventName)
+ ?? throw new ArgumentException($"Event '{eventName}' not found", nameof(eventName));
+
+ if (Events.TryGetValue((objRef, eventName, context), out var eventRelay)) {
+ eventRelay.Handler = eventHandler;
+ } else {
+ eventRelay = new EventRelay(objRef.Target, eventInfo, context, eventHandler);
+ Events.TryAdd((objRef, eventName, context), eventRelay);
+ }
+ }
+
+ /// <summary>
+ /// Unsubscribe to notifications of given event from given object
+ /// </summary>
+ /// <param name="objRefPtr">Native reference to target object.</param>
+ /// <param name="eventName">Name of event</param>
+ /// <param name="context">Opaque pointer to context data</param>
+ /// <exception cref="ArgumentException"></exception>
+ public static void RemoveEventHandler(
+ IntPtr objRefPtr,
+ string eventName,
+ IntPtr context)
+ {
+#if DEBUG
+ // Compile-time signature check of delegate vs. method
+ _ = new Delegates.RemoveEventHandler(RemoveEventHandler);
+#endif
+ var objRef = GetObjectRefFromPtr(objRefPtr);
+ if (objRef == null)
+ throw new ArgumentException("Invalid object reference", nameof(objRefPtr));
+ RemoveEventHandler(objRef, eventName, context);
+ }
+
+ private static void RemoveEventHandler(
+ ObjectRef objRef,
+ string eventName,
+ IntPtr context)
+ {
+ if (Events.TryRemove((objRef, eventName, context), out var evRelay))
+ evRelay.Enabled = false;
+ }
+
+ /// <summary>
+ /// Unsubscribe to notifications of all events from given object
+ /// </summary>
+ /// <param name="objRefPtr">Native reference to target object.</param>
+ /// <exception cref="ArgumentException"></exception>
+ public static void RemoveAllEventHandlers(IntPtr objRefPtr)
+ {
+#if DEBUG
+ // Compile-time signature check of delegate vs. method
+ _ = new Delegates.RemoveAllEventHandlers(RemoveAllEventHandlers);
+#endif
+ var objRef = GetObjectRefFromPtr(objRefPtr);
+ if (objRef == null)
+ throw new ArgumentException("Invalid object reference", nameof(objRefPtr));
+ RemoveAllEventHandlers(objRef);
+ }
+
+ private static void RemoveAllEventHandlers(ObjectRef objRef)
+ {
+ var evHandlers = Events.Where(x => x.Key.Source == objRef);
+ foreach (var evHandler in evHandlers)
+ RemoveEventHandler(objRef, evHandler.Key.Name, evHandler.Key.Context);
+ }
+ }
+}
diff --git a/src/Qt.DotNet.Adapter/Adapter.Methods.cs b/src/Qt.DotNet.Adapter/Adapter.Methods.cs
new file mode 100644
index 0000000..47db8a6
--- /dev/null
+++ b/src/Qt.DotNet.Adapter/Adapter.Methods.cs
@@ -0,0 +1,183 @@
+/***************************************************************************************************
+ Copyright (C) 2023 The Qt Company Ltd.
+ SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+***************************************************************************************************/
+
+using System.Collections.Concurrent;
+using System.Diagnostics;
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+namespace Qt.DotNet
+{
+ public partial class Adapter
+ {
+ public static IntPtr ResolveStaticMethod(
+ string typeName,
+ string methodName,
+ int parameterCount,
+ Parameter[] parameters)
+ {
+#if DEBUG
+ // Compile-time signature check of delegate vs. method
+ _ = new Delegates.ResolveStaticMethod(ResolveStaticMethod);
+#endif
+ var type = Type.GetType(typeName)
+ ?? throw new ArgumentException($"Type '{typeName}' not found", nameof(typeName));
+
+ var sigTypes = parameters
+ .Skip(1)
+ .Select((x, i) => x.GetParameterType()
+ ?? throw new ArgumentException($"Type not found [{i}]", nameof(parameters)))
+ .ToArray();
+
+ var method = type.GetMethod(
+ methodName, BindingFlags.Public | BindingFlags.Static, sigTypes)
+ ?? throw new ArgumentException(
+ $"Method '{methodName}' not found", nameof(methodName));
+
+ if (DelegatesByMethod.TryGetValue((type, method), out var objMethod))
+ return objMethod.FuncPtr;
+
+ var delegateType = CodeGenerator.CreateDelegateTypeForMethod(method, parameters)
+ ?? throw new ArgumentException("Error getting method delegate", nameof(methodName));
+
+ var methodDelegate = Delegate.CreateDelegate(delegateType, method, false)
+ ?? throw new ArgumentException("Error getting method delegate", nameof(methodName));
+
+ var methodHandle = GCHandle.Alloc(methodDelegate);
+ var methodFuncPtr = Marshal.GetFunctionPointerForDelegate(methodDelegate);
+
+ var delegateRef = new DelegateRef(methodHandle, methodFuncPtr);
+ DelegateRefs.TryAdd(methodFuncPtr, (type, method, delegateRef));
+ DelegatesByMethod.TryAdd((type, method), delegateRef);
+ return methodFuncPtr;
+ }
+
+ public static IntPtr ResolveConstructor(
+ int parameterCount,
+ Parameter[] parameters)
+ {
+#if DEBUG
+ // Compile-time signature check of delegate vs. method
+ _ = new Delegates.ResolveConstructor(ResolveConstructor);
+#endif
+ if (parameters == null || parameters.Length == 0)
+ throw new ArgumentException("Null or empty param list", nameof(parameters));
+
+ if (parameters[0].IsVoid)
+ throw new ArgumentException("Constructor cannot return void", nameof(parameters));
+
+ var type = parameters[0].GetParameterType()
+ ?? throw new ArgumentException("Return type not found", nameof(parameters));
+
+ var paramTypes = parameters
+ .Skip(1)
+ .Select((x, i) => x.GetParameterType()
+ ?? throw new ArgumentException($"Type not found [{i}]", nameof(parameters)))
+ .ToArray();
+
+ var ctor = type.GetConstructor(paramTypes)
+ ?? throw new ArgumentException("Constructor not found", nameof(parameters));
+
+ var ctorProxy = CodeGenerator.CreateProxyMethodForCtor(ctor, parameters)
+ ?? throw new ArgumentException("Error getting ctor delegate", nameof(parameters));
+
+ var delegateType = CodeGenerator.CreateDelegateTypeForMethod(ctorProxy, parameters)
+ ?? throw new ArgumentException("Error getting ctor delegate", nameof(parameters));
+
+ var methodDelegate = Delegate.CreateDelegate(delegateType, ctorProxy, false)
+ ?? throw new ArgumentException("Error getting ctor delegate", nameof(parameters));
+
+ var methodHandle = GCHandle.Alloc(methodDelegate);
+ var methodFuncPtr = Marshal.GetFunctionPointerForDelegate(methodDelegate);
+
+ var delegateRef = new DelegateRef(methodHandle, methodFuncPtr);
+ DelegateRefs.TryAdd(methodFuncPtr, (type, ctor, delegateRef));
+ DelegatesByMethod.TryAdd((type, ctor), delegateRef);
+ return methodFuncPtr;
+ }
+
+ public static IntPtr ResolveInstanceMethod(
+ IntPtr objRefPtr,
+ string methodName,
+ int parameterCount,
+ Parameter[] parameters)
+ {
+#if DEBUG
+ // Compile-time signature check of delegate vs. method
+ _ = new Delegates.ResolveInstanceMethod(ResolveInstanceMethod);
+#endif
+
+ var objRef = GetObjectRefFromPtr(objRefPtr);
+ if (objRef == null)
+ throw new ArgumentException("Invalid object reference", nameof(objRefPtr));
+ var obj = objRef.Target;
+ var type = obj.GetType();
+ var parameterTypes = parameters
+ .Skip(1)
+ .Select((x, i) => x.GetParameterType()
+ ?? throw new ArgumentException($"Type not found [{i}]", nameof(parameters)))
+ .ToArray();
+
+ var method = type.GetMethod(
+ methodName, BindingFlags.Public | BindingFlags.Instance, parameterTypes)
+ ?? throw new ArgumentException(
+ $"Method '{methodName}' not found", nameof(methodName));
+
+ if (DelegatesByMethod.TryGetValue((obj, method), out var objMethod))
+ return objMethod.FuncPtr;
+
+ var delegateType = CodeGenerator.CreateDelegateTypeForMethod(method, parameters)
+ ?? throw new ArgumentException("Error getting method delegate", nameof(methodName));
+
+ var methodDelegate = Delegate.CreateDelegate(delegateType, obj, method, false)
+ ?? throw new ArgumentException("Error getting method delegate", nameof(methodName));
+
+ var methodHandle = GCHandle.Alloc(methodDelegate);
+ var methodFuncPtr = Marshal.GetFunctionPointerForDelegate(methodDelegate);
+
+ var delegateRef = new DelegateRef(methodHandle, methodFuncPtr);
+ DelegateRefs.TryAdd(methodFuncPtr, (obj, method, delegateRef));
+ DelegatesByMethod.TryAdd((obj, method), delegateRef);
+ return methodFuncPtr;
+ }
+
+ public static IntPtr ResolveSafeMethod(
+ IntPtr funcPtr,
+ int parameterCount,
+ Parameter[] parameters)
+ {
+#if DEBUG
+ // Compile-time signature check of delegate vs. method
+ _ = new Delegates.ResolveSafeMethod(ResolveSafeMethod);
+#endif
+ var delegateHandle = DelegateRefs.Values
+ .Where(x => x.Ref.FuncPtr == funcPtr)
+ .Select(x => x.Ref.Handle)
+ .FirstOrDefault();
+
+ var funcDelegate = delegateHandle.Target as Delegate;
+#if DEBUG
+ Debug.Assert(funcDelegate != null, nameof(funcDelegate) + " is null");
+#endif
+ if (SafeMethods.TryGetValue(funcDelegate.Method, out var delegateRef))
+ return delegateRef.FuncPtr;
+
+ var method = CodeGenerator.CreateSafeMethod(funcDelegate.Method);
+ var delegateType = CodeGenerator.CreateDelegateTypeForMethod(method, parameters);
+ var methodDelegate = Delegate.CreateDelegate(delegateType, method);
+ var methodHandle = GCHandle.Alloc(methodDelegate);
+ var methodFuncPtr = Marshal.GetFunctionPointerForDelegate(methodDelegate);
+
+ delegateRef = new DelegateRef(methodHandle, methodFuncPtr);
+ SafeMethods.TryAdd(funcDelegate.Method, delegateRef);
+ return methodFuncPtr;
+ }
+
+ private static ConcurrentDictionary
+ <MethodBase, DelegateRef> SafeMethods
+ { get; } = new();
+
+ }
+}
diff --git a/src/Qt.DotNet.Adapter/Adapter.Objects.cs b/src/Qt.DotNet.Adapter/Adapter.Objects.cs
new file mode 100644
index 0000000..8692ea3
--- /dev/null
+++ b/src/Qt.DotNet.Adapter/Adapter.Objects.cs
@@ -0,0 +1,155 @@
+/***************************************************************************************************
+ Copyright (C) 2023 The Qt Company Ltd.
+ SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+***************************************************************************************************/
+
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+namespace Qt.DotNet
+{
+ public partial class Adapter
+ {
+ internal static IntPtr GetRefPtrToObject(object obj, bool weakRef = false)
+ {
+ var objHandle = GCHandle.Alloc(obj, weakRef ? GCHandleType.Weak : GCHandleType.Normal);
+ var objRefPtr = GCHandle.ToIntPtr(objHandle);
+ ObjectRefs.TryAdd(objRefPtr, new ObjectRef(objHandle));
+ return objRefPtr;
+ }
+
+ internal static ObjectRef GetObjectRefFromPtr(IntPtr objRefPtr)
+ {
+ if (objRefPtr == IntPtr.Zero)
+ throw new ArgumentNullException(nameof(objRefPtr));
+ if (!ObjectRefs.TryGetValue(objRefPtr, out var objRef))
+ return null;
+ if (!objRef.IsValid)
+ throw new ObjectDisposedException(nameof(objRefPtr));
+ return objRef;
+ }
+
+ /// <summary>
+ /// Create a new reference (GCHandle) to the given object
+ /// </summary>
+ /// <param name="objRefPtr">Native reference to target object.</param>
+ /// <param name="weakRef">'true' to create a weak ref.; 'false' otherwise (default)</param>
+ /// <returns>Native object reference</returns>
+ /// <exception cref="ArgumentException"></exception>
+ public static IntPtr AddObjectRef(IntPtr objRefPtr, bool weakRef = false)
+ {
+#if DEBUG
+ // Compile-time signature check of delegate vs. method
+ _ = new Delegates.AddObjectRef(AddObjectRef);
+#endif
+ var objRef = GetObjectRefFromPtr(objRefPtr);
+ if (objRef == null)
+ throw new ArgumentException("Invalid object reference", nameof(objRefPtr));
+ var obj = objRef.Target;
+ var objHandle = GCHandle.Alloc(
+ obj, weakRef ? GCHandleType.Weak : GCHandleType.Normal);
+ var newObjRefPtr = GCHandle.ToIntPtr(objHandle);
+ ObjectRefs.TryAdd(newObjRefPtr, new ObjectRef(objHandle));
+ return newObjRefPtr;
+ }
+
+ /// <summary>
+ /// Release object reference, as well as any associated instance method and event references
+ /// </summary>
+ /// <param name="objRefPtr">Native reference to target object.</param>
+ /// <returns>'true' if object ref. was released successfully; 'false' otherwise</returns>
+ /// <exception cref="ArgumentException"></exception>
+ public static void FreeObjectRef(IntPtr objRefPtr)
+ {
+#if DEBUG
+ // Compile-time signature check of delegate vs. method
+ _ = new Delegates.FreeObjectRef(FreeObjectRef);
+#endif
+ if (!ObjectRefs.TryRemove(objRefPtr, out var objRef))
+ throw new ArgumentException("Invalid object reference", nameof(objRefPtr));
+ RemoveAllEventHandlers(objRef);
+
+ var liveObjects = ObjectRefs.Values.Select(x => x.Target).ToList();
+ var isLive = liveObjects.Any(x => x.Equals(objRef.Target));
+ if (!isLive) {
+ var deadMethods = DelegatesByMethod
+ .Where(x => x.Key.Target.Equals(objRef.Target))
+ .Select(x => x.Value.FuncPtr)
+ .ToList();
+ deadMethods.ForEach(FreeDelegateRef);
+ }
+
+ objRef.Handle.Free();
+ }
+
+ /// <summary>
+ /// Release reference to a type (i.e. instance of System.Type)
+ /// </summary>
+ /// <param name="typeName">Fully qualified name of type</param>
+ /// <exception cref="ArgumentException"></exception>
+ public static void FreeTypeRef(string typeName)
+ {
+#if DEBUG
+ // Compile-time signature check of delegate vs. method
+ _ = new Delegates.FreeTypeRef(FreeTypeRef);
+#endif
+ var type = Type.GetType(typeName)
+ ?? throw new ArgumentException($"Type '{typeName}' not found", nameof(typeName));
+
+ var typeRefs = ObjectRefs
+ .Where(x => x.Value.Target.Equals(type))
+ .ToList();
+ foreach (var typeRef in typeRefs)
+ FreeObjectRef(typeRef.Key);
+
+ var deadMethods = DelegatesByMethod
+ .Where(x => x.Key.Target.Equals(type))
+ .Select(x => x.Value.FuncPtr)
+ .ToList();
+ deadMethods.ForEach(FreeDelegateRef);
+ }
+
+ public static void FreeDelegateRef(IntPtr delRefPtr)
+ {
+#if DEBUG
+ // Compile-time signature check of delegate vs. method
+ _ = new Delegates.FreeDelegateRef(FreeDelegateRef);
+#endif
+ if (!DelegateRefs.TryRemove(delRefPtr, out var delegateRef))
+ return;
+ DelegatesByMethod.TryRemove((delegateRef.Target, delegateRef.Method), out _);
+ delegateRef.Ref.Handle.Free();
+ }
+
+ public static IntPtr GetObject(IntPtr objRefPtr, string path)
+ {
+#if DEBUG
+ // Compile-time signature check of delegate vs. method
+ _ = new Delegates.GetObject(GetObject);
+#endif
+ var memberNames = path.Split('.',
+ StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
+
+ var objRef = GetObjectRefFromPtr(objRefPtr);
+ if (objRef == null)
+ throw new ArgumentException("Invalid object reference", nameof(objRefPtr));
+
+ var obj = objRef.Target;
+ foreach (var memberName in memberNames) {
+ if (obj?.GetType()?.GetMember(memberName) is not MemberInfo[] members)
+ throw new ArgumentException("Invalid object reference", memberName);
+ foreach (var member in members) {
+ if (member is PropertyInfo prop) {
+ obj = prop.GetValue(obj);
+ break;
+ } else if (member is FieldInfo field) {
+ obj = field.GetValue(obj);
+ break;
+ }
+ throw new ArgumentException("Invalid object reference", memberName);
+ }
+ }
+ return GetRefPtrToObject(obj);
+ }
+ }
+}
diff --git a/src/Qt.DotNet.Adapter/Adapter.Test.cs b/src/Qt.DotNet.Adapter/Adapter.Test.cs
new file mode 100644
index 0000000..eebde5e
--- /dev/null
+++ b/src/Qt.DotNet.Adapter/Adapter.Test.cs
@@ -0,0 +1,139 @@
+/***************************************************************************************************
+ Copyright (C) 2023 The Qt Company Ltd.
+ SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+***************************************************************************************************/
+
+using System.Diagnostics;
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+namespace Qt.DotNet
+{
+ public partial class Adapter
+ {
+#if DEBUG || TESTS
+ /// <summary>
+ /// Get current ref counters. For debug/test purposes.
+ /// </summary>
+ /// <param name="refCount">Object ref. count</param>
+ /// <param name="staticCount">Static method ref. count</param>
+ /// <param name="eventCount">Event ref. count</param>
+ public static void Stats(out int refCount, out int staticCount, out int eventCount)
+ {
+ refCount = ObjectRefs.Count;
+ staticCount = DelegateRefs.Count;
+ eventCount = Events.Count;
+ }
+
+ /// <summary>
+ /// Built-in test
+ /// </summary>
+ /// <returns></returns>
+ public static bool Test()
+ {
+ // TO-DO: revise this
+
+ LoadAssembly("FooLib.dll");
+
+ var ctorPtr = ResolveConstructor(1, new[] { new Parameter("FooLib.Foo, FooLib") });
+ var ctorPtr2 = ResolveConstructor(1, new[] { new Parameter("FooLib.Foo, FooLib") });
+
+ var ctor = GetMethod(ctorPtr) as ConstructorInfo;
+ Debug.Assert(ctor != null, nameof(ctor) + " is null");
+ var objRef = GetRefPtrToObject(ctor.Invoke(Array.Empty<object>()));
+
+ var getTypePtr = ResolveInstanceMethod(
+ objRef, "GetType", 1, new[] { new Parameter("System.Type") });
+ var getBarPtr = ResolveInstanceMethod(
+ objRef, "get_Bar", 1, new[] { new Parameter(UnmanagedType.LPWStr) });
+ var setBarPtr = ResolveInstanceMethod(
+ objRef, "set_Bar", 1, new[] { new(), new Parameter(UnmanagedType.LPWStr) });
+
+ AddEventHandler(
+ objRef,
+ "PropertyChanged",
+ new IntPtr(42),
+ TestNativeEventHandler);
+
+ for (int i = 0; i < 1000; ++i) {
+ var str = GetMethod(getBarPtr)
+ .Invoke(GetObjectRefFromPtr(objRef).Target, Array.Empty<object>()) as string;
+ str += "hello";
+ GetMethod(setBarPtr)
+ .Invoke(GetObjectRefFromPtr(objRef).Target, new object[] { str });
+ }
+
+ RemoveAllEventHandlers(objRef);
+ FreeObjectRef(objRef);
+ FreeTypeRef("FooLib.Foo, FooLib");
+
+ bool ok = Events.IsEmpty;
+ ok = ok && ObjectRefs.IsEmpty;
+ ok = ok && DelegateRefs.IsEmpty;
+ return ok;
+ }
+
+ private static void TestNativeEventHandler(
+ IntPtr context,
+ string eventName,
+ object senderObj,
+ object argsObj)
+ {
+ var senderRef = GetRefPtrToObject(senderObj);
+ var argsRef = GetRefPtrToObject(argsObj);
+
+ var getTypePtr = ResolveInstanceMethod(
+ argsRef, "GetType", 1, new[] { new Parameter("System.Type") });
+ if (eventName == "PropertyChanged") {
+ var typeObj = GetMethod(getTypePtr)
+ .Invoke(GetObjectRefFromPtr(argsRef).Target, Array.Empty<object>());
+ var typeRef = GetRefPtrToObject(typeObj);
+
+ var getFullNamePtr = ResolveInstanceMethod(
+ typeRef, "get_FullName", 1, new[] { new Parameter(UnmanagedType.LPWStr) });
+ var argsTypeName = GetMethod(getFullNamePtr)
+ .Invoke(GetObjectRefFromPtr(typeRef).Target, Array.Empty<object>())
+ as string;
+
+ if (argsTypeName == "System.ComponentModel.PropertyChangedEventArgs") {
+
+ var propChangeRef = AddObjectRef(argsRef);
+
+ var getPropertyNamePtr = ResolveInstanceMethod(
+ propChangeRef, "get_PropertyName", 1,
+ new[]
+ {
+ new Parameter(UnmanagedType.LPWStr)
+ });
+ var propName = GetMethod(getPropertyNamePtr)
+ .Invoke(GetObjectRefFromPtr(propChangeRef).Target, Array.Empty<object>())
+ as string;
+
+ if (propName == "Bar") {
+ var getBarPtr = ResolveInstanceMethod(
+ senderRef, "get_Bar", 1, new[] { new Parameter(UnmanagedType.LPWStr) });
+ var str = GetMethod(getBarPtr)
+ .Invoke(GetObjectRefFromPtr(senderRef).Target, Array.Empty<object>())
+ as string;
+ Debug.Assert(str != null, nameof(str) + " is null");
+ Console.WriteLine($"BAR CHANGED!!! [{str.Length / "hello".Length}x hello]");
+ }
+ FreeObjectRef(typeRef);
+ FreeObjectRef(propChangeRef);
+ }
+ }
+ FreeObjectRef(argsRef);
+ FreeObjectRef(senderRef);
+ }
+
+ private static MethodBase GetMethod(IntPtr funcPtr)
+ {
+ var methods = DelegateRefs
+ .Where(x => x.Value.Ref.FuncPtr == funcPtr)
+ .Select(x => x.Value.Method);
+ return methods.First();
+
+ }
+#endif
+ }
+}
diff --git a/src/Qt.DotNet.Adapter/Adapter.cs b/src/Qt.DotNet.Adapter/Adapter.cs
new file mode 100644
index 0000000..b26d89e
--- /dev/null
+++ b/src/Qt.DotNet.Adapter/Adapter.cs
@@ -0,0 +1,93 @@
+/***************************************************************************************************
+ Copyright (C) 2023 The Qt Company Ltd.
+ SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+***************************************************************************************************/
+
+using System.Collections.Concurrent;
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+namespace Qt.DotNet
+{
+ /// <summary>
+ /// Provides access to managed types, allowing native code to obtain references to, and call
+ /// constructors, static methods and instance methods.
+ /// </summary>
+ public partial class Adapter
+ {
+ /// <summary>
+ /// Loads a .NET assembly into memory
+ /// </summary>
+ /// <param name="assemblyName">Name of the assembly or path to the assembly DLL.</param>
+ /// <returns>'true' if load was successful; 'false' otherwise</returns>
+ public static bool LoadAssembly(string assemblyName)
+ {
+#if DEBUG
+ // Compile-time signature check of delegate vs. method
+ _ = new Delegates.LoadAssembly(LoadAssembly);
+#endif
+ try {
+ Assembly.Load(assemblyName);
+ return true;
+ } catch (Exception) {
+ }
+
+ if (File.Exists(Path.GetFullPath(assemblyName))) {
+ try {
+ Assembly.LoadFile(Path.GetFullPath(assemblyName));
+ return true;
+ } catch (Exception) {
+ }
+ }
+
+ if (File.Exists(Path.GetFullPath($"{assemblyName}.dll"))) {
+ try {
+ Assembly.LoadFile(Path.GetFullPath($"{assemblyName}.dll"));
+ return true;
+ } catch (Exception) {
+ }
+ }
+ return false;
+ }
+
+ internal class DelegateRef
+ {
+ public GCHandle Handle { get; }
+ public bool IsValid => Handle.IsAllocated;
+ public Delegate Target => Handle.Target as Delegate;
+ public IntPtr FuncPtr { get; }
+ public DelegateRef(GCHandle handle, IntPtr funcPtr)
+ {
+ Handle = handle;
+ FuncPtr = funcPtr;
+ }
+ }
+
+ internal class ObjectRef
+ {
+ public GCHandle Handle { get; }
+ public object Target => Handle.Target;
+ public bool IsValid => Handle.IsAllocated && Handle.Target != null;
+ public ObjectRef(GCHandle handle)
+ {
+ Handle = handle;
+ }
+ }
+
+ private static ConcurrentDictionary
+ <IntPtr, ObjectRef> ObjectRefs
+ { get; } = new();
+
+ private static ConcurrentDictionary
+ <IntPtr, (object Target, MethodBase Method, DelegateRef Ref)> DelegateRefs
+ { get; } = new();
+
+ private static ConcurrentDictionary
+ <(object Target, MethodBase Method), DelegateRef> DelegatesByMethod
+ { get; } = new();
+
+ private static ConcurrentDictionary
+ <(ObjectRef Source, string Name, IntPtr Context), EventRelay> Events
+ { get; } = new();
+ }
+}
diff --git a/src/Qt.DotNet.Adapter/CodeGenerator.cs b/src/Qt.DotNet.Adapter/CodeGenerator.cs
new file mode 100644
index 0000000..5922960
--- /dev/null
+++ b/src/Qt.DotNet.Adapter/CodeGenerator.cs
@@ -0,0 +1,643 @@
+/***************************************************************************************************
+ Copyright (C) 2023 The Qt Company Ltd.
+ SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+***************************************************************************************************/
+
+using System.Collections.Concurrent;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
+using System.Reflection.Emit;
+using System.Runtime.InteropServices;
+
+namespace Qt.DotNet
+{
+ using DelegateIndex = ConcurrentDictionary<(MethodBase, Parameter[]), Type>;
+ using ProxyIndex = ConcurrentDictionary<(MethodBase, Parameter[]), MethodInfo>;
+ using IIndexer = IEqualityComparer<(MethodBase method, Parameter[] parameters)>;
+
+ public class SafeReturn<T>
+ {
+ public T Value { get; init; } = default;
+ public Exception Exception { get; init; }
+ }
+
+ /// <summary>
+ /// Functions that generate code at run-time, needed to support native interop.
+ /// </summary>
+ internal static class CodeGenerator
+ {
+ public static MethodInfo CreateSafeMethod(MethodInfo unsafeMethod)
+ {
+#if TESTS || DEBUG
+ Debug.Assert(unsafeMethod.DeclaringType != null, "unsafeMethod.DeclaringType is null");
+#endif
+ var typeGen = ModuleGen.DefineType(
+ UniqueName("Safe", unsafeMethod.DeclaringType?.Name, unsafeMethod.Name),
+ TypeAttributes.Public, typeof(object));
+ var returnType = unsafeMethod.ReturnType;
+ var returnTypeIsVoid = returnType == typeof(void);
+ var paramTypes = unsafeMethod.GetParameters()
+ .Select(x => x.ParameterType)
+ .Prepend(typeof(object))
+ .ToArray();
+
+ Type safeReturnType = returnTypeIsVoid
+ ? typeof(SafeReturn<bool>)
+ : typeof(SafeReturn<>).MakeGenericType(returnType);
+
+ var safeReturnCtor = safeReturnType.GetConstructor(Array.Empty<Type>());
+ var safeReturnSetValue = safeReturnType.GetMethod("set_Value");
+ var safeReturnSetException = safeReturnType.GetMethod("set_Exception");
+#if TESTS || DEBUG
+ Debug.Assert(safeReturnCtor != null, nameof(safeReturnCtor) + " is null");
+ Debug.Assert(safeReturnSetValue != null, nameof(safeReturnSetValue) + " is null");
+ Debug.Assert(safeReturnSetException != null,
+ nameof(safeReturnSetException) + " is null");
+#endif
+ var safeMethod = typeGen.DefineMethod("SafeInvoke",
+ MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Static,
+ safeReturnType, paramTypes);
+ var code = safeMethod.GetILGenerator();
+
+ code.DeclareLocal(safeReturnType);
+ code.DeclareLocal(returnTypeIsVoid ? typeof(bool) : returnType);
+ code.DeclareLocal(typeof(Exception));
+
+ // {0} = new SafeReturn<T>();
+ code.Emit(OpCodes.Newobj, safeReturnCtor);
+ code.Emit(OpCodes.Stloc_0);
+
+ //try
+ code.BeginExceptionBlock();
+ {
+ // this = arg0
+ if (!unsafeMethod.IsStatic)
+ code.Emit(OpCodes.Ldarg_0);
+
+ // Load arguments into stack
+ for (int paramIdx = 1; paramIdx < paramTypes.Length; ++paramIdx) {
+ if (paramIdx == 1)
+ code.Emit(OpCodes.Ldarg_1);
+ else if (paramIdx == 2)
+ code.Emit(OpCodes.Ldarg_2);
+ else if (paramIdx == 3)
+ code.Emit(OpCodes.Ldarg_3);
+ else
+ code.Emit(OpCodes.Ldarg_S, paramIdx + 1);
+ }
+
+ // Invoke method
+ if (!returnTypeIsVoid) {
+ // {1} = unsafeMethod([...]);
+ code.Emit(OpCodes.Call, unsafeMethod);
+ code.Emit(OpCodes.Stloc_1);
+ // {0}.Value = {1};
+ code.Emit(OpCodes.Ldloc_0);
+ code.Emit(OpCodes.Ldloc_1);
+ code.Emit(OpCodes.Call, safeReturnSetValue);
+ } else {
+ // unsafeMethod([...]);
+ code.Emit(OpCodes.Call, unsafeMethod);
+ }
+ }
+ // ... } catch (Exception [0]) { ...
+ code.BeginCatchBlock(typeof(Exception));
+ {
+ // {2} = [0];
+ code.Emit(OpCodes.Stloc_2);
+ code.Emit(OpCodes.Ldloc_0);
+ code.Emit(OpCodes.Ldloc_2);
+ code.Emit(OpCodes.Call, safeReturnSetException);
+ }
+ // ... }
+ code.EndExceptionBlock();
+
+ // Return [0]
+ code.Emit(OpCodes.Ldloc_0);
+ code.Emit(OpCodes.Ret);
+
+ var safeInvokeType = typeGen.CreateType()
+ ?? throw new TypeAccessException("Error creating safe invoke");
+ var safeInvoke = safeInvokeType.GetMethod("SafeInvoke");
+ return safeInvoke;
+ }
+
+ public static Type CreateInterfaceProxyType(Type interfaceType)
+ {
+ if (InterfaceProxyTypes.TryGetValue(interfaceType, out var proxyType))
+ return proxyType;
+
+ var typeGen = ModuleGen.DefineType(UniqueName("Proxy", interfaceType.Name),
+ TypeAttributes.Public, typeof(InterfaceProxy), new[] { interfaceType });
+
+ var interfaceImpl_CleanUp = typeof(InterfaceProxy).GetMethod("CleanUp");
+#if TEST || DEBUG
+ Debug.Assert(interfaceImpl_CleanUp != null, nameof(interfaceImpl_CleanUp) + " is null");
+#endif
+ foreach (var method in interfaceType.GetMethods()) {
+ var parameterInfos = method.GetParameters();
+ var paramTypes = parameterInfos
+ .Select(x => x.ParameterType)
+ .ToArray();
+ var callbackParamTypes = paramTypes
+ .Prepend(typeof(ulong))
+ .Prepend(typeof(IntPtr))
+ .ToArray();
+ var callbackParameters = callbackParamTypes
+ .Prepend(method.ReturnType)
+ .Select(t => new Parameter(t))
+ .ToArray();
+
+ var delegateGen = typeGen.DefineNestedType(
+ UniqueName(method.Name, "Delegate"),
+ TypeAttributes.Sealed | TypeAttributes.NestedPublic,
+ typeof(MulticastDelegate));
+ var callbackInvoke = InitDelegateType(delegateGen, callbackParameters);
+
+ var callbackGen = typeGen.DefineField(
+ UniqueName(method.Name, "Callback"), delegateGen, FieldAttributes.Public);
+ var nativeCallbackGen = typeGen.DefineField(
+ $"Native_{callbackGen.Name}", typeof(Delegate), FieldAttributes.Public);
+ var cleanUpPtr = typeGen.DefineField(
+ $"CleanUp_{callbackGen.Name}", typeof(IntPtr), FieldAttributes.Public);
+ var contextGen = typeGen.DefineField(
+ $"Context_{callbackGen.Name}", typeof(IntPtr), FieldAttributes.Public);
+ var countGen = typeGen.DefineField(
+ $"Count_{callbackGen.Name}", typeof(ulong), FieldAttributes.Public);
+
+ var methodGen = typeGen.DefineMethod(method.Name,
+ MethodAttributes.Public
+ | MethodAttributes.Final
+ | MethodAttributes.HideBySig
+ | MethodAttributes.NewSlot
+ | MethodAttributes.Virtual,
+ method.ReturnType, paramTypes);
+ var code = methodGen.GetILGenerator();
+
+ // ++Count;
+ code.Emit(OpCodes.Ldarg_0);
+ code.Emit(OpCodes.Ldarg_0);
+ code.Emit(OpCodes.Ldfld, countGen);
+ code.Emit(OpCodes.Ldc_I4_1);
+ code.Emit(OpCodes.Conv_I8);
+ code.Emit(OpCodes.Add);
+ code.Emit(OpCodes.Stfld, countGen);
+
+ // NativeCallback.Invoke(
+ code.Emit(OpCodes.Ldarg_0);
+ code.Emit(OpCodes.Ldfld, nativeCallbackGen);
+ // context,
+ code.Emit(OpCodes.Ldarg_0);
+ code.Emit(OpCodes.Ldfld, contextGen);
+ // count,
+ code.Emit(OpCodes.Ldarg_0);
+ code.Emit(OpCodes.Ldfld, countGen);
+ // Load method call arguments into stack
+ for (int paramIdx = 0; paramIdx < paramTypes.Length; ++paramIdx) {
+ if (paramIdx == 0)
+ code.Emit(OpCodes.Ldarg_1);
+ else if (paramIdx == 1)
+ code.Emit(OpCodes.Ldarg_2);
+ else if (paramIdx == 2)
+ code.Emit(OpCodes.Ldarg_3);
+ else
+ code.Emit(OpCodes.Ldarg_S, paramIdx + 1);
+ }
+ // ); //NativeCallback.Invoke
+ code.Emit(OpCodes.Callvirt, callbackInvoke);
+
+ // CleanUp.Invoke(
+ code.Emit(OpCodes.Ldarg_0);
+ // cleanUpPtr,
+ code.Emit(OpCodes.Ldarg_0);
+ code.Emit(OpCodes.Ldfld, cleanUpPtr);
+ // context,
+ code.Emit(OpCodes.Ldarg_0);
+ code.Emit(OpCodes.Ldfld, contextGen);
+ // count,
+ code.Emit(OpCodes.Ldarg_0);
+ code.Emit(OpCodes.Ldfld, countGen);
+ // ); //CleanUp.Invoke
+ code.Emit(OpCodes.Callvirt, interfaceImpl_CleanUp);
+
+ // return <ret>;
+ code.Emit(OpCodes.Ret);
+
+ // Generate nested type
+ delegateGen.CreateType();
+ }
+ proxyType = typeGen.CreateType();
+ InterfaceProxyTypes.TryAdd(interfaceType, proxyType);
+ return proxyType;
+ }
+
+ public static Type CreateDelegateType(string methodName, Parameter[] parameters)
+ {
+ // Generate dynamic Delegate sub-type
+ var typeGen = ModuleGen.DefineType(
+ UniqueName(methodName, "Delegate"),
+ TypeAttributes.Sealed | TypeAttributes.Public,
+ typeof(MulticastDelegate));
+
+ InitDelegateType(typeGen, parameters);
+
+ // Get generated type
+ var delegateType = typeGen.CreateType()
+ ?? throw new TypeAccessException("Error creating dynamic delegate type");
+
+ return delegateType;
+ }
+
+ /// <summary>
+ /// Generate a delegate type that matches the signature of a given method.
+ /// </summary>
+ /// <remarks>
+ /// This is used to configure marshaling for incoming native interop calls.
+ /// </remarks>
+ /// <param name="method">Information on the managed method being called</param>
+ /// <param name="parameters">Marshaling configuration of each parameter</param>
+ /// <returns>Generated delegate type</returns>
+ /// <exception cref="TypeAccessException"/>
+ public static Type CreateDelegateTypeForMethod(MethodInfo method, Parameter[] parameters)
+ {
+#if TESTS || DEBUG
+ Debug.Assert(method.GetParameters().Length == parameters.Length - 1);
+ Debug.Assert(method.DeclaringType != null, "method.DeclaringType is null");
+ Debug.Assert(method.ReturnType.IsAssignableTo(parameters[0].GetParameterType())
+ || method.ReturnType.IsAssignableFrom(parameters[0].GetParameterType()));
+ Debug.Assert(method.GetParameters().Zip(parameters.Skip(1))
+ .All(x => x.First.ParameterType.IsAssignableTo(x.Second.GetParameterType())
+ || x.First.ParameterType.IsAssignableFrom(x.Second.GetParameterType())));
+#endif
+ // Check if already in cache
+ if (DelegateTypes.TryGetValue((method, parameters), out Type delegateType))
+ return delegateType;
+
+ // Generate dynamic Delegate sub-type
+ var typeGen = ModuleGen.DefineType(
+ UniqueName(method.DeclaringType.Name, method.Name),
+ TypeAttributes.Sealed | TypeAttributes.Public,
+ typeof(MulticastDelegate));
+
+ // Generate constructor for Delegate sub-type
+ var ctorGen = typeGen.DefineConstructor(
+ MethodAttributes.HideBySig | MethodAttributes.Public,
+ CallingConventions.Standard,
+ new[] { typeof(object), typeof(IntPtr) });
+ ctorGen.SetImplementationFlags(MethodImplAttributes.CodeTypeMask);
+
+ // Delegate matches return and param types
+ Type returnType = method.ReturnType;
+ var paramInfos = method.GetParameters();
+ var paramTypes = paramInfos
+ .Select(p => p.ParameterType)
+ .ToArray();
+
+ // Generate Invoke() method
+ var invokeGen = typeGen.DefineMethod("Invoke",
+ MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.Public,
+ returnType, paramTypes);
+ invokeGen.SetImplementationFlags(MethodImplAttributes.CodeTypeMask);
+
+ // Generate return parameter (if return type is not void)
+ var returnParam = parameters[0];
+ if (!returnParam.IsVoid) {
+ var returnGen = invokeGen.DefineParameter(0, ParameterAttributes.Retval, "");
+ SetMarshalAs(returnGen, returnParam);
+ }
+
+ // Generate method parameters
+ for (int i = 0; i < parameters.Length - 1; i++) {
+ var param = parameters[i + 1];
+ var paramInfo = paramInfos[i];
+ var paramGen = invokeGen.DefineParameter(
+ i + 1, ParameterAttributes.None, paramInfo.Name);
+ if (param.MarshalAs != 0)
+ SetMarshalAs(paramGen, param);
+ if (param.IsIn)
+ SetIn(paramGen);
+ if (param.IsOut)
+ SetOut(paramGen);
+ }
+
+ // Get generated type
+ delegateType = typeGen.CreateType()
+ ?? throw new TypeAccessException("Error creating dynamic delegate type");
+
+ // Add to cache and return
+ DelegateTypes.TryAdd((method, parameters), delegateType);
+ return delegateType;
+ }
+
+ /// <summary>
+ /// Generate static method that encapsulates a call to a given constructor.
+ /// </summary>
+ /// <remarks>
+ /// This is used to configure marshaling for incoming native interop calls to constructors.
+ /// </remarks>
+ /// <param name="ctor">Constructor information</param>
+ /// <param name="parameters">Marshaling configuration of each parameter</param>
+ /// <returns>Generated method information</returns>
+ /// <exception cref="TypeAccessException"></exception>
+ public static MethodInfo CreateProxyMethodForCtor(
+ ConstructorInfo ctor, Parameter[] parameters)
+ {
+#if TESTS || DEBUG
+ Debug.Assert(ctor.GetParameters().Length == parameters.Length - 1);
+ Debug.Assert(ctor.DeclaringType != null, "ctor.DeclaringType is null");
+ Debug.Assert(ctor.DeclaringType.IsAssignableTo(parameters[0].GetParameterType()));
+ Debug.Assert(ctor.GetParameters().Zip(parameters.Skip(1))
+ .All(x => x.First.ParameterType.IsAssignableTo(x.Second.GetParameterType())));
+#endif
+ // Check if already in cache
+ if (Proxies.TryGetValue((ctor, parameters), out MethodInfo proxy))
+ return proxy;
+
+ // Proxy matches return and param types
+ Type returnType = ctor.DeclaringType;
+ var paramInfos = ctor.GetParameters();
+ var paramTypes = paramInfos
+ .Select(p => p.ParameterType)
+ .ToArray();
+
+ // Generate placeholder type for proxy method
+ var typeGen = ModuleGen.DefineType(
+ UniqueName(returnType.Name, "ctor"),
+ TypeAttributes.Sealed | TypeAttributes.Public,
+ typeof(object));
+
+ // Generate proxy method Create()
+ var proxyGen = typeGen.DefineMethod("Create",
+ MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Static,
+ returnType, paramTypes);
+
+ // Get code generator for proxy method
+ var code = proxyGen.GetILGenerator();
+
+ // Load arguments into stack
+ for (int paramIdx = 0; paramIdx < paramTypes.Length; ++paramIdx) {
+ if (paramIdx == 0)
+ code.Emit(OpCodes.Ldarg_0);
+ else if (paramIdx == 1)
+ code.Emit(OpCodes.Ldarg_1);
+ else if (paramIdx == 2)
+ code.Emit(OpCodes.Ldarg_2);
+ else if (paramIdx == 3)
+ code.Emit(OpCodes.Ldarg_3);
+ else
+ code.Emit(OpCodes.Ldarg_S, paramIdx);
+ }
+
+ // Invoke encapsulated constructor
+ code.Emit(OpCodes.Newobj, ctor);
+
+ // Return newly created object
+ code.Emit(OpCodes.Ret);
+
+ // Get generated type
+ var proxyType = typeGen.CreateType()
+ ?? throw new TypeAccessException("Error creating dynamic ctor proxy");
+
+ // Get generated method
+ proxy = proxyType.GetMethod("Create");
+
+ // Add to cache and return
+ Proxies.TryAdd((ctor, parameters), proxy);
+ return proxy;
+ }
+
+ private static MethodBuilder InitDelegateType(TypeBuilder typeGen, Parameter[] parameters)
+ {
+ // Generate constructor for Delegate sub-type
+ var ctorGen = typeGen.DefineConstructor(
+ MethodAttributes.HideBySig | MethodAttributes.Public,
+ CallingConventions.Standard,
+ new[] { typeof(object), typeof(IntPtr) });
+ ctorGen.SetImplementationFlags(MethodImplAttributes.CodeTypeMask);
+
+ // Delegate matches return and param types
+ Type returnType = parameters[0].GetParameterType();
+ var paramTypes = parameters
+ .Skip(1)
+ .Select(p => p.GetParameterType())
+ .ToArray();
+
+ // Generate Invoke() method
+ var invokeGen = typeGen.DefineMethod("Invoke",
+ MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.Public,
+ returnType, paramTypes);
+ invokeGen.SetImplementationFlags(MethodImplAttributes.CodeTypeMask);
+
+ // Generate return parameter (if return type is not void)
+ var returnParam = parameters[0];
+ if (!returnParam.IsVoid) {
+ var returnGen = invokeGen.DefineParameter(0, ParameterAttributes.Retval, "");
+ SetMarshalAs(returnGen, returnParam);
+ }
+
+ // Generate method parameters
+ for (int i = 0; i < parameters.Length - 1; i++) {
+ var param = parameters[i + 1];
+ var paramGen = invokeGen.DefineParameter(
+ i + 1, ParameterAttributes.None, null);
+ if (param.MarshalAs != 0)
+ SetMarshalAs(paramGen, param);
+ if (param.IsIn)
+ SetIn(paramGen);
+ if (param.IsOut)
+ SetOut(paramGen);
+ }
+
+ return invokeGen;
+ }
+
+ /// <summary>
+ /// [MarshalAs] attribute type
+ /// </summary>
+ private static Type MarshalAs { get; } = typeof(MarshalAsAttribute);
+
+ /// <summary>
+ /// [MarshalAs] attribute constructor
+ /// </summary>
+ private static ConstructorInfo MarshalAsCtor { get; }
+ = MarshalAs.GetConstructor(new[] { typeof(UnmanagedType) });
+
+ // Set parameter [MarshalAs] attribute
+ /// <summary>
+ /// Set the [MarshalAs] attribute for a generated parameter.
+ /// </summary>
+ /// <param name="paramGen">Generated parameter</param>
+ /// <param name="parameter">Marshaling configuration of the parameter</param>
+ private static void SetMarshalAs(ParameterBuilder paramGen, Parameter parameter)
+ {
+ CustomAttributeBuilder paramAttribute;
+ if (parameter.MarshalAs == Parameter.ObjectRef) {
+ paramAttribute = new CustomAttributeBuilder(
+ MarshalAsCtor, new object[] { UnmanagedType.CustomMarshaler },
+ new[]
+ {
+ MarshalAs.GetField("MarshalTypeRef"),
+ MarshalAs.GetField("MarshalCookie")
+ },
+ new object[]
+ {
+ typeof(ObjectMarshaler),
+ parameter.IsWeakRef ? "weak" : "normal"
+ });
+ } else if (parameter.MarshalAs == UnmanagedType.CustomMarshaler) {
+ paramAttribute = new CustomAttributeBuilder(
+ MarshalAsCtor, new object[] { UnmanagedType.CustomMarshaler },
+ new[]
+ {
+ MarshalAs.GetField("MarshalTypeRef"),
+ MarshalAs.GetField("MarshalCookie")
+ },
+ new object[]
+ {
+ Type.GetType(parameter.TypeName),
+ parameter.TypeName
+ });
+ } else if (parameter.MarshalAs != 0) {
+ if (parameter.IsArray) {
+ paramAttribute = new CustomAttributeBuilder(
+ MarshalAsCtor, new object[] { UnmanagedType.LPArray },
+ new[]
+ {
+ MarshalAs.GetField("ArraySubType"),
+ parameter.IsFixedLength
+ ? MarshalAs.GetField("SizeConst")
+ : MarshalAs.GetField("SizeParamIndex")
+ },
+ new object[]
+ {
+ parameter.MarshalAs,
+ parameter.ArrayLength
+ });
+ } else {
+ paramAttribute = new CustomAttributeBuilder(
+ MarshalAsCtor, new object[] { parameter.MarshalAs });
+ }
+ } else {
+ return;
+ }
+ paramGen.SetCustomAttribute(paramAttribute);
+ }
+
+ /// <summary>
+ /// [In] attribute type
+ /// </summary>
+ private static Type In { get; } = typeof(InAttribute);
+
+ /// <summary>
+ /// [In] attribute constructor
+ /// </summary>
+ private static ConstructorInfo InCtor { get; }
+ = In.GetConstructor(Array.Empty<Type>());
+
+ /// <summary>
+ /// Set parameter [In] attribute.
+ /// </summary>
+ /// <param name="paramGen">Marshaling configuration of the parameter</param>
+ private static void SetIn(ParameterBuilder paramGen)
+ {
+ var inAttribute = new CustomAttributeBuilder(InCtor, Array.Empty<object>());
+ paramGen.SetCustomAttribute(inAttribute);
+ }
+
+ /// <summary>
+ /// [Out] attribute type
+ /// </summary>
+ private static Type Out { get; } = typeof(OutAttribute);
+
+ /// <summary>
+ /// [Out] attribute constructor
+ /// </summary>
+ private static ConstructorInfo OutCtor { get; }
+ = Out.GetConstructor(Array.Empty<Type>());
+
+ /// <summary>
+ /// Set parameter [Out] attribute.
+ /// </summary>
+ /// <param name="paramGen">Marshaling configuration of the parameter</param>
+ private static void SetOut(ParameterBuilder paramGen)
+ {
+ var outAttribute = new CustomAttributeBuilder(OutCtor, Array.Empty<object>());
+ paramGen.SetCustomAttribute(outAttribute);
+ }
+
+ /// <summary>
+ /// Generated assembly unique name.
+ /// </summary>
+ private static string UniqueAssemblyName { get; } = UniqueName("InteropDelegates");
+
+ /// <summary>
+ /// Dynamic assembly generator.
+ /// </summary>
+ private static AssemblyBuilder AssemblyGen { get; }
+ = AssemblyBuilder.DefineDynamicAssembly(
+ new AssemblyName(UniqueAssemblyName), AssemblyBuilderAccess.Run);
+
+ /// <summary>
+ /// Dynamic module generator.
+ /// </summary>
+ private static ModuleBuilder ModuleGen { get; }
+ = AssemblyGen.DefineDynamicModule(UniqueAssemblyName);
+
+ /// <summary>
+ /// Delegate type cache.
+ /// </summary>
+ private static DelegateIndex DelegateTypes { get; } = new(new Indexer());
+
+ /// <summary>
+ /// Proxy method cache.
+ /// </summary>
+ private static ProxyIndex Proxies { get; } = new(new Indexer());
+
+ /// <summary>
+ /// Interface proxy type cache.
+ /// </summary>
+ private static ConcurrentDictionary<Type, Type> InterfaceProxyTypes { get; } = new();
+
+ /// <summary>
+ /// Cache custom indexer.
+ /// </summary>
+ private class Indexer : IIndexer
+ {
+ public bool Equals(
+ (MethodBase method, Parameter[] parameters) x,
+ (MethodBase method, Parameter[] parameters) y)
+ {
+ if (x.method != y.method)
+ return false;
+ if (x.parameters.Length != y.parameters.Length)
+ return false;
+ var xyParameters = x.parameters.Zip(y.parameters).ToList();
+ if (xyParameters.Any(xy => xy.First.ParamInfo != xy.Second.ParamInfo))
+ return false;
+ return xyParameters.All(xy => xy.First.TypeName == xy.Second.TypeName);
+ }
+
+ public int GetHashCode([DisallowNull] (MethodBase method, Parameter[] parameters) obj)
+ {
+ int hashCode = obj.method.GetHashCode();
+ foreach (var parameter in obj.parameters)
+ hashCode = HashCode.Combine(hashCode, parameter.GetHashCode());
+ return hashCode;
+ }
+ }
+
+ /// <summary>
+ /// Get a unique name, based on a concatenation of several parts and a random string.
+ /// </summary>
+ /// <remarks>
+ /// Utility function.
+ /// </remarks>
+ /// <param name="nameParts">List of strings to concatenate</param>
+ /// <returns>Unique name</returns>
+ private static string UniqueName(params string[] nameParts)
+ {
+ return $"{string.Join("_", nameParts)}_{Path.GetRandomFileName().Replace(".", "")}";
+ }
+ }
+}
diff --git a/src/Qt.DotNet.Adapter/EventRelay.cs b/src/Qt.DotNet.Adapter/EventRelay.cs
new file mode 100644
index 0000000..f64b938
--- /dev/null
+++ b/src/Qt.DotNet.Adapter/EventRelay.cs
@@ -0,0 +1,79 @@
+/***************************************************************************************************
+ Copyright (C) 2023 The Qt Company Ltd.
+ SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+***************************************************************************************************/
+
+using System.Diagnostics;
+using System.Reflection;
+
+namespace Qt.DotNet
+{
+ /// <summary>
+ /// Notify native subscribers of events raised by a given target object
+ /// </summary>
+ internal class EventRelay
+ {
+ public object Target { get; }
+ public EventInfo Event { get;}
+ public IntPtr Context { get; }
+
+ public Adapter.Delegates.NativeEventHandler Handler { get; set; }
+
+ public EventRelay(
+ object target,
+ EventInfo eventInfo,
+ IntPtr context,
+ Adapter.Delegates.NativeEventHandler eventHandler,
+ bool enabled = true)
+ {
+ Target = target;
+ Event = eventInfo;
+ Handler = eventHandler;
+ Context = context;
+ RelayEventDelegate = null;
+ Enabled = enabled;
+#if TESTS || DEBUG
+ Debug.Assert(Event.EventHandlerType != null, "Event.EventHandlerType is null");
+#endif
+ }
+
+ private readonly object eventSync = new();
+
+ public bool Enabled
+ {
+ get
+ {
+ lock (eventSync)
+ return RelayEventDelegate != null;
+ }
+ set
+ {
+ lock (eventSync) {
+ if (value && RelayEventDelegate == null) {
+ RelayEvent(null, null);
+ Event.AddEventHandler(Target, RelayEventDelegate);
+ } else if (!value && RelayEventDelegate != null) {
+ Event.RemoveEventHandler(Target, RelayEventDelegate);
+ RelayEventDelegate = null;
+ }
+ }
+ }
+ }
+
+ private Delegate RelayEventDelegate { get; set; }
+ public void RelayEvent(object sender, EventArgs args)
+ {
+ if (sender == null && args == null) {
+ var info = MethodBase.GetCurrentMethod() as MethodInfo;
+#if TESTS || DEBUG
+ Debug.Assert(info != null, nameof(info) + " != null");
+ Debug.Assert(Event.EventHandlerType != null, "Event.EventHandlerType is null");
+#endif
+ RelayEventDelegate = Delegate.CreateDelegate(Event.EventHandlerType, this, info);
+ } else {
+ lock (eventSync)
+ Handler(Context, Event.Name, sender, args);
+ }
+ }
+ }
+}
diff --git a/src/Qt.DotNet.Adapter/InterfaceProxy.cs b/src/Qt.DotNet.Adapter/InterfaceProxy.cs
new file mode 100644
index 0000000..ec40e22
--- /dev/null
+++ b/src/Qt.DotNet.Adapter/InterfaceProxy.cs
@@ -0,0 +1,102 @@
+/***************************************************************************************************
+ Copyright (C) 2023 The Qt Company Ltd.
+ SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+***************************************************************************************************/
+
+using System.Diagnostics;
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+namespace Qt.DotNet
+{
+ public class InterfaceProxy
+ {
+ public delegate void CleanUpDelegate(IntPtr context, ulong count);
+ public CleanUpDelegate CleanUpCallback;
+
+ public void CleanUp(IntPtr callback, IntPtr context, ulong count)
+ {
+ CleanUpCallback(context, count);
+ }
+ }
+
+ public partial class Adapter
+ {
+ public static InterfaceProxy AddInterfaceProxy(string interfaceName)
+ {
+#if DEBUG
+ // Compile-time signature check of delegate vs. method
+ _ = new Delegates.AddInterfaceProxy(AddInterfaceProxy);
+#endif
+ var interfaceType = Type.GetType(interfaceName)
+ ?? throw new ArgumentException(
+ $"Interface '{interfaceName}' not found", nameof(interfaceName));
+ var proxyType = CodeGenerator.CreateInterfaceProxyType(interfaceType);
+ var ctor = proxyType.GetConstructor(Array.Empty<Type>());
+#if DEBUG
+ Debug.Assert(ctor != null, nameof(ctor) + " is null");
+#endif
+ var obj = ctor.Invoke(null);
+ return obj as InterfaceProxy;
+ }
+
+ public static void SetInterfaceMethod(
+ InterfaceProxy proxy,
+ string methodName,
+ int parameterCount,
+ Parameter[] parameters,
+ IntPtr callbackPtr,
+ IntPtr cleanUpPtr,
+ IntPtr context)
+ {
+#if DEBUG
+ // Compile-time signature check of delegate vs. method
+ _ = new Delegates.SetInterfaceMethod(SetInterfaceMethod);
+#endif
+ var type = proxy.GetType();
+ var parameterTypes = parameters
+ .Skip(3)
+ .Select((x, i) => x.GetParameterType()
+ ?? throw new ArgumentException($"Type not found [{i}]", nameof(parameters)))
+ .ToArray();
+
+ var method = type.GetMethod(methodName, parameterTypes)
+ ?? throw new ArgumentException(
+ $"Method '{methodName}' not found", nameof(methodName));
+
+ var delegateType = CodeGenerator.CreateDelegateType(method.Name, parameters);
+ var delegateTypeInvoke = delegateType.GetMethod("Invoke");
+#if DEBUG
+ Debug.Assert(delegateTypeInvoke != null, nameof(delegateTypeInvoke) + " != null");
+#endif
+ var paramTypes = delegateTypeInvoke.GetParameters()
+ .Select(p => p.ParameterType);
+
+ var fieldDelegate = proxy.GetType().GetFields()
+ .FirstOrDefault(f => f.FieldType.IsAssignableTo(typeof(Delegate))
+ && f.FieldType.GetMethod("Invoke") is MethodInfo invoke
+ && invoke.GetParameters()
+ .Zip(paramTypes)
+ .All(x => x.First.ParameterType == x.Second)
+ && invoke.ReturnType.IsAssignableTo(delegateTypeInvoke.ReturnType))
+ ?? throw new ArgumentException("Signature mismatch", nameof(parameters));
+
+ var fieldNative = proxy.GetType().GetField($"Native_{fieldDelegate.Name}");
+ var callbackDelegate = Marshal.GetDelegateForFunctionPointer(callbackPtr, delegateType);
+#if DEBUG
+ Debug.Assert(fieldNative != null, nameof(fieldNative) + " is null");
+#endif
+ fieldNative.SetValue(proxy, callbackDelegate);
+
+ var fieldContext = proxy.GetType().GetField($"Context_{fieldDelegate.Name}");
+#if DEBUG
+ Debug.Assert(fieldContext != null, nameof(fieldContext) + " is null");
+#endif
+ fieldContext.SetValue(proxy, context);
+
+ proxy.CleanUpCallback = Marshal.GetDelegateForFunctionPointer(
+ cleanUpPtr, typeof(InterfaceProxy.CleanUpDelegate))
+ as InterfaceProxy.CleanUpDelegate;
+ }
+ }
+}
diff --git a/src/Qt.DotNet.Adapter/ObjectMarshaler.cs b/src/Qt.DotNet.Adapter/ObjectMarshaler.cs
new file mode 100644
index 0000000..49c011f
--- /dev/null
+++ b/src/Qt.DotNet.Adapter/ObjectMarshaler.cs
@@ -0,0 +1,56 @@
+/***************************************************************************************************
+ Copyright (C) 2023 The Qt Company Ltd.
+ SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+***************************************************************************************************/
+
+using System.Runtime.InteropServices;
+
+namespace Qt.DotNet
+{
+ /// <summary>
+ /// Custom interop marshaling of object references
+ /// </summary>
+ internal class ObjectMarshaler : ICustomMarshaler
+ {
+ private bool useWeakRefs;
+ private static ObjectMarshaler NormalRefMarshaler { get; } = new() { useWeakRefs = false };
+ private static ObjectMarshaler WeakRefMarshaler { get; } = new() { useWeakRefs = true };
+
+ public static ICustomMarshaler GetInstance(string refMode)
+ {
+ return refMode == "weak" ? WeakRefMarshaler : NormalRefMarshaler;
+ }
+
+ public int GetNativeDataSize()
+ {
+ return Marshal.SizeOf(typeof(IntPtr));
+ }
+
+ public IntPtr MarshalManagedToNative(object obj)
+ {
+ if (obj == null)
+ return IntPtr.Zero;
+ return Adapter.GetRefPtrToObject(obj, useWeakRefs);
+ }
+
+ public object MarshalNativeToManaged(IntPtr objRefPtr)
+ {
+ if (objRefPtr == IntPtr.Zero)
+ return null;
+ var objRef = Adapter.GetObjectRefFromPtr(objRefPtr);
+ if (objRef != null)
+ return objRef.Target;
+ if (Marshal.PtrToStringUni(objRefPtr) is string str)
+ return str;
+ throw new ArgumentException("Invalid object reference", nameof(objRefPtr));
+ }
+
+ public void CleanUpManagedData(object obj)
+ {
+ }
+
+ public void CleanUpNativeData(IntPtr pId)
+ {
+ }
+ }
+}
diff --git a/src/Qt.DotNet.Adapter/Parameter.cs b/src/Qt.DotNet.Adapter/Parameter.cs
new file mode 100644
index 0000000..4caa9ad
--- /dev/null
+++ b/src/Qt.DotNet.Adapter/Parameter.cs
@@ -0,0 +1,127 @@
+/***************************************************************************************************
+ Copyright (C) 2023 The Qt Company Ltd.
+ SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+***************************************************************************************************/
+
+using System.Diagnostics;
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+namespace Qt.DotNet
+{
+ public interface IAdapterCustomMarshaler
+ {
+ static Type NativeType { get; }
+ }
+
+ /// <summary>
+ /// Information describing a parameter or the return type of a function.
+ /// Used to generate dynamic delegate types for methods requested through the Adapter.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+ public struct Parameter
+ {
+ [MarshalAs(UnmanagedType.LPWStr)]
+ public string TypeName = string.Empty;
+ public readonly ulong ParamInfo = 0;
+
+ // Format of parameter info field
+ // 63......................31...........15............0
+ // |------ ARRAYLENGTH -----|--- FLAGS --|--- TYPE ---|
+
+ const int TYPE_OFFSET = 0;
+ const int TYPE_SIZE = 16;
+
+ const int FLAGS_OFFSET = TYPE_OFFSET + TYPE_SIZE;
+ const int FLAGS_SIZE = 16;
+ const int FLAGS_IN_BIT = 0;
+ const int FLAGS_OUT_BIT = 1;
+ const int FLAGS_ARRAY_BIT = 2;
+ const int FLAGS_FIXEDLENGTH_BIT = 3;
+ const int FLAGS_WEAKREF_BIT = 4;
+
+ const int ARRAYLENGTH_OFFSET = FLAGS_OFFSET + FLAGS_SIZE;
+ const int ARRAYLENGTH_SIZE = 32;
+
+ private static ulong MASK(ulong value, int size) => value & ((((ulong)1) << size) - 1);
+ private static bool FLAG(ulong value, int offset) => ((value >> offset) & 1) == 1;
+
+ public Type GetParameterType()
+ {
+ if (IsVoid)
+ return typeof(void);
+ if (MarshalAs == UnmanagedType.CustomMarshaler) {
+ Type customMarshalerType = Type.GetType(TypeName);
+#if TEST || DEBUG
+ Debug.Assert(customMarshalerType != null, nameof(customMarshalerType) + " is null");
+#endif
+ if (customMarshalerType.IsAssignableTo(typeof(IAdapterCustomMarshaler))) {
+ var nativeType = customMarshalerType.GetProperty(
+ "NativeType", BindingFlags.Public | BindingFlags.Static);
+ if (nativeType != null && nativeType.GetValue(null) is Type customType)
+ return customType;
+ }
+ return typeof(object);
+ }
+ if (!string.IsNullOrEmpty(TypeName))
+ return Type.GetType(TypeName);
+ string typeName = $"{nameof(System)}."
+ + MarshalAs switch
+ {
+ UnmanagedType.Bool => nameof(Boolean),
+ UnmanagedType.I1 => nameof(SByte),
+ UnmanagedType.U1 => nameof(Byte),
+ UnmanagedType.I2 => nameof(Int16),
+ UnmanagedType.U2 => nameof(UInt16),
+ UnmanagedType.I4 => nameof(Int32),
+ UnmanagedType.U4 => nameof(UInt32),
+ UnmanagedType.I8 => nameof(Int64),
+ UnmanagedType.U8 => nameof(UInt64),
+ UnmanagedType.R4 => nameof(Single),
+ UnmanagedType.R8 => nameof(Double),
+ UnmanagedType.SysInt => nameof(IntPtr),
+ UnmanagedType.SysUInt => nameof(UIntPtr),
+ UnmanagedType.FunctionPtr => nameof(Delegate),
+ UnmanagedType.LPStr => nameof(String),
+ UnmanagedType.LPWStr => nameof(String),
+ UnmanagedType.LPTStr => nameof(String),
+ UnmanagedType.ByValTStr => nameof(String),
+ _ => nameof(Object)
+ }
+ + (IsArray ? "[]" : "");
+
+ var type = Type.GetType(typeName);
+ if (type != null)
+ TypeName = typeName;
+ return type;
+ }
+
+ public Parameter(Type type, ulong paramInfo = 0)
+ {
+ TypeName = type.FullName;
+ ParamInfo = paramInfo;
+ }
+
+ public Parameter(string typeName, ulong paramInfo = 0)
+ {
+ TypeName = typeName;
+ ParamInfo = paramInfo;
+ }
+
+ public Parameter(UnmanagedType type)
+ {
+ ParamInfo = MASK((ulong)type, TYPE_SIZE) << TYPE_OFFSET;
+ }
+
+ public static UnmanagedType ObjectRef => (UnmanagedType)MASK(ulong.MaxValue, TYPE_SIZE);
+ public UnmanagedType MarshalAs => (UnmanagedType)MASK(ParamInfo >> TYPE_OFFSET, TYPE_SIZE);
+ public bool IsIn => FLAG(ParamInfo >> FLAGS_OFFSET, FLAGS_IN_BIT);
+ public bool IsOut => FLAG(ParamInfo >> FLAGS_OFFSET, FLAGS_OUT_BIT);
+ public bool IsArray => FLAG(ParamInfo >> FLAGS_OFFSET, FLAGS_ARRAY_BIT);
+ public bool IsFixedLength => FLAG(ParamInfo >> FLAGS_OFFSET, FLAGS_FIXEDLENGTH_BIT);
+ public bool IsWeakRef => FLAG(ParamInfo >> FLAGS_OFFSET, FLAGS_WEAKREF_BIT);
+ public int ArrayLength => (int)MASK(ParamInfo >> ARRAYLENGTH_OFFSET, ARRAYLENGTH_SIZE);
+ public bool IsVoid => string.IsNullOrEmpty(TypeName) && MarshalAs == 0;
+ public static Parameter Void { get; } = new();
+ }
+}
diff --git a/src/Qt.DotNet.Adapter/Qt.DotNet.Adapter.csproj b/src/Qt.DotNet.Adapter/Qt.DotNet.Adapter.csproj
new file mode 100644
index 0000000..db731eb
--- /dev/null
+++ b/src/Qt.DotNet.Adapter/Qt.DotNet.Adapter.csproj
@@ -0,0 +1,28 @@
+<!--
+/***************************************************************************************************
+ Copyright (C) 2023 The Qt Company Ltd.
+ SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+***************************************************************************************************/
+-->
+
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>net6.0</TargetFramework>
+ <ImplicitUsings>enable</ImplicitUsings>
+ <Nullable>disable</Nullable>
+ <Configurations>Debug;Release;Tests</Configurations>
+ </PropertyGroup>
+
+ <Target Name="PostBuild" AfterTargets="PostBuildEvent">
+ <Exec Command="MKDIR $(SolutionDir)bin 2&gt; NUL&#xD;&#xA;COPY $(OutputPath)$(AssemblyName).dll $(SolutionDir)bin\$(AssemblyName).dll&#xD;&#xA;" />
+ </Target>
+ <Target Name="CleanPostBuild" AfterTargets="Clean">
+ <Delete Files="$(SolutionDir)bin\$(AssemblyName).dll"/>
+ </Target>
+
+ <ItemGroup>
+ <ProjectReference Include="..\includegen\includegen.csproj" />
+ </ItemGroup>
+
+</Project>
diff --git a/src/Qt.DotNet.Adapter/StringMarshaler.cs b/src/Qt.DotNet.Adapter/StringMarshaler.cs
new file mode 100644
index 0000000..c7b1869
--- /dev/null
+++ b/src/Qt.DotNet.Adapter/StringMarshaler.cs
@@ -0,0 +1,47 @@
+/**************************************************************************************************
+ Copyright (C) 2023 The Qt Company Ltd.
+ SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+***************************************************************************************************/
+
+using System.Text.RegularExpressions;
+using System.Runtime.InteropServices;
+
+namespace Qt.DotNet
+{
+ internal class StringMarshaler : ICustomMarshaler, IAdapterCustomMarshaler
+ {
+ public static Type NativeType => typeof(string);
+
+ public static ICustomMarshaler GetInstance(string options) => new StringMarshaler
+ {
+ Options = options
+ };
+
+ private string Options { get; init; }
+ private bool CleanUp =>
+ Regex.IsMatch(Options, @"\bCleanUp\s*=\s*true\b", RegexOptions.IgnoreCase);
+
+ public int GetNativeDataSize()
+ {
+ return Marshal.SizeOf(typeof(IntPtr));
+ }
+
+ public IntPtr MarshalManagedToNative(object objStr)
+ {
+ return Marshal.StringToHGlobalUni(objStr as string);
+ }
+
+ public object MarshalNativeToManaged(IntPtr ptrStr)
+ {
+ return Marshal.PtrToStringUni(ptrStr);
+ }
+
+ public void CleanUpNativeData(IntPtr ptrStr)
+ {
+ if (CleanUp)
+ Marshal.FreeHGlobal(ptrStr);
+ }
+ public void CleanUpManagedData(object objStr)
+ { }
+ }
+}