diff --git a/src/native/QmlNet/QmlNet/types/NetPropertyInfo.cpp b/src/native/QmlNet/QmlNet/types/NetPropertyInfo.cpp index faddd309..7b6dad1a 100644 --- a/src/native/QmlNet/QmlNet/types/NetPropertyInfo.cpp +++ b/src/native/QmlNet/QmlNet/types/NetPropertyInfo.cpp @@ -66,6 +66,23 @@ void NetPropertyInfo::setNotifySignal(QSharedPointer signal) _notifySignal = std::move(signal); } +void NetPropertyInfo::addIndexParameter(QString name, QSharedPointer typeInfo) +{ + _indexParameters.append(QSharedPointer(new NetMethodInfoArguement(std::move(name), std::move(typeInfo)))); +} + +int NetPropertyInfo::getIndexParameterCount() +{ + return _indexParameters.size(); +} + +QSharedPointer NetPropertyInfo::getIndexParameter(int index) +{ + if(index < 0) return QSharedPointer(nullptr); + if(index >= _indexParameters.length()) return QSharedPointer(nullptr); + return _indexParameters.at(index); +} + extern "C" { Q_DECL_EXPORT NetPropertyInfoContainer* property_info_create(NetTypeInfoContainer* parentTypeContainer, @@ -144,6 +161,25 @@ Q_DECL_EXPORT void property_info_setNotifySignal(NetPropertyInfoContainer* conta container->property->setNotifySignal(signalContainer->signal); } +Q_DECL_EXPORT void property_info_addIndexParameter(NetPropertyInfoContainer* container, LPWCSTR name, NetTypeInfoContainer* typeInfoContainer) +{ + container->property->addIndexParameter(QString::fromUtf16(name), typeInfoContainer->netTypeInfo); +} +Q_DECL_EXPORT int property_info_getIndexParameterCount(NetPropertyInfoContainer* container) +{ + return container->property->getIndexParameterCount(); +} + +Q_DECL_EXPORT NetMethodInfoArguementContainer* property_info_getIndexParameter(NetPropertyInfoContainer* container, int index) +{ + QSharedPointer parameter = container->property->getIndexParameter(index); + if(parameter == nullptr) { + return nullptr; + } + NetMethodInfoArguementContainer* result = new NetMethodInfoArguementContainer(); + result->methodArguement = parameter; + return result; +} } diff --git a/src/native/QmlNet/QmlNet/types/NetPropertyInfo.h b/src/native/QmlNet/QmlNet/types/NetPropertyInfo.h index c8c83660..5d81e935 100644 --- a/src/native/QmlNet/QmlNet/types/NetPropertyInfo.h +++ b/src/native/QmlNet/QmlNet/types/NetPropertyInfo.h @@ -2,6 +2,7 @@ #define NET_TYPE_INFO_PROPERTY_H #include +#include class NetPropertyInfo { public: @@ -19,6 +20,9 @@ public: bool canWrite(); QSharedPointer getNotifySignal(); void setNotifySignal(QSharedPointer signal); + void addIndexParameter(QString name, QSharedPointer typeInfo); + int getIndexParameterCount(); + QSharedPointer getIndexParameter(int index); private: int _id; QSharedPointer _parentType; @@ -27,6 +31,7 @@ private: bool _canRead; bool _canWrite; QSharedPointer _notifySignal; + QList> _indexParameters; }; struct NetPropertyInfoContainer { diff --git a/src/net/Qml.Net.Tests/CodeGen/CodeGenParameterTests.cs b/src/net/Qml.Net.Tests/CodeGen/CodeGenParameterTests.cs index f5b7efb2..eb04a26d 100644 --- a/src/net/Qml.Net.Tests/CodeGen/CodeGenParameterTests.cs +++ b/src/net/Qml.Net.Tests/CodeGen/CodeGenParameterTests.cs @@ -93,6 +93,26 @@ namespace Qml.Net.Tests.CodeGen { } + public virtual void TestObjOfType(RandomType param) + { + } + + public virtual void TestStruct(RandomStruct param) + { + } + + public virtual void TestStructNullable(RandomStruct? param) + { + } + + public virtual void TestEnum(RandomEnum param) + { + } + + public virtual void TestEnumNullable(RandomEnum? param) + { + } + public virtual void MultipleParams(int param1, long param2) { } @@ -103,6 +123,21 @@ namespace Qml.Net.Tests.CodeGen } } + public class RandomType + { + } + + public struct RandomStruct + { + public int Value; + } + + public enum RandomEnum + { + Value1, + Value2 + } + [Fact] public void Can_call_method_with_bool_parameter() { @@ -192,6 +227,30 @@ namespace Qml.Net.Tests.CodeGen Test(x => x.TestObj(It.Is(v => v.Equals(o))), o); } + [Fact] + public void Can_call_method_with_typed_object_parameter() + { + var o = new RandomType(); + Test(x => x.TestObjOfType(It.Is(v => v.Equals(o))), o); + } + + [Fact] + public void Can_call_method_with_struct_parameter() + { + var o = new RandomStruct { Value = 2 }; + Test(x => x.TestStruct(It.Is(v => v.Equals(o))), o); + Test(x => x.TestStructNullable(It.Is(v => v.Equals(o))), o); + Test(x => x.TestStructNullable(It.Is(v => v == null)), (RandomStruct?)null); + } + + [Fact] + public void Can_call_method_with_enum() + { + Test(x => x.TestEnum(It.Is(v => v == RandomEnum.Value2)), RandomEnum.Value2); + Test(x => x.TestEnumNullable(It.Is(v => v == RandomEnum.Value2)), RandomEnum.Value2); + Test(x => x.TestEnumNullable(It.Is(v => v == null)), (RandomEnum?)null); + } + [Fact] public void Can_call_method_with_multiple_parameters() { diff --git a/src/net/Qml.Net.Tests/CodeGen/CodeGenPropertyTests.cs b/src/net/Qml.Net.Tests/CodeGen/CodeGenPropertyTests.cs index b2d60fd1..10dfc67f 100644 --- a/src/net/Qml.Net.Tests/CodeGen/CodeGenPropertyTests.cs +++ b/src/net/Qml.Net.Tests/CodeGen/CodeGenPropertyTests.cs @@ -12,6 +12,12 @@ namespace Qml.Net.Tests.CodeGen { public class TestObject { + public virtual int this[int index] + { + get => 0; + set { } + } + public virtual bool Bool { get; set; } public virtual bool? BoolNullable { get; set; } @@ -51,6 +57,63 @@ namespace Qml.Net.Tests.CodeGen public virtual DateTimeOffset? DateTimeNullable { get; set; } public virtual object Obj { get; set; } + + public virtual RandomType ObjTyped { get; set; } + + public virtual RandomStruct Struct { get; set; } + + public virtual RandomStruct? StructNullable { get; set; } + + public virtual RandomEnum Enum { get; set; } + + public virtual RandomEnum? EnumNullable { get; set; } + } + + public class RandomType + { + } + + public struct RandomStruct + { + public int Value; + } + + public enum RandomEnum + { + Value1, + Value2 + } + + [Fact] + public void Can_use_indexer() + { + _mock.SetupGet(x => x[10]).Returns(20); + + var del = (Net.Internal.CodeGen.CodeGen.InvokeMethodDelegate)BuildReadPropertyDelegate("Item"); + + using (var netReference = NetReference.CreateForObject(_mock.Object)) + { + using (var result = new NetVariant()) + { + del(netReference, NetVariantList.From(NetVariant.From(10)), result); + result.VariantType.Should().Be(NetVariantType.Int); + result.Int.Should().Be(20); + } + } + + _mock.Reset(); + _mock.SetupSet(x => x[10] = 20); + + del = (Net.Internal.CodeGen.CodeGen.InvokeMethodDelegate)BuildSetPropertyDelegate("Item"); + + using (var netReference = NetReference.CreateForObject(_mock.Object)) + { + using (var result = new NetVariant()) + { + del(netReference, NetVariantList.From(NetVariant.From(10), NetVariant.From(20)), null); + _mock.VerifySet(x => x[10] = 20); + } + } } [Fact] @@ -305,18 +368,84 @@ namespace Qml.Net.Tests.CodeGen TestSet(x => x.Obj, NetVariant.From(o), x => x.Obj = o); } + [Fact] + public void Can_use_prop_typed_object() + { + var o = new RandomType(); + TestGet(x => x.ObjTyped, o, result => + { + result.VariantType.Should().Be(NetVariantType.Object); + result.Instance.Instance.Should().BeSameAs(o); + }); + TestGet(x => x.ObjTyped, null, result => + { + result.VariantType.Should().Be(NetVariantType.Invalid); + }); + + TestSet(x => x.ObjTyped, NetVariant.From((RandomType)null), x => x.ObjTyped = null); + TestSet(x => x.ObjTyped, NetVariant.From(o), x => x.ObjTyped = o); + } + + [Fact] + public void Can_use_prop_struct() + { + var o = new RandomStruct(); + o.Value = 3; + TestGet(x => x.Struct, o, result => + { + result.VariantType.Should().Be(NetVariantType.Object); + result.Instance.Instance.Should().Be(o); + }); + TestGet(x => x.StructNullable, o, result => + { + result.VariantType.Should().Be(NetVariantType.Object); + result.Instance.Instance.Should().Be(o); + }); + TestGet(x => x.StructNullable, null, result => + { + result.VariantType.Should().Be(NetVariantType.Invalid); + }); + + TestSet(x => x.Struct, NetVariant.From(o), x => x.Struct = o); + TestSet(x => x.StructNullable, NetVariant.From(o), x => x.StructNullable = o); + TestSet(x => x.StructNullable, new NetVariant(), x => x.StructNullable = null); + } + + [Fact] + public void Can_use_prop_enum() + { + TestGet(x => x.Enum, RandomEnum.Value2, result => + { + result.VariantType.Should().Be(NetVariantType.Int); + result.Int.Should().Be((int)RandomEnum.Value2); + }); + TestGet(x => x.EnumNullable, RandomEnum.Value2, result => + { + result.VariantType.Should().Be(NetVariantType.Int); + result.Int.Should().Be((int)RandomEnum.Value2); + }); + TestGet(x => x.EnumNullable, null, result => + { + result.VariantType.Should().Be(NetVariantType.Invalid); + }); + + TestSet(x => x.Enum, NetVariant.From(RandomEnum.Value2), x => x.Enum = RandomEnum.Value2); + TestSet(x => x.EnumNullable, NetVariant.From(RandomEnum.Value2), x => x.EnumNullable = RandomEnum.Value2); + TestSet(x => x.EnumNullable, new NetVariant(), x => x.EnumNullable = null); + } + private void TestGet(Expression> expression, TProperty value, Action assert) { _mock.Reset(); _mock.SetupGet(expression).Returns(value); - var del = (Net.Internal.CodeGen.CodeGen.ReadPropertyDelegate)BuildReadPropertyDelegate(((MemberExpression)expression.Body).Member.Name); + var del = (Net.Internal.CodeGen.CodeGen.InvokeMethodDelegate)BuildReadPropertyDelegate(((MemberExpression)expression.Body).Member.Name); using (var netReference = NetReference.CreateForObject(_mock.Object)) { using (var result = new NetVariant()) { - del(netReference, result); + del(netReference, null, result); _mock.VerifyGet(expression); assert(result); } @@ -330,11 +459,15 @@ namespace Qml.Net.Tests.CodeGen _mock.SetupSet(expression); #pragma warning restore CS0618 - var del = (Net.Internal.CodeGen.CodeGen.SetPropertyDelegate)BuildSetPropertyDelegate(((MemberExpression)expression.Body).Member.Name); + var del = (Net.Internal.CodeGen.CodeGen.InvokeMethodDelegate)BuildSetPropertyDelegate(((MemberExpression)expression.Body).Member.Name); using (var netReference = NetReference.CreateForObject(_mock.Object)) { - del(netReference, value); + using (var list = NetVariantList.From(value)) + { + del(netReference, list, null); + } + _mock.VerifySet(verify, Times.Once); } } diff --git a/src/net/Qml.Net.Tests/CodeGen/CodeGenReturnTypeTests.cs b/src/net/Qml.Net.Tests/CodeGen/CodeGenReturnTypeTests.cs index 38ab8f2d..87660342 100644 --- a/src/net/Qml.Net.Tests/CodeGen/CodeGenReturnTypeTests.cs +++ b/src/net/Qml.Net.Tests/CodeGen/CodeGenReturnTypeTests.cs @@ -113,6 +113,30 @@ namespace Qml.Net.Tests.CodeGen { return null; } + + public virtual RandomType ReturnTypeObjectTyped() + { + return null; + } + + public virtual RandomStruct ReturnTypeStruct() + { + return new RandomStruct(); + } + + public virtual RandomStruct? ReturnTypeStructNullable() + { + return null; + } + } + + public class RandomType + { + } + + public struct RandomStruct + { + public int Value1; } [Fact] @@ -322,6 +346,41 @@ namespace Qml.Net.Tests.CodeGen }); } + [Fact] + public void Can_return_typed_object() + { + var o = new RandomType(); + Test(x => x.ReturnTypeObjectTyped(), o, result => + { + result.VariantType.Should().Be(NetVariantType.Object); + result.Instance.Instance.Should().BeSameAs(o); + }); + Test(x => x.ReturnTypeObjectTyped(), null, result => + { + result.VariantType.Should().Be(NetVariantType.Invalid); + }); + } + + [Fact] + public void Can_return_struct() + { + var value = new RandomStruct(); + Test(x => x.ReturnTypeStruct(), value, result => + { + result.VariantType.Should().Be(NetVariantType.Object); + result.Instance.Instance.Should().Be(value); + }); + Test(x => x.ReturnTypeStructNullable(), value, result => + { + result.VariantType.Should().Be(NetVariantType.Object); + result.Instance.Instance.Should().Be(value); + }); + Test(x => x.ReturnTypeStructNullable(), null, result => + { + result.VariantType.Should().Be(NetVariantType.Invalid); + }); + } + private void Test(Expression> expression, TResult value, Action assert) { _mock.Reset(); diff --git a/src/net/Qml.Net.Tests/Types/NetTypeManagerTests.cs b/src/net/Qml.Net.Tests/Types/NetTypeManagerTests.cs index 5a23ffea..0fb8cfc9 100644 --- a/src/net/Qml.Net.Tests/Types/NetTypeManagerTests.cs +++ b/src/net/Qml.Net.Tests/Types/NetTypeManagerTests.cs @@ -397,5 +397,29 @@ namespace Qml.Net.Tests.Types type1.Id.Should().NotBe(type2.Id); } + + public class TestType19 + { + public object this[int index] + { + get => null; + set { } + } + } + + [Fact] + public void Can_get_index_parameters() + { + var type = NetTypeManager.GetTypeInfo(); + type.EnsureLoaded(); + + var prop = type.GetProperty(0); + prop.Name.Should().Be("Item"); + + var indexParameters = prop.GetAllIndexParameters(); + indexParameters.Count.Should().Be(1); + indexParameters[0].Name.Should().Be("index"); + indexParameters[0].Type.FullTypeName.Should().Be(typeof(int).AssemblyQualifiedName); + } } } \ No newline at end of file diff --git a/src/net/Qml.Net/Internal/CodeGen/CodeGen.Methods.cs b/src/net/Qml.Net/Internal/CodeGen/CodeGen.Methods.cs index c1ba7d4c..8e7417d8 100644 --- a/src/net/Qml.Net/Internal/CodeGen/CodeGen.Methods.cs +++ b/src/net/Qml.Net/Internal/CodeGen/CodeGen.Methods.cs @@ -442,15 +442,7 @@ namespace Qml.Net.Internal.CodeGen { using (var variant = list.Get(index)) { - if (variant == null || variant.VariantType == NetVariantType.Invalid) - { - return null; - } - - using (var instance = variant.Instance) - { - return instance?.Instance; - } + return variant?.AsObject(); } } } diff --git a/src/net/Qml.Net/Internal/CodeGen/CodeGen.cs b/src/net/Qml.Net/Internal/CodeGen/CodeGen.cs index b241e6d4..9b05c7eb 100644 --- a/src/net/Qml.Net/Internal/CodeGen/CodeGen.cs +++ b/src/net/Qml.Net/Internal/CodeGen/CodeGen.cs @@ -14,25 +14,37 @@ namespace Qml.Net.Internal.CodeGen { public delegate void InvokeMethodDelegate(NetReference reference, NetVariantList parameters, NetVariant result); - public delegate void ReadPropertyDelegate(NetReference reference, NetVariant result); - - public delegate void SetPropertyDelegate(NetReference reference, NetVariant value); - public static InvokeMethodDelegate BuildInvokeMethodDelegate(NetMethodInfo methodInfo) { var invokeType = Type.GetType(methodInfo.ParentType.FullTypeName); - var invokeMethod = invokeType.GetMethod(methodInfo.MethodName); + var parameterTypes = new List(); + for (var x = 0; x < methodInfo.ParameterCount; x++) + { + using (var p = methodInfo.GetParameter(x)) + using (var t = p.Type) + { + parameterTypes.Add(Type.GetType(t.FullTypeName)); + } + } + var invokeMethod = invokeType.GetMethod(methodInfo.MethodName, parameterTypes.ToArray()); + return BuildInvokeMethodDelegate(invokeMethod); + } + + public static InvokeMethodDelegate BuildInvokeMethodDelegate(MethodInfo methodInfo) + { + var invokeType = methodInfo.DeclaringType; var dynamicMethod = new DynamicMethod( "method", typeof(void), new[] { typeof(NetReference), typeof(NetVariantList), typeof(NetVariant) }); - if (invokeMethod.ReturnType != null && invokeMethod.ReturnType != typeof(void)) + bool box = false; + if (methodInfo.ReturnType != null && methodInfo.ReturnType != typeof(void)) { MethodInfo returnMethod = null; var isNullable = false; - switch (GetPrefVariantType(invokeMethod.ReturnType, ref isNullable)) + switch (GetPrefVariantType(methodInfo.ReturnType, ref isNullable)) { case NetVariantType.Bool: returnMethod = isNullable ? LoadMethods.LoadBoolNullableMethod : LoadMethods.LoadBoolMethod; @@ -66,6 +78,7 @@ namespace Qml.Net.Internal.CodeGen break; case NetVariantType.Object: returnMethod = LoadMethods.LoadObjectMethod; + box = methodInfo.ReturnType.IsValueType; break; case NetVariantType.JsValue: throw new NotImplementedException(); @@ -82,9 +95,15 @@ namespace Qml.Net.Internal.CodeGen il.Emit(OpCodes.Callvirt, GenericMethods.InstanceProperty.GetMethod); il.Emit(OpCodes.Castclass, invokeType); - InvokeParameters(il, invokeMethod); + InvokeParameters(il, methodInfo); + + il.Emit(OpCodes.Callvirt, methodInfo); + + if (box) + { + il.Emit(OpCodes.Box, methodInfo.ReturnType); + } - il.Emit(OpCodes.Callvirt, invokeMethod); il.Emit(OpCodes.Call, returnMethod); il.Emit(OpCodes.Ret); @@ -96,9 +115,9 @@ namespace Qml.Net.Internal.CodeGen il.Emit(OpCodes.Callvirt, GenericMethods.InstanceProperty.GetMethod); il.Emit(OpCodes.Castclass, invokeType); - InvokeParameters(il, invokeMethod); + InvokeParameters(il, methodInfo); - il.Emit(OpCodes.Callvirt, invokeMethod); + il.Emit(OpCodes.Callvirt, methodInfo); il.Emit(OpCodes.Ret); } @@ -106,141 +125,42 @@ namespace Qml.Net.Internal.CodeGen return (InvokeMethodDelegate)dynamicMethod.CreateDelegate(typeof(InvokeMethodDelegate)); } - public static ReadPropertyDelegate BuildReadPropertyDelegate(NetPropertyInfo propertyInfo) + public static InvokeMethodDelegate BuildReadPropertyDelegate(NetPropertyInfo propertyInfo) { var invokeType = Type.GetType(propertyInfo.ParentType.FullTypeName); - var invokeProperty = invokeType.GetProperty(propertyInfo.Name); - MethodInfo loadMethod = null; - var isNullable = false; - switch (GetPrefVariantType(invokeProperty.PropertyType, ref isNullable)) + var parameterTypes = new List(); + for (var x = 0; x < propertyInfo.IndexParameterCount; x++) { - case NetVariantType.Bool: - loadMethod = isNullable ? LoadMethods.LoadBoolNullableMethod : LoadMethods.LoadBoolMethod; - break; - case NetVariantType.Char: - loadMethod = isNullable ? LoadMethods.LoadCharNullableMethod : LoadMethods.LoadCharMethod; - break; - case NetVariantType.Int: - loadMethod = isNullable ? LoadMethods.LoadIntNullableMethod : LoadMethods.LoadIntMethod; - break; - case NetVariantType.UInt: - loadMethod = isNullable ? LoadMethods.LoadUIntNullableMethod : LoadMethods.LoadUIntMethod; - break; - case NetVariantType.Long: - loadMethod = isNullable ? LoadMethods.LoadLongNullableMethod : LoadMethods.LoadLongMethod; - break; - case NetVariantType.ULong: - loadMethod = isNullable ? LoadMethods.LoadULongNullableMethod : LoadMethods.LoadULongMethod; - break; - case NetVariantType.Float: - loadMethod = isNullable ? LoadMethods.LoadFloatNullableMethod : LoadMethods.LoadFloatMethod; - break; - case NetVariantType.Double: - loadMethod = isNullable ? LoadMethods.LoadDoubleNullableMethod : LoadMethods.LoadDoubleMethod; - break; - case NetVariantType.String: - loadMethod = LoadMethods.LoadStringMethod; - break; - case NetVariantType.DateTime: - loadMethod = isNullable ? LoadMethods.LoadDateTimeNullableMethod : LoadMethods.LoadDateTimeMethod; - break; - case NetVariantType.Object: - loadMethod = LoadMethods.LoadObjectMethod; - break; - case NetVariantType.JsValue: - throw new NotImplementedException(); - case NetVariantType.Invalid: - throw new Exception("invalid type"); - default: - throw new Exception("unknown type"); + using (var p = propertyInfo.GetIndexParameter(x)) + using (var t = p.Type) + { + parameterTypes.Add(Type.GetType(t.FullTypeName)); + } } - var dynamicMethod = new DynamicMethod( - "method", - typeof(void), - new[] { typeof(NetReference), typeof(NetVariant) }); + var invokeProperty = invokeType.GetProperty(propertyInfo.Name); - var il = dynamicMethod.GetILGenerator(); - - il.Emit(OpCodes.Ldarg_1); // result - il.Emit(OpCodes.Ldarg_0); // net reference - il.Emit(OpCodes.Callvirt, GenericMethods.InstanceProperty.GetMethod); - il.Emit(OpCodes.Castclass, invokeType); - il.Emit(OpCodes.Callvirt, invokeProperty.GetMethod); - il.Emit(OpCodes.Call, loadMethod); - il.Emit(OpCodes.Ret); - - return (ReadPropertyDelegate)dynamicMethod.CreateDelegate(typeof(ReadPropertyDelegate)); + return BuildInvokeMethodDelegate(invokeProperty.GetMethod); } - public static SetPropertyDelegate BuildSetPropertyDelegate(NetPropertyInfo propertyInfo) + public static InvokeMethodDelegate BuildSetPropertyDelegate(NetPropertyInfo propertyInfo) { var invokeType = Type.GetType(propertyInfo.ParentType.FullTypeName); - var invokeProperty = invokeType.GetProperty(propertyInfo.Name); - MethodInfo getMethod = null; - var isNullable = false; - switch (GetPrefVariantType(invokeProperty.PropertyType, ref isNullable)) + var parameterTypes = new List(); + for (var x = 0; x < propertyInfo.IndexParameterCount; x++) { - case NetVariantType.Bool: - getMethod = isNullable ? GetMethods.BoolNullableMethod : GetMethods.BoolMethod; - break; - case NetVariantType.Char: - getMethod = isNullable ? GetMethods.CharNullableMethod : GetMethods.CharMethod; - break; - case NetVariantType.Int: - getMethod = isNullable ? GetMethods.IntNullableMethod : GetMethods.IntMethod; - break; - case NetVariantType.UInt: - getMethod = isNullable ? GetMethods.UIntNullableMethod : GetMethods.UIntMethod; - break; - case NetVariantType.Long: - getMethod = isNullable ? GetMethods.LongNullableMethod : GetMethods.LongMethod; - break; - case NetVariantType.ULong: - getMethod = isNullable ? GetMethods.ULongNullableMethod : GetMethods.LongMethod; - break; - case NetVariantType.Float: - getMethod = isNullable ? GetMethods.FloatNullableMethod : GetMethods.FloatMethod; - break; - case NetVariantType.Double: - getMethod = isNullable ? GetMethods.DoubleNullableMethod : GetMethods.DoubleMethod; - break; - case NetVariantType.String: - getMethod = GetMethods.StringMethod; - break; - case NetVariantType.DateTime: - getMethod = isNullable ? GetMethods.DateTimeNullableMethod : GetMethods.DateTimeMethod; - break; - case NetVariantType.Object: - getMethod = GetMethods.ObjMethod; - break; - case NetVariantType.JsValue: - throw new NotImplementedException(); - case NetVariantType.Invalid: - throw new Exception("invalid type"); - default: - throw new Exception("unknown type"); + using (var p = propertyInfo.GetIndexParameter(x)) + using (var t = p.Type) + { + parameterTypes.Add(Type.GetType(t.FullTypeName)); + } } - var dynamicMethod = new DynamicMethod( - "method", - typeof(void), - new[] { typeof(NetReference), typeof(NetVariant) }); + var invokeProperty = invokeType.GetProperty(propertyInfo.Name); - var il = dynamicMethod.GetILGenerator(); - - il.Emit(OpCodes.Ldarg_0); // net reference - il.Emit(OpCodes.Callvirt, GenericMethods.InstanceProperty.GetMethod); - il.Emit(OpCodes.Castclass, invokeType); - il.Emit(OpCodes.Ldarg_1); // variant - il.Emit(OpCodes.Call, getMethod); - il.Emit(OpCodes.Callvirt, invokeProperty.SetMethod); - - il.Emit(OpCodes.Ret); - - return (SetPropertyDelegate)dynamicMethod.CreateDelegate(typeof(SetPropertyDelegate)); + return BuildInvokeMethodDelegate(invokeProperty.SetMethod); } private static void InvokeParameters(ILGenerator il, MethodInfo methodInfo) @@ -253,6 +173,7 @@ namespace Qml.Net.Internal.CodeGen MethodInfo returnMethod = null; var isNullable = false; + var unbox = false; switch (GetPrefVariantType(parameter.ParameterType, ref isNullable)) { case NetVariantType.Bool: @@ -287,6 +208,7 @@ namespace Qml.Net.Internal.CodeGen break; case NetVariantType.Object: returnMethod = ListMethods.ObjectAtMethod; + unbox = parameter.ParameterType.IsValueType; break; case NetVariantType.JsValue: throw new NotImplementedException(); @@ -297,6 +219,12 @@ namespace Qml.Net.Internal.CodeGen } il.Emit(OpCodes.Call, returnMethod); + + if (unbox) + { + il.Emit(OpCodes.Unbox_Any, parameter.ParameterType); + } + parameterIndex++; } } @@ -333,6 +261,11 @@ namespace Qml.Net.Internal.CodeGen // ReSharper restore TailRecursiveCall } + if (typeInfo.IsEnum) + { + return GetPrefVariantType(Enum.GetUnderlyingType(typeInfo), ref isNullable); + } + return NetVariantType.Object; } } diff --git a/src/net/Qml.Net/Internal/DefaultCallbacks.cs b/src/net/Qml.Net/Internal/DefaultCallbacks.cs index cfcfb246..97cdc920 100644 --- a/src/net/Qml.Net/Internal/DefaultCallbacks.cs +++ b/src/net/Qml.Net/Internal/DefaultCallbacks.cs @@ -15,6 +15,10 @@ namespace Qml.Net.Internal { internal class DefaultCallbacks : ICallbacks { + private Dictionary _cachedReadProperties = new Dictionary(); + private Dictionary _cachedSetProperties = new Dictionary(); + private Dictionary _cachedInvokeMethods = new Dictionary(); + public bool IsTypeValid(string typeName) { var t = Type.GetType(typeName); @@ -174,6 +178,11 @@ namespace Qml.Net.Internal propertyInfo.CanWrite, notifySignal)) { + foreach (var indexParameter in propertyInfo.GetIndexParameters()) + { + property.AddIndexParameter(indexParameter.Name, NetTypeManager.GetTypeInfo(indexParameter.ParameterType)); + } + type.AddProperty(property); } } @@ -221,22 +230,21 @@ namespace Qml.Net.Internal using (var indexParameter = ip != IntPtr.Zero ? new NetVariant(ip) : null) using (var result = new NetVariant(r)) { - var o = target.Instance; - - var propertInfo = o.GetType() - .GetProperty(property.Name, BindingFlags.Instance | BindingFlags.Public); - if (propertInfo == null) - throw new InvalidOperationException($"Invalid property {property.Name}"); - - if (indexParameter != null) + CodeGen.CodeGen.InvokeMethodDelegate del; + if (!_cachedReadProperties.TryGetValue(property.Id, out del)) { - object indexParameterValue = null; - Helpers.Unpackvalue(ref indexParameterValue, indexParameter); - Helpers.PackValue(propertInfo.GetValue(o, new[] { indexParameterValue }), result); + del = CodeGen.CodeGen.BuildReadPropertyDelegate(property); + _cachedReadProperties[property.Id] = del; } - else + + using (var list = indexParameter != null ? new NetVariantList() : null) { - Helpers.PackValue(propertInfo.GetValue(o), result); + if (indexParameter != null) + { + list.Add(indexParameter); + } + + del(target, list, result); } } } @@ -248,29 +256,22 @@ namespace Qml.Net.Internal using (var indexParameter = ip != IntPtr.Zero ? new NetVariant(ip) : null) using (var value = new NetVariant(v)) { - var o = target.Instance; - - var propertInfo = o.GetType() - .GetProperty(property.Name, BindingFlags.Instance | BindingFlags.Public); - - if (propertInfo == null) - throw new InvalidOperationException($"Invalid property {property.Name}"); - - object newValue = null; - Helpers.Unpackvalue(ref newValue, value); - - if (indexParameter != null) + CodeGen.CodeGen.InvokeMethodDelegate del; + if (!_cachedSetProperties.TryGetValue(property.Id, out del)) { - // TODO: Type coercion? - object indexParameterValue = null; - Helpers.Unpackvalue(ref indexParameterValue, indexParameter); - propertInfo.SetValue(o, newValue, new[] { indexParameterValue }); + del = CodeGen.CodeGen.BuildSetPropertyDelegate(property); + _cachedSetProperties[property.Id] = del; } - else + + using (var list = new NetVariantList()) { - var propertyType = propertInfo.PropertyType; - var underlyingType = Nullable.GetUnderlyingType(propertyType); - propertInfo.SetValue(o, Converter.To(newValue, underlyingType ?? propertyType)); + if (indexParameter != null) + { + list.Add(indexParameter); + } + + list.Add(value); + del(target, list, null); } } } @@ -282,84 +283,14 @@ namespace Qml.Net.Internal using (var parameters = new NetVariantList(p)) using (var result = r != IntPtr.Zero ? new NetVariant(r) : null) { - var instance = target.Instance; - - List methodParameters = null; - - if (parameters.Count > 0) + CodeGen.CodeGen.InvokeMethodDelegate del; + if (!_cachedInvokeMethods.TryGetValue(method.Id, out del)) { - methodParameters = new List(); - var parameterCount = parameters.Count; - for (var x = 0; x < parameterCount; x++) - { - object v = null; - Helpers.Unpackvalue(ref v, parameters.Get(x)); - methodParameters.Add(v); - } + del = CodeGen.CodeGen.BuildInvokeMethodDelegate(method); + _cachedInvokeMethods[method.Id] = del; } - MethodInfo methodInfo = null; - var methodName = method.MethodName; - var methods = instance.GetType() - .GetMethods(BindingFlags.Instance | BindingFlags.Public) - .Where(x => x.Name == methodName) - .ToList(); - - if (methods.Count == 1) - { - methodInfo = methods[0]; - } - else if (methods.Count > 1) - { - // This is an overload. - - // TODO: Make this more performant. https://github.com/pauldotknopf/Qml.Net/issues/39 - - // Get all the parameters for the method we are invoking. - var parameterTypes = method.GetAllParameters().Select(x => x.Type.FullTypeName).ToList(); - - // And find a good method to invoke. - foreach (var potentialMethod in methods) - { - var potentialMethodParameters = potentialMethod.GetParameters(); - if (potentialMethodParameters.Length != parameterTypes.Count) - { - continue; - } - - bool valid = true; - for (var x = 0; x < potentialMethodParameters.Length; x++) - { - if (potentialMethodParameters[x].ParameterType.AssemblyQualifiedName != parameterTypes[x]) - { - valid = false; - break; - } - } - - if (valid) - { - methodInfo = potentialMethod; - break; - } - } - } - - if (methodInfo == null) - { - throw new InvalidOperationException($"Invalid method name {method.MethodName}"); - } - - var returnObject = methodInfo.Invoke(instance, methodParameters?.ToArray()); - - if (result == null) - { - // this method doesn't have return type - } - else - { - Helpers.PackValue(returnObject, result); - } + del(target, parameters, result); } } diff --git a/src/net/Qml.Net/Internal/Qml/NetVariant.cs b/src/net/Qml.Net/Internal/Qml/NetVariant.cs index 4efb01c4..7cc6f890 100644 --- a/src/net/Qml.Net/Internal/Qml/NetVariant.cs +++ b/src/net/Qml.Net/Internal/Qml/NetVariant.cs @@ -140,6 +140,45 @@ namespace Qml.Net.Internal.Qml Interop.NetVariant.Clear(Handle); } + public object AsObject() + { + switch (VariantType) + { + case NetVariantType.Invalid: + return null; + case NetVariantType.Bool: + return Bool; + case NetVariantType.Char: + return Char; + case NetVariantType.Int: + return Int; + case NetVariantType.UInt: + return UInt; + case NetVariantType.Long: + return Long; + case NetVariantType.ULong: + return ULong; + case NetVariantType.Float: + return Float; + case NetVariantType.Double: + return Double; + case NetVariantType.String: + return String; + case NetVariantType.DateTime: + return DateTime; + case NetVariantType.Object: + using (var instance = Instance) + { + return instance.Instance; + } + + case NetVariantType.JsValue: + return JsValue.AsDynamic(); + default: + throw new NotImplementedException($"unhandled type {VariantType}"); + } + } + protected override void DisposeUnmanaged(IntPtr ptr) { Interop.NetVariant.Destroy(ptr); diff --git a/src/net/Qml.Net/Internal/Types/NetPropertyInfo.cs b/src/net/Qml.Net/Internal/Types/NetPropertyInfo.cs index 3573049a..c8c85b60 100644 --- a/src/net/Qml.Net/Internal/Types/NetPropertyInfo.cs +++ b/src/net/Qml.Net/Internal/Types/NetPropertyInfo.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Runtime.InteropServices; namespace Qml.Net.Internal.Types @@ -70,6 +71,31 @@ namespace Qml.Net.Internal.Types } } + public void AddIndexParameter(string name, NetTypeInfo type) + { + Interop.NetPropertyInfo.AddIndexParameter(Handle, name, type.Handle); + } + + public int IndexParameterCount => Interop.NetPropertyInfo.GetIndexParameterCount(Handle); + + public NetMethodInfoParameter GetIndexParameter(int index) + { + var result = Interop.NetPropertyInfo.GetIndexParameter(Handle, index); + if (result == IntPtr.Zero) return null; + return new NetMethodInfoParameter(result); + } + + public List GetAllIndexParameters() + { + var result = new List(); + var count = IndexParameterCount; + for (var x = 0; x < count; x++) + { + result.Add(GetIndexParameter(x)); + } + return result; + } + protected override void DisposeUnmanaged(IntPtr ptr) { Interop.NetPropertyInfo.Destroy(ptr); @@ -133,5 +159,20 @@ namespace Qml.Net.Internal.Types public SetNotifySignalDel SetNotifySignal { get; set; } public delegate void SetNotifySignalDel(IntPtr property, IntPtr signal); + + [NativeSymbol(Entrypoint = "property_info_addIndexParameter")] + public AddIndexParameterDel AddIndexParameter { get; set; } + + public delegate void AddIndexParameterDel(IntPtr method, [MarshalAs(UnmanagedType.LPWStr)]string name, IntPtr type); + + [NativeSymbol(Entrypoint = "property_info_getIndexParameterCount")] + public GetIndexParameterCountDel GetIndexParameterCount { get; set; } + + public delegate int GetIndexParameterCountDel(IntPtr method); + + [NativeSymbol(Entrypoint = "property_info_getIndexParameter")] + public GetIndexParameterDel GetIndexParameter { get; set; } + + public delegate IntPtr GetIndexParameterDel(IntPtr method, int index); } } \ No newline at end of file