aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMiguel Costa <[email protected]>2025-05-15 16:35:39 +0200
committerMiguel Costa <[email protected]>2025-07-01 10:44:58 +0000
commit148a6b9e371b578c001a1c941dcb4d0b0f16fe30 (patch)
tree7595d5e535d18e81e66aeb3b1cdf4c5f11234c22
parent0130a895ea9b9367198d096a782a04b291b50633 (diff)
Add access to .NET constants and fieldsHEADdev
Task-number: QTBUG-134961 Change-Id: I447a125faa5e40fea07bd9957a4b80192d582f96 Reviewed-by: Karsten Heimrich <[email protected]>
-rw-r--r--include/qdotnetadapter.h52
-rw-r--r--include/qdotnetobject.h43
-rw-r--r--include/qdotnettype.h56
-rw-r--r--src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.Delegates.cs38
-rw-r--r--src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.Fields.cs144
-rw-r--r--src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.Methods.cs13
-rw-r--r--src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.Objects.cs10
-rw-r--r--src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.Test.cs34
-rw-r--r--src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.cs52
-rw-r--r--src/Qt.DotNet.Adapter/Qt/DotNet/CodeGenerator.cs105
-rw-r--r--tests/FooLib/FooClass.cs4
-rw-r--r--tests/tst_qtdotnet/foo.cpp51
-rw-r--r--tests/tst_qtdotnet/foo.h8
-rw-r--r--tests/tst_qtdotnet/tst_qtdotnet.cpp21
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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params) 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