diff options
author | Miguel Costa <[email protected]> | 2025-05-15 16:35:39 +0200 |
---|---|---|
committer | Miguel Costa <[email protected]> | 2025-07-01 10:44:58 +0000 |
commit | 148a6b9e371b578c001a1c941dcb4d0b0f16fe30 (patch) | |
tree | 7595d5e535d18e81e66aeb3b1cdf4c5f11234c22 | |
parent | 0130a895ea9b9367198d096a782a04b291b50633 (diff) |
Task-number: QTBUG-134961
Change-Id: I447a125faa5e40fea07bd9957a4b80192d582f96
Reviewed-by: Karsten Heimrich <[email protected]>
-rw-r--r-- | include/qdotnetadapter.h | 52 | ||||
-rw-r--r-- | include/qdotnetobject.h | 43 | ||||
-rw-r--r-- | include/qdotnettype.h | 56 | ||||
-rw-r--r-- | src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.Delegates.cs | 38 | ||||
-rw-r--r-- | src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.Fields.cs | 144 | ||||
-rw-r--r-- | src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.Methods.cs | 13 | ||||
-rw-r--r-- | src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.Objects.cs | 10 | ||||
-rw-r--r-- | src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.Test.cs | 34 | ||||
-rw-r--r-- | src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.cs | 52 | ||||
-rw-r--r-- | src/Qt.DotNet.Adapter/Qt/DotNet/CodeGenerator.cs | 105 | ||||
-rw-r--r-- | tests/FooLib/FooClass.cs | 4 | ||||
-rw-r--r-- | tests/tst_qtdotnet/foo.cpp | 51 | ||||
-rw-r--r-- | tests/tst_qtdotnet/foo.h | 8 | ||||
-rw-r--r-- | tests/tst_qtdotnet/tst_qtdotnet.cpp | 21 |
14 files changed, 592 insertions, 39 deletions
diff --git a/include/qdotnetadapter.h b/include/qdotnetadapter.h index d9b5b95..adabaa7 100644 --- a/include/qdotnetadapter.h +++ b/include/qdotnetadapter.h @@ -94,6 +94,10 @@ public: host->resolveFunction(QDOTNETADAPTER_DELEGATE(ResolveStaticMethod)); host->resolveFunction(QDOTNETADAPTER_DELEGATE(ResolveConstructor)); host->resolveFunction(QDOTNETADAPTER_DELEGATE(ResolveInstanceMethod)); + host->resolveFunction(QDOTNETADAPTER_DELEGATE(ResolveStaticFieldGet)); + host->resolveFunction(QDOTNETADAPTER_DELEGATE(ResolveStaticFieldSet)); + host->resolveFunction(QDOTNETADAPTER_DELEGATE(ResolveInstanceFieldGet)); + host->resolveFunction(QDOTNETADAPTER_DELEGATE(ResolveInstanceFieldSet)); host->resolveFunction(QDOTNETADAPTER_DELEGATE(ResolveSafeMethod)); host->resolveFunction(QDOTNETADAPTER_DELEGATE(AddEventHandler)); host->resolveFunction(QDOTNETADAPTER_DELEGATE(RemoveEventHandler)); @@ -161,6 +165,46 @@ public: objectRef, methodName, static_cast<qint32>(params.size()), params); } + void *resolveStaticFieldGet(const QString &typeName, const QString &fieldName, + const QList<QDotNetParameter> ¶ms) + { + init(); + if (typeName.isEmpty() || fieldName.isEmpty()) + return nullptr; + return fnResolveStaticFieldGet( + typeName, fieldName, static_cast<qint32>(params.size()), params); + } + + void *resolveStaticFieldSet(const QString &typeName, const QString &fieldName, + const QList<QDotNetParameter> ¶ms) + { + init(); + if (typeName.isEmpty() || fieldName.isEmpty()) + return nullptr; + return fnResolveStaticFieldSet( + typeName, fieldName, static_cast<qint32>(params.size()), params); + } + + void *resolveInstanceFieldGet(const QDotNetRef &objectRef, const QString &fieldName, + const QList<QDotNetParameter> ¶ms) + { + init(); + if (QtDotNet::isNull(objectRef) || fieldName.isEmpty()) + return nullptr; + return fnResolveInstanceFieldGet( + objectRef, fieldName, static_cast<qint32>(params.size()), params); + } + + void *resolveInstanceFieldSet(const QDotNetRef &objectRef, const QString &fieldName, + const QList<QDotNetParameter> ¶ms) + { + init(); + if (QtDotNet::isNull(objectRef) || fieldName.isEmpty()) + return nullptr; + return fnResolveInstanceFieldSet( + objectRef, fieldName, static_cast<qint32>(params.size()), params); + } + using EventCallback = void(QDOTNETFUNCTION_CALLTYPE *)(void *, void *, void *, void *); void *resolveSafeMethod(void *funcPtr, const QList<QDotNetParameter> ¶ms) const @@ -303,6 +347,14 @@ private: mutable QDotNetFunction<void *, qint32, QList<QDotNetParameter>> fnResolveConstructor; mutable QDotNetFunction<void *, QDotNetRef, QString, qint32, QList<QDotNetParameter>> fnResolveInstanceMethod; + mutable QDotNetFunction<void *, QString, QString, qint32, QList<QDotNetParameter>> + fnResolveStaticFieldGet; + mutable QDotNetFunction<void *, QString, QString, qint32, QList<QDotNetParameter>> + fnResolveStaticFieldSet; + mutable QDotNetFunction<void *, QDotNetRef, QString, qint32, QList<QDotNetParameter>> + fnResolveInstanceFieldGet; + mutable QDotNetFunction<void *, QDotNetRef, QString, qint32, QList<QDotNetParameter>> + fnResolveInstanceFieldSet; mutable QDotNetFunction<void *, void *, qint32, QList<QDotNetParameter>> fnResolveSafeMethod; mutable QDotNetFunction<void, QDotNetRef, QString, void *, EventCallback> fnAddEventHandler; mutable QDotNetFunction<void, QDotNetRef, QString, void *> fnRemoveEventHandler; diff --git a/include/qdotnetobject.h b/include/qdotnetobject.h index 5657c6f..01a2fd2 100644 --- a/include/qdotnetobject.h +++ b/include/qdotnetobject.h @@ -237,6 +237,49 @@ public: return QDotNetType::constructor(typeName, ctor); } + template<typename T> + QDotNetFunction<T, QDotNetRef> fieldGet(const QString &fieldName) + { + const QList<QDotNetParameter> parameters + { + QDotNetInbound<T>::Parameter, + QDotNetOutbound<QDotNetRef>::Parameter + }; + + return adapter().resolveInstanceFieldGet(*this, fieldName, parameters); + } + + template<typename T> + QDotNetFunction<T, QDotNetRef> fieldGet(const QString &fieldName, + QDotNetFunction<T, QDotNetRef> &func) + { + if (!func.isValid()) + func = fieldGet<T>(fieldName); + return func; + } + + template<typename T> + QDotNetFunction<void, QDotNetRef, T> fieldSet(const QString &fieldName) + { + const QList<QDotNetParameter> parameters + { + QDotNetInbound<void>::Parameter, + QDotNetOutbound<QDotNetRef>::Parameter, + QDotNetOutbound<T>::Parameter, + }; + + return adapter().resolveInstanceFieldSet(*this, fieldName, parameters); + } + + template<typename T> + QDotNetFunction<void, QDotNetRef, T> fieldSet(const QString &fieldName, + QDotNetFunction<void, QDotNetRef, T> &func) + { + if (!func.isValid()) + func = fieldSet<T>(fieldName); + return func; + } + void subscribe(const QString &eventName, QDotNetEventHandler *eventHandler) { adapter().addEventHandler(*this, eventName, eventHandler, eventCallback); diff --git a/include/qdotnettype.h b/include/qdotnettype.h index dc3bb90..1cbfd96 100644 --- a/include/qdotnettype.h +++ b/include/qdotnettype.h @@ -81,6 +81,62 @@ public: return typeOf(T::AssemblyQualifiedName); } + template<typename T> + static QDotNetFunction<T> staticFieldGet(const QString &typeName, + const QString &fieldName) + { + const QList<QDotNetParameter> parameters + { + QDotNetInbound<T>::Parameter + }; + return adapter().resolveStaticFieldGet(typeName, fieldName, parameters); + } + + template<typename T> + static QDotNetFunction<T> &staticFieldGet(const QString &typeName, + const QString &fieldName, QDotNetFunction<T> &func) + { + if (!func.isValid()) + func = staticFieldGet<T>(typeName, fieldName); + return func; + } + + template<typename T> + QDotNetFunction<T> staticFieldGet(const QString &fieldName) const + { + return staticFieldGet<T>(assemblyQualifiedName(), fieldName); + } + + template<typename T> + QDotNetFunction<T> &staticFieldGet( + const QString &fieldName, QDotNetFunction<T> &func) const + { + if (!func.isValid()) + func = staticFieldGet<T>(fieldName); + return func; + } + + template<typename T> + static QDotNetFunction<void, T> staticFieldSet(const QString &typeName, + const QString &fieldName) + { + const QList<QDotNetParameter> parameters + { + QDotNetInbound<void>::Parameter, + QDotNetOutbound<T>::Parameter + }; + return adapter().resolveStaticFieldSet(typeName, fieldName, parameters); + } + + template<typename T> + static QDotNetFunction<void, T> &staticFieldSet(const QString &typeName, + const QString &fieldName, QDotNetFunction<void, T> &func) + { + if (!func.isValid()) + func = staticFieldSet<T>(typeName, fieldName); + return func; + } + template<typename TResult, typename ...TArg> static QDotNetFunction<TResult, TArg...> staticMethod(const QString &typeName, const QString &methodName) diff --git a/src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.Delegates.cs b/src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.Delegates.cs index bb5e716..882b2a3 100644 --- a/src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.Delegates.cs +++ b/src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.Delegates.cs @@ -45,6 +45,44 @@ namespace Qt.DotNet [In] Parameter[] parameters); [UnmanagedFunctionPointer(CallingConvention.Winapi)] + public delegate IntPtr ResolveStaticFieldGet( + [MarshalAs(UnmanagedType.LPWStr)] + [In] string typeName, + [MarshalAs(UnmanagedType.LPWStr)] + [In] string fieldName, + [In] int parameterCount, + [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] + [In] Parameter[] parameters); + + [UnmanagedFunctionPointer(CallingConvention.Winapi)] + public delegate IntPtr ResolveStaticFieldSet( + [MarshalAs(UnmanagedType.LPWStr)] + [In] string typeName, + [MarshalAs(UnmanagedType.LPWStr)] + [In] string fieldName, + [In] int parameterCount, + [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] + [In] Parameter[] parameters); + + [UnmanagedFunctionPointer(CallingConvention.Winapi)] + public delegate IntPtr ResolveInstanceFieldGet( + [In] IntPtr objRefPtr, + [MarshalAs(UnmanagedType.LPWStr)] + [In] string fieldName, + [In] int parameterCount, + [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] + [In] Parameter[] parameters); + + [UnmanagedFunctionPointer(CallingConvention.Winapi)] + public delegate IntPtr ResolveInstanceFieldSet( + [In] IntPtr objRefPtr, + [MarshalAs(UnmanagedType.LPWStr)] + [In] string fieldName, + [In] int parameterCount, + [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] + [In] Parameter[] parameters); + + [UnmanagedFunctionPointer(CallingConvention.Winapi)] public delegate IntPtr ResolveSafeMethod( [In] IntPtr funcPtr, [In] int parameterCount, diff --git a/src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.Fields.cs b/src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.Fields.cs new file mode 100644 index 0000000..01e4810 --- /dev/null +++ b/src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.Fields.cs @@ -0,0 +1,144 @@ +/*************************************************************************************************** + Copyright (C) 2025 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; +using System.Runtime.Remoting; + +namespace Qt.DotNet +{ + public partial class Adapter + { + public static IntPtr ResolveStaticFieldGet( + string typeName, + string fieldName, + int parameterCount, + Parameter[] parameters) + { +#if DEBUG + // Compile-time signature check of delegate vs. method + _ = new Delegates.ResolveStaticFieldGet(ResolveStaticFieldGet); +#endif + return ResolveStaticFieldAccess(typeName, fieldName, false, parameters); + } + + public static IntPtr ResolveStaticFieldSet( + string typeName, + string fieldName, + int parameterCount, + Parameter[] parameters) + { +#if DEBUG + // Compile-time signature check of delegate vs. method + _ = new Delegates.ResolveStaticFieldSet(ResolveStaticFieldSet); +#endif + return ResolveStaticFieldAccess(typeName, fieldName, true, parameters); + } + + public static IntPtr ResolveInstanceFieldGet( + IntPtr objRefPtr, + string fieldName, + int parameterCount, + Parameter[] parameters) + { +#if DEBUG + // Compile-time signature check of delegate vs. method + _ = new Delegates.ResolveInstanceFieldGet(ResolveInstanceFieldGet); +#endif + return ResolveInstanceFieldAccess(objRefPtr, fieldName, false, parameters); + + } + + public static IntPtr ResolveInstanceFieldSet( + IntPtr objRefPtr, + string fieldName, + int parameterCount, + Parameter[] parameters) + { +#if DEBUG + // Compile-time signature check of delegate vs. method + _ = new Delegates.ResolveInstanceFieldSet(ResolveInstanceFieldSet); +#endif + return ResolveInstanceFieldAccess(objRefPtr, fieldName, true, parameters); + } + + private static IntPtr ResolveStaticFieldAccess( + string typeName, + string fieldName, + bool isFieldSet, + Parameter[] parameters) + { + var type = Type.GetType(typeName) + ?? throw new ArgumentException($"Type '{typeName}' not found", nameof(typeName)); + return ResolveFieldAccess(type, null, fieldName, isFieldSet, parameters); + } + + private static IntPtr ResolveInstanceFieldAccess( + IntPtr objRefPtr, string fieldName, bool isFieldSet, Parameter[] parameters) + { + var objRef = GetObjectRefFromPtr(objRefPtr); + if (objRef == null) + throw new ArgumentException("Invalid object reference", nameof(objRefPtr)); + var obj = objRef.Target; + var type = obj.GetType(); + + return ResolveFieldAccess(type, obj, fieldName, isFieldSet, parameters); + } + + private static IntPtr ResolveFieldAccess( + Type type, object obj, string fieldName, bool isFieldSet, Parameter[] parameters) + { + var isStatic = (obj == null); + object target = isStatic ? type : obj; + var fieldFlags = BindingFlags.Public + | (isStatic ? BindingFlags.Static : BindingFlags.Instance); + + var fieldAccess = isFieldSet ? MemberAccess.FieldSet : MemberAccess.FieldGet; + + var field = type.GetField(fieldName, fieldFlags) + ?? throw new ArgumentException($"Field not found [{fieldName}]", nameof(fieldName)); + + bool sigOk = + (isFieldSet, isStatic) switch + { + (true, true) => parameters.Length == 2 + && parameters[0].GetParameterType() == typeof(void) + && parameters[1].GetParameterType() == field.FieldType, + (true, false) => parameters.Length == 3 + && parameters[0].GetParameterType() == typeof(void) + && parameters[1].GetParameterType() == typeof(object) + && parameters[2].GetParameterType() == field.FieldType, + (false, true) => parameters.Length == 1 + && parameters[0].GetParameterType() == field.FieldType, + (false, false) => parameters.Length == 2 + && parameters[0].GetParameterType() == field.FieldType + && parameters[1].GetParameterType() == typeof(object), + }; + if (!sigOk) + throw new ArgumentException($"Invalid signature", nameof(parameters)); + + if (TryGetDelegate(target, field, fieldAccess, out var fieldMethod)) + return fieldMethod.FuncPtr; + + var fieldProxy = CodeGenerator.CreateProxyMethodForField(field, isFieldSet, parameters) + ?? throw new InvalidOperationException("Failed to create proxy"); + + var delegateType = CodeGenerator.CreateDelegateTypeForMethod(fieldProxy, parameters) + ?? throw new InvalidOperationException("Failed to generate delegate type"); + + var methodDelegate = Delegate.CreateDelegate(delegateType, fieldProxy, false) + ?? throw new InvalidOperationException("Failed to create delegate"); + + var methodHandle = GCHandle.Alloc(methodDelegate); + var methodFuncPtr = Marshal.GetFunctionPointerForDelegate(methodDelegate); + + var delegateRef = new DelegateRef(methodHandle, methodFuncPtr); + AddDelegateToCache(methodFuncPtr, target, field, fieldAccess, delegateRef); + return methodFuncPtr; + } + } +} diff --git a/src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.Methods.cs b/src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.Methods.cs index 47db8a6..0f54e61 100644 --- a/src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.Methods.cs +++ b/src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.Methods.cs @@ -36,7 +36,7 @@ namespace Qt.DotNet ?? throw new ArgumentException( $"Method '{methodName}' not found", nameof(methodName)); - if (DelegatesByMethod.TryGetValue((type, method), out var objMethod)) + if (TryGetDelegateForMethod(type, method, out var objMethod)) return objMethod.FuncPtr; var delegateType = CodeGenerator.CreateDelegateTypeForMethod(method, parameters) @@ -49,8 +49,7 @@ namespace Qt.DotNet var methodFuncPtr = Marshal.GetFunctionPointerForDelegate(methodDelegate); var delegateRef = new DelegateRef(methodHandle, methodFuncPtr); - DelegateRefs.TryAdd(methodFuncPtr, (type, method, delegateRef)); - DelegatesByMethod.TryAdd((type, method), delegateRef); + AddMethodDelegateToCache(methodFuncPtr, type, method, delegateRef); return methodFuncPtr; } @@ -93,8 +92,7 @@ namespace Qt.DotNet var methodFuncPtr = Marshal.GetFunctionPointerForDelegate(methodDelegate); var delegateRef = new DelegateRef(methodHandle, methodFuncPtr); - DelegateRefs.TryAdd(methodFuncPtr, (type, ctor, delegateRef)); - DelegatesByMethod.TryAdd((type, ctor), delegateRef); + AddCtorDelegateToCache(methodFuncPtr, type, ctor, delegateRef); return methodFuncPtr; } @@ -125,7 +123,7 @@ namespace Qt.DotNet ?? throw new ArgumentException( $"Method '{methodName}' not found", nameof(methodName)); - if (DelegatesByMethod.TryGetValue((obj, method), out var objMethod)) + if (TryGetDelegateForMethod(obj, method, out var objMethod)) return objMethod.FuncPtr; var delegateType = CodeGenerator.CreateDelegateTypeForMethod(method, parameters) @@ -138,8 +136,7 @@ namespace Qt.DotNet var methodFuncPtr = Marshal.GetFunctionPointerForDelegate(methodDelegate); var delegateRef = new DelegateRef(methodHandle, methodFuncPtr); - DelegateRefs.TryAdd(methodFuncPtr, (obj, method, delegateRef)); - DelegatesByMethod.TryAdd((obj, method), delegateRef); + AddMethodDelegateToCache(methodFuncPtr, obj, method, delegateRef); return methodFuncPtr; } diff --git a/src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.Objects.cs b/src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.Objects.cs index 5377d55..f24030b 100644 --- a/src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.Objects.cs +++ b/src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.Objects.cs @@ -76,7 +76,7 @@ ADAPTER::FreeObjectRef: WARNING Invalid object reference: 0x{objRefPtr:x16}"); var liveObjects = ObjectRefs.Values.Select(x => x.Target).ToList(); var isLive = liveObjects.Any(x => x.Equals(objRef.Target)); if (!isLive) { - var deadMethods = DelegatesByMethod + var deadMethods = DelegatesByMember .Where(x => x.Key.Target.Equals(objRef.Target)) .Select(x => x.Value.FuncPtr) .ToList(); @@ -106,7 +106,7 @@ ADAPTER::FreeObjectRef: WARNING Invalid object reference: 0x{objRefPtr:x16}"); foreach (var typeRef in typeRefs) FreeObjectRef(typeRef.Key); - var deadMethods = DelegatesByMethod + var deadMethods = DelegatesByMember .Where(x => x.Key.Target.Equals(type)) .Select(x => x.Value.FuncPtr) .ToList(); @@ -121,7 +121,11 @@ ADAPTER::FreeObjectRef: WARNING Invalid object reference: 0x{objRefPtr:x16}"); #endif if (!DelegateRefs.TryRemove(delRefPtr, out var delegateRef)) return; - DelegatesByMethod.TryRemove((delegateRef.Target, delegateRef.Method), out _); + DelegatesByMember + .Where(x => x.Key.Target == delegateRef.Target + && x.Key.Member == delegateRef.Member) + .ToList() + .ForEach(x => DelegatesByMember.TryRemove(x)); delegateRef.Ref.Handle.Free(); } diff --git a/src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.Test.cs b/src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.Test.cs index 983e582..abea3ca 100644 --- a/src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.Test.cs +++ b/src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.Test.cs @@ -33,7 +33,7 @@ namespace Qt.DotNet { var ctorPtr = ResolveConstructor(1, new[] { new Parameter("FooLib.Foo, FooLib") }); - var ctor = GetMethod(ctorPtr) as ConstructorInfo; + var ctor = GetMember(ctorPtr) as ConstructorInfo; Debug.Assert(ctor != null, nameof(ctor) + " is null"); var objRef = GetRefPtrToObject(ctor.Invoke(Array.Empty<object>())); @@ -51,11 +51,11 @@ namespace Qt.DotNet TestNativeEventHandler); for (int i = 0; i < 1000; ++i) { - var str = GetMethod(getBarPtr) - .Invoke(GetObjectRefFromPtr(objRef).Target, Array.Empty<object>()) as string; + var str = (GetMember(getBarPtr) as MethodBase) + ?.Invoke(GetObjectRefFromPtr(objRef).Target, Array.Empty<object>()) as string; str += "hello"; - GetMethod(setBarPtr) - .Invoke(GetObjectRefFromPtr(objRef).Target, new object[] { str }); + (GetMember(setBarPtr) as MethodBase) + ?.Invoke(GetObjectRefFromPtr(objRef).Target, new object[] { str }); } RemoveAllEventHandlers(objRef); @@ -80,14 +80,14 @@ namespace Qt.DotNet 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 typeObj = (GetMember(getTypePtr) as MethodBase) + ?.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>()) + var argsTypeName = (GetMember(getFullNamePtr) as MethodBase) + ?.Invoke(GetObjectRefFromPtr(typeRef).Target, Array.Empty<object>()) as string; if (argsTypeName == "System.ComponentModel.PropertyChangedEventArgs") { @@ -100,15 +100,15 @@ namespace Qt.DotNet { new Parameter(UnmanagedType.LPWStr) }); - var propName = GetMethod(getPropertyNamePtr) - .Invoke(GetObjectRefFromPtr(propChangeRef).Target, Array.Empty<object>()) + var propName = (GetMember(getPropertyNamePtr) as MethodBase) + ?.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>()) + var str = (GetMember(getBarPtr) as MethodBase) + ?.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]"); @@ -121,12 +121,12 @@ namespace Qt.DotNet FreeObjectRef(senderRef); } - private static MethodBase GetMethod(IntPtr funcPtr) + private static MemberInfo GetMember(IntPtr funcPtr) { - var methods = DelegateRefs + var members = DelegateRefs .Where(x => x.Value.Ref.FuncPtr == funcPtr) - .Select(x => x.Value.Method); - return methods.First(); + .Select(x => x.Value.Member); + return members.First(); } #endif diff --git a/src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.cs b/src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.cs index 286c28c..18bcb5a 100644 --- a/src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.cs +++ b/src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.cs @@ -123,7 +123,7 @@ namespace Qt.DotNet #endif ObjectRefs.Clear(); DelegateRefs.Clear(); - DelegatesByMethod.Clear(); + DelegatesByMember.Clear(); Events.Clear(); } @@ -151,20 +151,66 @@ namespace Qt.DotNet } } + private enum MemberAccess + { + Constructor = MemberTypes.Constructor, + Method = MemberTypes.Method, + FieldGet = MemberTypes.Field, + FieldSet = -MemberTypes.Field + } + private static ConcurrentDictionary <IntPtr, ObjectRef> ObjectRefs { get; } = new(); private static ConcurrentDictionary - <IntPtr, (object Target, MethodBase Method, DelegateRef Ref)> DelegateRefs + <IntPtr, (object Target, MemberInfo Member, MemberAccess Access, DelegateRef Ref)> + DelegateRefs { get; } = new(); private static ConcurrentDictionary - <(object Target, MethodBase Method), DelegateRef> DelegatesByMethod + <(object Target, MemberInfo Member, MemberAccess Access), DelegateRef> DelegatesByMember { get; } = new(); private static ConcurrentDictionary <(ObjectRef Source, string Name, IntPtr Context), EventRelay> Events { get; } = new(); + + private static void AddDelegateToCache( + IntPtr ptr, object obj, MemberInfo member, MemberAccess access, DelegateRef delegateRef) + { + DelegateRefs.TryAdd(ptr, (obj, member, access, delegateRef)); + DelegatesByMember.TryAdd((obj, member, access), delegateRef); + } + + private static bool TryGetDelegate( + object obj, MemberInfo member, MemberAccess access, out DelegateRef delegateRef) + { + return DelegatesByMember.TryGetValue((obj, member, access), out delegateRef); + } + + private static void AddMethodDelegateToCache( + IntPtr ptr, object obj, MethodInfo method, DelegateRef delegateRef) + { + AddDelegateToCache(ptr, obj, method, MemberAccess.Method, delegateRef); + } + + private static bool TryGetDelegateForMethod( + object obj, MethodInfo method, out DelegateRef delegateRef) + { + return TryGetDelegate(obj, method, MemberAccess.Method, out delegateRef); + } + + private static void AddCtorDelegateToCache( + IntPtr ptr, object obj, ConstructorInfo ctor, DelegateRef delegateRef) + { + AddDelegateToCache(ptr, obj, ctor, MemberAccess.Constructor, delegateRef); + } + + private static bool TryGetDelegateForCtor( + object obj, ConstructorInfo ctor, out DelegateRef delegateRef) + { + return TryGetDelegate(obj, ctor, MemberAccess.Constructor, out delegateRef); + } } } diff --git a/src/Qt.DotNet.Adapter/Qt/DotNet/CodeGenerator.cs b/src/Qt.DotNet.Adapter/Qt/DotNet/CodeGenerator.cs index bb97c60..c1f6df0 100644 --- a/src/Qt.DotNet.Adapter/Qt/DotNet/CodeGenerator.cs +++ b/src/Qt.DotNet.Adapter/Qt/DotNet/CodeGenerator.cs @@ -8,14 +8,16 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Reflection.Emit; +using System.Reflection.Metadata; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Security.Cryptography; namespace Qt.DotNet { - using DelegateIndex = ConcurrentDictionary<(MethodBase, Parameter[]), Type>; - using ProxyIndex = ConcurrentDictionary<(MethodBase, Parameter[]), MethodInfo>; - using IIndexer = IEqualityComparer<(MethodBase method, Parameter[] parameters)>; + using DelegateIndex = ConcurrentDictionary<(MemberInfo, Parameter[]), Type>; + using ProxyIndex = ConcurrentDictionary<(MemberInfo, Parameter[]), MethodInfo>; + using IIndexer = IEqualityComparer<(MemberInfo member, Parameter[] parameters)>; public class SafeReturn<T> { @@ -377,6 +379,93 @@ namespace Qt.DotNet return delegateType; } + public static MethodInfo CreateProxyMethodForField( + FieldInfo field, bool isFieldSet, Parameter[] parameters) + { +#if TESTS || DEBUG + Debug.Assert(field is not null); +#endif + // Check if already in cache + if (Proxies.TryGetValue((field, parameters), out MethodInfo proxy)) + return proxy; + + var className = UniqueName( + field.DeclaringType.Name, isFieldSet ? "FieldSet" : "FieldGet", field.Name); + var methodName = isFieldSet ? "Set" : "Get"; + var methodType = isFieldSet ? typeof(void) : field.FieldType; + Type[] methodParams = + (isFieldSet, field.IsStatic) switch + { + (true, true) => [field.FieldType], + (true, false) => [typeof(object), field.FieldType], + (false, true) => [], + (false, false) => [typeof(object)] + }; + + // Generate placeholder type for proxy method + var typeGen = ModuleGen.DefineType(className, + TypeAttributes.Sealed | TypeAttributes.Public, typeof(object)); + FieldBuilder fieldInfo = null; + if (field.IsLiteral) { + fieldInfo = typeGen.DefineField("FieldInfo", typeof(FieldInfo), + FieldAttributes.Public | FieldAttributes.Static); + } + + // Generate proxy method + var proxyGen = typeGen.DefineMethod(methodName, + MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Static, + methodType, methodParams); + + // Get code generator for proxy method + var code = proxyGen.GetILGenerator(); + + // Implement method + if (isFieldSet) { + if (field.IsStatic) { + if (!field.IsLiteral) { + code.Emit(OpCodes.Ldarg_0); + code.Emit(OpCodes.Stsfld, field); + } + } else { + code.Emit(OpCodes.Ldarg_0); + code.Emit(OpCodes.Ldarg_1); + code.Emit(OpCodes.Stfld, field); + } + } else { + if (field.IsStatic) { + if (field.IsLiteral) { + code.Emit(OpCodes.Ldsfld, fieldInfo); + code.Emit(OpCodes.Ldnull); + code.Emit(OpCodes.Callvirt, + typeof(FieldInfo).GetMethod(nameof(FieldInfo.GetValue))); + if (field.FieldType.IsValueType) + code.Emit(OpCodes.Unbox_Any, field.FieldType); + else + code.Emit(OpCodes.Castclass, field.FieldType); + } else { + code.Emit(OpCodes.Ldsfld, field); + } + } else { + code.Emit(OpCodes.Ldarg_0); + code.Emit(OpCodes.Ldfld, field); + } + } + code.Emit(OpCodes.Ret); + + // Get generated type + var proxyType = typeGen.CreateType() + ?? throw new TypeAccessException("Error creating dynamic get_field proxy"); + if (field.IsLiteral) + proxyType.GetField("FieldInfo")?.SetValue(null, field); + + // Get generated method + proxy = proxyType.GetMethod(methodName); + + // Add to cache and return + Proxies.TryAdd((field, parameters), proxy); + return proxy; + } + /// <summary> /// Generate static method that encapsulates a call to a given constructor. /// </summary> @@ -652,10 +741,10 @@ namespace Qt.DotNet private class Indexer : IIndexer { public bool Equals( - (MethodBase method, Parameter[] parameters) x, - (MethodBase method, Parameter[] parameters) y) + (MemberInfo member, Parameter[] parameters) x, + (MemberInfo member, Parameter[] parameters) y) { - if (x.method != y.method) + if (x.member != y.member) return false; if (x.parameters.Length != y.parameters.Length) return false; @@ -665,9 +754,9 @@ namespace Qt.DotNet return xyParameters.All(xy => xy.First.TypeName == xy.Second.TypeName); } - public int GetHashCode([DisallowNull] (MethodBase method, Parameter[] parameters) obj) + public int GetHashCode([DisallowNull] (MemberInfo member, Parameter[] parameters) obj) { - int hashCode = obj.method.GetHashCode(); + int hashCode = obj.member.GetHashCode(); foreach (var parameter in obj.parameters) hashCode = HashCode.Combine(hashCode, parameter.GetHashCode()); return hashCode; diff --git a/tests/FooLib/FooClass.cs b/tests/FooLib/FooClass.cs index a3afe95..8464bfd 100644 --- a/tests/FooLib/FooClass.cs +++ b/tests/FooLib/FooClass.cs @@ -32,6 +32,10 @@ namespace FooLib public class Foo : INotifyPropertyChanged { + public const int FooNumber = 42; + public const string FooString = "FOO"; + public int FooField = FooNumber; + public static int FooStaticField = -FooNumber; public Foo(IBarTransformation barTransformation) { BarTransformation = barTransformation; diff --git a/tests/tst_qtdotnet/foo.cpp b/tests/tst_qtdotnet/foo.cpp index 789ec1b..abf2336 100644 --- a/tests/tst_qtdotnet/foo.cpp +++ b/tests/tst_qtdotnet/foo.cpp @@ -17,6 +17,9 @@ struct FooPrivate final : QDotNetEventHandler QDotNetFunction<QString> bar; QDotNetFunction<void, QString> setBar; + QDotNetFunction<int, QDotNetRef> fnFooField = nullptr; + QDotNetFunction<void, QDotNetRef, int> fnSetFooField = nullptr; + QDotNetType typePropertyEvent = nullptr; void handleEvent(const QString &eventName, QDotNetObject &sender, QDotNetObject &args) override @@ -65,6 +68,54 @@ void Foo::setBar(const QString &value) method("set_Bar", d->setBar).invoke(*this, value); } +int Foo::fooNumberConst() +{ + static QDotNetFunction<int> fnFieldGet = nullptr; + static int fieldValue; + if (!fnFieldGet.isValid()) { + fieldValue = QDotNetType::typeOf<Foo>() + .staticFieldGet("FooNumber", fnFieldGet) + .invoke(nullptr); + } + return fieldValue; +} + +QString Foo::fooStringConst() +{ + static QDotNetFunction<QString> fnFieldGet = nullptr; + static QString fieldValue; + if (!fnFieldGet.isValid()) { + fieldValue = QDotNetType::typeOf<Foo>() + .staticFieldGet("FooString", fnFieldGet) + .invoke(nullptr); + } + return fieldValue; +} + +int Foo::fooStaticField() +{ + static QDotNetFunction<int> fnFieldGet = nullptr; + QDotNetType::staticFieldGet(AssemblyQualifiedName, "FooStaticField", fnFieldGet); + return fnFieldGet(); +} + +void Foo::setFooStaticField(int value) +{ + static QDotNetFunction<void, int> fnFieldSet = nullptr; + QDotNetType::staticFieldSet(AssemblyQualifiedName, "FooStaticField", fnFieldSet); + fnFieldSet(value); +} + +int Foo::fooField() +{ + return fieldGet<int>("FooField", d->fnFooField).invoke(nullptr, *this); +} + +void Foo::setFooField(int value) +{ + return fieldSet<int>("FooField", d->fnSetFooField).invoke(nullptr, *this, value); +} + IBarTransformation::IBarTransformation() : QDotNetInterface(AssemblyQualifiedName, nullptr) { setCallback<QString, QString>("Transform", diff --git a/tests/tst_qtdotnet/foo.h b/tests/tst_qtdotnet/foo.h index 6510c31..eee6d3c 100644 --- a/tests/tst_qtdotnet/foo.h +++ b/tests/tst_qtdotnet/foo.h @@ -53,6 +53,14 @@ public: [[nodiscard]] QString bar() const; void setBar(const QString &value); + static int fooNumberConst(); + static QString fooStringConst(); + static int fooStaticField(); + static void setFooStaticField(int value); + + int fooField(); + void setFooField(int value); + signals: void barChanged(); diff --git a/tests/tst_qtdotnet/tst_qtdotnet.cpp b/tests/tst_qtdotnet/tst_qtdotnet.cpp index a51dbb3..98e1bf3 100644 --- a/tests/tst_qtdotnet/tst_qtdotnet.cpp +++ b/tests/tst_qtdotnet/tst_qtdotnet.cpp @@ -137,6 +137,7 @@ private slots: void models(); void delegates(); void signalConverters(); + void fieldAccess(); #endif //TEST_FUNCTION_CALLS #ifdef TEST_APP_SHUTDOWN void appShutdown(); @@ -765,6 +766,26 @@ void tst_qtdotnet::signalConverters() skipCleanup = true; // TODO: figure out why refs are still pending here } +void tst_qtdotnet::fieldAccess() +{ + // Value-type constant + QVERIFY(Foo::fooNumberConst() == 42); + + // Ref-type constant + QVERIFY(Foo::fooStringConst() == "FOO"); + + // Static field + QVERIFY(Foo::fooStaticField() == -42); + Foo::setFooStaticField(123); + QVERIFY(Foo::fooStaticField() == 123); + + // Instance field + Foo foo; + QVERIFY(foo.fooField() == 42); + foo.setFooField(123); + QVERIFY(foo.fooField() == 123); +} + #endif //TEST_FUNCTION_CALLS #ifdef TEST_HOST_UNLOAD |