Cached delegates for perf increases.

This commit is contained in:
Paul Knopf 2019-02-17 23:19:38 -05:00
parent 864b2c4cf8
commit db701da9ec
11 changed files with 503 additions and 251 deletions

View file

@ -66,6 +66,23 @@ void NetPropertyInfo::setNotifySignal(QSharedPointer<NetSignalInfo> signal)
_notifySignal = std::move(signal);
}
void NetPropertyInfo::addIndexParameter(QString name, QSharedPointer<NetTypeInfo> typeInfo)
{
_indexParameters.append(QSharedPointer<NetMethodInfoArguement>(new NetMethodInfoArguement(std::move(name), std::move(typeInfo))));
}
int NetPropertyInfo::getIndexParameterCount()
{
return _indexParameters.size();
}
QSharedPointer<NetMethodInfoArguement> NetPropertyInfo::getIndexParameter(int index)
{
if(index < 0) return QSharedPointer<NetMethodInfoArguement>(nullptr);
if(index >= _indexParameters.length()) return QSharedPointer<NetMethodInfoArguement>(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<NetMethodInfoArguement> parameter = container->property->getIndexParameter(index);
if(parameter == nullptr) {
return nullptr;
}
NetMethodInfoArguementContainer* result = new NetMethodInfoArguementContainer();
result->methodArguement = parameter;
return result;
}
}

View file

@ -2,6 +2,7 @@
#define NET_TYPE_INFO_PROPERTY_H
#include <QmlNet/types/NetTypeInfo.h>
#include <QmlNet/types/NetMethodInfo.h>
class NetPropertyInfo {
public:
@ -19,6 +20,9 @@ public:
bool canWrite();
QSharedPointer<NetSignalInfo> getNotifySignal();
void setNotifySignal(QSharedPointer<NetSignalInfo> signal);
void addIndexParameter(QString name, QSharedPointer<NetTypeInfo> typeInfo);
int getIndexParameterCount();
QSharedPointer<NetMethodInfoArguement> getIndexParameter(int index);
private:
int _id;
QSharedPointer<NetTypeInfo> _parentType;
@ -27,6 +31,7 @@ private:
bool _canRead;
bool _canWrite;
QSharedPointer<NetSignalInfo> _notifySignal;
QList<QSharedPointer<NetMethodInfoArguement>> _indexParameters;
};
struct NetPropertyInfoContainer {

View file

@ -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<object>(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<RandomType>(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<RandomStruct>(v => v.Equals(o))), o);
Test(x => x.TestStructNullable(It.Is<RandomStruct>(v => v.Equals(o))), o);
Test(x => x.TestStructNullable(It.Is<RandomStruct?>(v => v == null)), (RandomStruct?)null);
}
[Fact]
public void Can_call_method_with_enum()
{
Test(x => x.TestEnum(It.Is<RandomEnum>(v => v == RandomEnum.Value2)), RandomEnum.Value2);
Test(x => x.TestEnumNullable(It.Is<RandomEnum?>(v => v == RandomEnum.Value2)), RandomEnum.Value2);
Test(x => x.TestEnumNullable(It.Is<RandomEnum?>(v => v == null)), (RandomEnum?)null);
}
[Fact]
public void Can_call_method_with_multiple_parameters()
{

View file

@ -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<TProperty>(Expression<Func<TestObject, TProperty>> expression, TProperty value, Action<NetVariant> 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);
}
}

View file

@ -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<TResult>(Expression<Func<TestObject, TResult>> expression, TResult value, Action<NetVariant> assert)
{
_mock.Reset();

View file

@ -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<TestType19>();
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);
}
}
}

View file

@ -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();
}
}
}

View file

@ -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<Type>();
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<Type>();
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<Type>();
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;
}
}

View file

@ -15,6 +15,10 @@ namespace Qml.Net.Internal
{
internal class DefaultCallbacks : ICallbacks
{
private Dictionary<int, CodeGen.CodeGen.InvokeMethodDelegate> _cachedReadProperties = new Dictionary<int, CodeGen.CodeGen.InvokeMethodDelegate>();
private Dictionary<int, CodeGen.CodeGen.InvokeMethodDelegate> _cachedSetProperties = new Dictionary<int, CodeGen.CodeGen.InvokeMethodDelegate>();
private Dictionary<int, CodeGen.CodeGen.InvokeMethodDelegate> _cachedInvokeMethods = new Dictionary<int, CodeGen.CodeGen.InvokeMethodDelegate>();
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<object> methodParameters = null;
if (parameters.Count > 0)
CodeGen.CodeGen.InvokeMethodDelegate del;
if (!_cachedInvokeMethods.TryGetValue(method.Id, out del))
{
methodParameters = new List<object>();
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);
}
}

View file

@ -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);

View file

@ -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<NetMethodInfoParameter> GetAllIndexParameters()
{
var result = new List<NetMethodInfoParameter>();
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);
}
}