From bd32b7434725376a6a921a7edf107bc521652549 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 6 Sep 2018 16:20:23 +0200 Subject: [PATCH] Add support for delegates as return values in protocol members. Fixes #4102. (#4758) * Add support for delegates as return values in protocol members. Fixes #4102. This required a few changes: * The generator now emits the DelegateProxy attribute for property getters in protocol interfaces. * The generator now emits the DelegateProxy attribute in ProtocolMember attributes (and the ProtocolMember attribute has been extended with additional properties for this purpose). * The generator now emits the BlockProxy attribute for the parameter in property setters. * The generator now emits the BlockProxy attribute in ProtocolMember attributes for property setters. * The static registrar now emits the metadata token for the DelegateProxy.DelegateType property into the generated code so that the DelegateProxy attribute itself isn't needed at runtime. This is required when the dynamic registrar has been optimized away. Fixes https://github.com/xamarin/xamarin-macios/issues/4102. * [tests] Update MX4105 test to expect new warnings. --- docs/website/mmp-errors.md | 14 ++ docs/website/mtouch-errors.md | 14 ++ runtime/delegates.t4 | 3 +- runtime/runtime.m | 4 +- runtime/trampolines.m | 2 +- runtime/xamarin/runtime.h | 2 +- src/Foundation/ProtocolAttribute.cs | 1 + src/ObjCRuntime/Blocks.cs | 66 +++++-- src/ObjCRuntime/ErrorHelper.cs | 21 +++ src/ObjCRuntime/Runtime.cs | 105 +++++++---- src/generator.cs | 30 +++- tests/bindings-test/ApiDefinition.cs | 55 ++++++ tests/bindings-test/RegistrarBindingTest.cs | 144 +++++++++++++++ tests/common/BundlerTest.cs | 5 +- tests/mtouch/RegistrarTest.cs | 39 +++++ tests/test-libraries/libtest.h | 28 ++- tests/test-libraries/libtest.m | 68 ++++++++ tools/common/StaticRegistrar.cs | 184 +++++++++++++++----- 18 files changed, 684 insertions(+), 101 deletions(-) diff --git a/docs/website/mmp-errors.md b/docs/website/mmp-errors.md index c0c832f675..862f06b110 100644 --- a/docs/website/mmp-errors.md +++ b/docs/website/mmp-errors.md @@ -378,6 +378,20 @@ will be shown. Reference: https://github.com/xamarin/xamarin-macios/issues/4072 +### MM4176: Unable to locate the delegate to block conversion type for the return value of the method {method}. + +This is a warning indicating that the static registrar couldn't find the type +used to convert a delegate to an Objective-C block. An attempt will be made at +runtime to find the method, but it will likely fail as well (with an MM8009 +exception). + +One possible reason for this warning is when manually writing bindings for API +that uses blocks. It's recommended to use a binding project to bind +Objective-C code, in particular when it involves blocks, since it's quite +complicated to get it right when doing it manually. + +If this is not the case, please file a bug at [https://bugzilla.xamarin.com](https://bugzilla.xamarin.com/enter_bug.cgi?product=Xamarin.Mac) with a test case. + # MM5xxx: GCC and toolchain ## MM51xx: compilation diff --git a/docs/website/mtouch-errors.md b/docs/website/mtouch-errors.md index f1391feada..8ac2322efd 100644 --- a/docs/website/mtouch-errors.md +++ b/docs/website/mtouch-errors.md @@ -1787,6 +1787,20 @@ will be shown. Reference: https://github.com/xamarin/xamarin-macios/issues/4072 +### MT4176: Unable to locate the delegate to block conversion type for the return value of the method {method}. + +This is a warning indicating that the static registrar couldn't find the type +used to convert a delegate to an Objective-C block. An attempt will be made at +runtime to find the method, but it will likely fail as well (with an MT8009 +exception). + +One possible reason for this warning is when manually writing bindings for API +that uses blocks. It's recommended to use a binding project to bind +Objective-C code, in particular when it involves blocks, since it's quite +complicated to get it right when doing it manually. + +If this is not the case, please file a bug at [https://bugzilla.xamarin.com](https://bugzilla.xamarin.com/enter_bug.cgi?product=iOS) with a test case. + # MT5xxx: GCC and toolchain error messages ### MT51xx: Compilation diff --git a/runtime/delegates.t4 b/runtime/delegates.t4 index 0cd5370e8f..464b0ba1af 100644 --- a/runtime/delegates.t4 +++ b/runtime/delegates.t4 @@ -69,7 +69,8 @@ new XDelegate ("id", "IntPtr", "xamarin_create_delegate_proxy", "MonoObject *", "IntPtr", "method", "MonoObject *", "IntPtr", "block", - "const char *", "IntPtr", "signature" + "const char *", "IntPtr", "signature", + "unsigned int", "uint", "token_ref" ) { WrappedManagedFunction = "CreateDelegateProxy", OnlyDynamicUsage = false, diff --git a/runtime/runtime.m b/runtime/runtime.m index 67610fa2e1..91cbc21de8 100644 --- a/runtime/runtime.m +++ b/runtime/runtime.m @@ -2187,10 +2187,10 @@ xamarin_get_delegate_for_block_parameter (MonoMethod *method, guint32 token_ref, } id -xamarin_get_block_for_delegate (MonoMethod *method, MonoObject *delegate, const char *signature, guint32 *exception_gchandle) +xamarin_get_block_for_delegate (MonoMethod *method, MonoObject *delegate, const char *signature, guint32 token_ref, guint32 *exception_gchandle) { // COOP: accesses managed memory: unsafe mode. - return delegates.create_delegate_proxy ((MonoObject *) mono_method_get_object (mono_domain_get (), method, NULL), delegate, signature, exception_gchandle); + return delegates.create_delegate_proxy ((MonoObject *) mono_method_get_object (mono_domain_get (), method, NULL), delegate, signature, token_ref, exception_gchandle); } void diff --git a/runtime/trampolines.m b/runtime/trampolines.m index 6f9c6d8c1d..0beb2ad60d 100644 --- a/runtime/trampolines.m +++ b/runtime/trampolines.m @@ -60,7 +60,7 @@ xamarin_marshal_return_value (MonoType *mtype, const char *type, MonoObject *ret case _C_PTR: { MonoClass *klass = mono_class_from_mono_type (mtype); if (mono_class_is_delegate (klass)) { - return xamarin_get_block_for_delegate (method, retval, NULL, exception_gchandle); + return xamarin_get_block_for_delegate (method, retval, NULL, INVALID_TOKEN_REF, exception_gchandle); } else { return *(void **) mono_object_unbox (retval); } diff --git a/runtime/xamarin/runtime.h b/runtime/xamarin/runtime.h index 3f951e0c45..25f93ee837 100644 --- a/runtime/xamarin/runtime.h +++ b/runtime/xamarin/runtime.h @@ -176,7 +176,7 @@ MonoType * xamarin_get_parameter_type (MonoMethod *managed_method, int index); MonoObject * xamarin_get_nsobject_with_type_for_ptr (id self, bool owns, MonoType* type, SEL selector, MonoMethod *managed_method, guint32 *exception_gchandle); MonoObject * xamarin_get_nsobject_with_type_for_ptr_created (id self, bool owns, MonoType *type, int32_t *created, SEL selector, MonoMethod *managed_method, guint32 *exception_gchandle); int * xamarin_get_delegate_for_block_parameter (MonoMethod *method, guint32 token_ref, int par, void *nativeBlock, guint32 *exception_gchandle); -id xamarin_get_block_for_delegate (MonoMethod *method, MonoObject *delegate, const char *signature /* NULL allowed, but requires the dynamic registrar at runtime to compute */, guint32 *exception_gchandle); +id xamarin_get_block_for_delegate (MonoMethod *method, MonoObject *delegate, const char *signature /* NULL allowed, but requires the dynamic registrar at runtime to compute */, guint32 token_ref /* INVALID_TOKEN_REF allowed, but requires the dynamic registrar at runtime */, guint32 *exception_gchandle); id xamarin_get_nsobject_handle (MonoObject *obj); void xamarin_set_nsobject_handle (MonoObject *obj, id handle); uint8_t xamarin_get_nsobject_flags (MonoObject *obj); diff --git a/src/Foundation/ProtocolAttribute.cs b/src/Foundation/ProtocolAttribute.cs index a0f18c8dc1..ead18ef44d 100644 --- a/src/Foundation/ProtocolAttribute.cs +++ b/src/Foundation/ProtocolAttribute.cs @@ -59,6 +59,7 @@ namespace Foundation { public string Name { get; set; } public string Selector { get; set; } public Type ReturnType { get; set; } + public Type ReturnTypeDelegateProxy { get; set; } public Type[] ParameterType { get; set; } public bool[] ParameterByRef { get; set; } public Type[] ParameterBlockProxy { get; set; } diff --git a/src/ObjCRuntime/Blocks.cs b/src/ObjCRuntime/Blocks.cs index 1a30b8ef40..69075777fe 100644 --- a/src/ObjCRuntime/Blocks.cs +++ b/src/ObjCRuntime/Blocks.cs @@ -247,8 +247,50 @@ namespace ObjCRuntime { return descriptor->copy_helper == ((BlockDescriptor *) literal->block_descriptor)->copy_helper; } - [BindingImpl (BindingImplOptions.Optimizable)] - internal static IntPtr GetBlockForDelegate (MethodInfo minfo, object @delegate, string signature) + static Type GetDelegateProxyType (MethodInfo minfo, uint token_ref, out MethodInfo baseMethod) + { + // A mirror of this method is also implemented in StaticRegistrar:GetDelegateProxyType + // If this method is changed, that method will probably have to be updated too (tests!!!) + baseMethod = null; + + if (token_ref != Runtime.INVALID_TOKEN_REF) + return (Type) Class.ResolveTokenReference (token_ref, 0x02000000 /* TypeDef */); + + baseMethod = minfo.GetBaseDefinition (); + var delegateProxies = baseMethod.ReturnTypeCustomAttributes.GetCustomAttributes (typeof (DelegateProxyAttribute), false); + if (delegateProxies.Length > 0) + return ((DelegateProxyAttribute) delegateProxies [0]).DelegateType; + + // We might be implementing a protocol, find any DelegateProxy attributes on the corresponding interface as well. + string selector = null; + foreach (var iface in minfo.DeclaringType.GetInterfaces ()) { + if (!iface.IsDefined (typeof (ProtocolAttribute), false)) + continue; + + var map = minfo.DeclaringType.GetInterfaceMap (iface); + for (int i = 0; i < map.TargetMethods.Length; i++) { + if (map.TargetMethods [i] == minfo) { + delegateProxies = map.InterfaceMethods [i].ReturnTypeCustomAttributes.GetCustomAttributes (typeof (DelegateProxyAttribute), false); + if (delegateProxies.Length > 0) + return ((DelegateProxyAttribute) delegateProxies [0]).DelegateType; + } + } + + // It might be an optional method/property, in which case we need to check any ProtocolMember attributes + if (selector == null) + selector = Runtime.GetExportAttribute (minfo)?.Selector ?? string.Empty; + if (!string.IsNullOrEmpty (selector)) { + var attrib = Runtime.GetProtocolMemberAttribute (iface, selector, minfo); + if (attrib?.ReturnTypeDelegateProxy != null) + return attrib.ReturnTypeDelegateProxy; + } + } + + throw ErrorHelper.CreateError (8011, "Unable to locate the delegate to block conversion attribute ([DelegateProxy]) for the return value for the method {0}.{1}. Please file a bug at http://bugzilla.xamarin.com.", baseMethod.DeclaringType.FullName, baseMethod.Name); + } + + [BindingImpl(BindingImplOptions.Optimizable)] + internal static IntPtr GetBlockForDelegate (MethodInfo minfo, object @delegate, uint token_ref, string signature) { if (@delegate == null) return IntPtr.Zero; @@ -256,26 +298,22 @@ namespace ObjCRuntime { if (!(@delegate is Delegate)) throw ErrorHelper.CreateError (8016, "Unable to convert delegate to block for the return value for the method {0}.{1}, because the input isn't a delegate, it's a {1}. Please file a bug at http://bugzilla.xamarin.com.", minfo.DeclaringType.FullName, minfo.Name, @delegate.GetType ().FullName); - var baseMethod = minfo.GetBaseDefinition (); - - var delegateProxies = baseMethod.ReturnTypeCustomAttributes.GetCustomAttributes (typeof (DelegateProxyAttribute), false); - if (delegateProxies.Length == 0) - throw ErrorHelper.CreateError (8011, "Unable to locate the delegate to block conversion attribute ([DelegateProxy]) for the return value for the method {0}.{1}. Please file a bug at http://bugzilla.xamarin.com.", baseMethod.DeclaringType.FullName, baseMethod.Name); - - var delegateProxy = (DelegateProxyAttribute) delegateProxies [0]; - if (delegateProxy.DelegateType == null) + Type delegateProxyType = GetDelegateProxyType (minfo, token_ref, out var baseMethod); + if (baseMethod == null) + baseMethod = minfo; // 'baseMethod' is only used in error messages, and if it's null, we just use the closest alternative we have (minfo). + if (delegateProxyType == null) throw ErrorHelper.CreateError (8012, "Invalid DelegateProxyAttribute for the return value for the method {0}.{1}: DelegateType is null. Please file a bug at http://bugzilla.xamarin.com.", baseMethod.DeclaringType.FullName, baseMethod.Name); - var delegateProxyField = delegateProxy.DelegateType.GetField ("Handler", BindingFlags.NonPublic | BindingFlags.Static); + var delegateProxyField = delegateProxyType.GetField ("Handler", BindingFlags.NonPublic | BindingFlags.Static); if (delegateProxyField == null) - throw ErrorHelper.CreateError (8013, "Invalid DelegateProxyAttribute for the return value for the method {0}.{1}: DelegateType ({2}) specifies a type without a 'Handler' field. Please file a bug at http://bugzilla.xamarin.com.", baseMethod.DeclaringType.FullName, baseMethod.Name, delegateProxy.DelegateType.FullName); + throw ErrorHelper.CreateError (8013, "Invalid DelegateProxyAttribute for the return value for the method {0}.{1}: DelegateType ({2}) specifies a type without a 'Handler' field. Please file a bug at http://bugzilla.xamarin.com.", baseMethod.DeclaringType.FullName, baseMethod.Name, delegateProxyType.FullName); var handlerDelegate = delegateProxyField.GetValue (null); if (handlerDelegate == null) - throw ErrorHelper.CreateError (8014, "Invalid DelegateProxyAttribute for the return value for the method {0}.{1}: The DelegateType's ({2}) 'Handler' field is null. Please file a bug at http://bugzilla.xamarin.com.", baseMethod.DeclaringType.FullName, baseMethod.Name, delegateProxy.DelegateType.FullName); + throw ErrorHelper.CreateError (8014, "Invalid DelegateProxyAttribute for the return value for the method {0}.{1}: The DelegateType's ({2}) 'Handler' field is null. Please file a bug at http://bugzilla.xamarin.com.", baseMethod.DeclaringType.FullName, baseMethod.Name, delegateProxyType.FullName); if (!(handlerDelegate is Delegate)) - throw ErrorHelper.CreateError (8015, "Invalid DelegateProxyAttribute for the return value for the method {0}.{1}: The DelegateType's ({2}) 'Handler' field is not a delegate, it's a {3}. Please file a bug at http://bugzilla.xamarin.com.", baseMethod.DeclaringType.FullName, baseMethod.Name, delegateProxy.DelegateType.FullName, handlerDelegate.GetType ().FullName); + throw ErrorHelper.CreateError (8015, "Invalid DelegateProxyAttribute for the return value for the method {0}.{1}: The DelegateType's ({2}) 'Handler' field is not a delegate, it's a {3}. Please file a bug at http://bugzilla.xamarin.com.", baseMethod.DeclaringType.FullName, baseMethod.Name, delegateProxyType.FullName, handlerDelegate.GetType ().FullName); // We now have the information we need to create the block. // Note that we must create a heap-allocated block, so we diff --git a/src/ObjCRuntime/ErrorHelper.cs b/src/ObjCRuntime/ErrorHelper.cs index 737b15d8b5..5c83f6b95c 100644 --- a/src/ObjCRuntime/ErrorHelper.cs +++ b/src/ObjCRuntime/ErrorHelper.cs @@ -32,6 +32,7 @@ using ProductException=MonoMac.RuntimeException; using ObjCRuntime; #endif #else +using System.Linq; using Mono.Cecil.Cil; #endif @@ -297,6 +298,26 @@ namespace ObjCRuntime { Show (new ProductException (code, false, innerException, message, args)); } +#if MMP || MTOUCH + // Shows any warnings, and if there are any errors, throws an AggregateException. + public static void ThrowIfErrors (IList exceptions) + { + if (exceptions?.Any () != true) + return; + + // Separate warnings from errors + var grouped = exceptions.GroupBy ((v) => (v as ProductException)?.Error == false); + + var warnings = grouped.SingleOrDefault ((v) => v.Key); + if (warnings?.Any () == true) + Show (warnings); + + var errors = grouped.SingleOrDefault ((v) => !v.Key); + if (errors?.Any () == true) + throw new AggregateException (errors); + } +#endif + public static void Show (IEnumerable list) { List exceptions = new List (); diff --git a/src/ObjCRuntime/Runtime.cs b/src/ObjCRuntime/Runtime.cs index eed5e3a6cc..0884b67467 100644 --- a/src/ObjCRuntime/Runtime.cs +++ b/src/ObjCRuntime/Runtime.cs @@ -447,9 +447,9 @@ namespace ObjCRuntime { return ObjectWrapper.Convert (CreateBlockProxy ((MethodInfo) ObjectWrapper.Convert (method), block)); } - static IntPtr CreateDelegateProxy (IntPtr method, IntPtr @delegate, IntPtr signature) + static IntPtr CreateDelegateProxy (IntPtr method, IntPtr @delegate, IntPtr signature, uint token_ref) { - return BlockLiteral.GetBlockForDelegate ((MethodInfo) ObjectWrapper.Convert (method), ObjectWrapper.Convert (@delegate), Marshal.PtrToStringAuto (signature)); + return BlockLiteral.GetBlockForDelegate ((MethodInfo) ObjectWrapper.Convert (method), ObjectWrapper.Convert (@delegate), token_ref, Marshal.PtrToStringAuto (signature)); } static unsafe Assembly GetEntryAssembly () @@ -776,6 +776,48 @@ namespace ObjCRuntime { return null; } + internal static ProtocolMemberAttribute GetProtocolMemberAttribute (Type type, string selector, MethodInfo method) + { + var memberAttributes = type.GetCustomAttributes (); + if (memberAttributes == null) + return null; + + foreach (var attrib in memberAttributes) { + if (attrib.IsStatic != method.IsStatic) + continue; + + if (attrib.Selector != selector) + continue; + + if (!attrib.IsProperty) { + var methodParameters = method.GetParameters (); + if ((attrib.ParameterType?.Length ?? 0) != methodParameters.Length) + continue; + var notApplicable = false; + for (int i = 0; i < methodParameters.Length; i++) { + var paramType = methodParameters [i].ParameterType; + var isByRef = paramType.IsByRef; + if (isByRef) + paramType = paramType.GetElementType (); + if (isByRef != attrib.ParameterByRef [i]) { + notApplicable = true; + break; + } + if (paramType != attrib.ParameterType [i]) { + notApplicable = true; + break; + } + } + if (notApplicable) + continue; + } + + return attrib; + } + + return null; + } + // // Returns a MethodInfo that represents the method that can be used to turn // a the block in the given method at the given parameter into a strongly typed @@ -824,39 +866,11 @@ namespace ObjCRuntime { // We may run into binding assemblies built with earlier versions of the generator, // which means we can't rely on finding the BlockProxy attribute in the ProtocolMemberAttribute. if (selector == null) - selector = method.GetCustomAttribute ()?.Selector ?? string.Empty; + selector = GetExportAttribute (method)?.Selector ?? string.Empty; if (!string.IsNullOrEmpty (selector)) { - var memberAttributes = iface.GetCustomAttributes (); - foreach (var attrib in memberAttributes) { - if (attrib.ParameterBlockProxy == null || attrib.ParameterBlockProxy.Length <= parameter || attrib.ParameterBlockProxy [parameter] == null) - continue; // no need to check anything if what we want isn't there - if (attrib.Selector != selector) - continue; - if (attrib.IsStatic != method.IsStatic) - continue; - var methodParameters = method.GetParameters (); - if (attrib.ParameterType.Length != methodParameters.Length) - continue; - var notApplicable = false; - for (int i = 0; i < methodParameters.Length; i++) { - var paramType = methodParameters [i].ParameterType; - var isByRef = paramType.IsByRef; - if (isByRef) - paramType = paramType.GetElementType (); - if (isByRef != attrib.ParameterByRef [i]) { - notApplicable = true; - break; - } - if (paramType != attrib.ParameterType [i]) { - notApplicable = true; - break; - } - } - if (notApplicable) - continue; - + var attrib = GetProtocolMemberAttribute (iface, selector, method); + if (attrib != null && attrib.ParameterBlockProxy.Length > parameter && attrib.ParameterBlockProxy [parameter] != null) return attrib.ParameterBlockProxy [parameter].GetMethod ("Create"); - } } // Might be an implementation of an optional protocol member. @@ -1010,6 +1024,31 @@ namespace ObjCRuntime { } } + internal static PropertyInfo FindPropertyInfo (MethodInfo accessor) + { + if (!accessor.IsSpecialName) + return null; + + foreach (var pi in accessor.DeclaringType.GetProperties ()) { + if (pi.GetGetMethod () == accessor) + return pi; + if (pi.GetSetMethod () == accessor) + return pi; + } + + return null; + } + + internal static ExportAttribute GetExportAttribute (MethodInfo method) + { + var attrib = method.GetCustomAttribute (); + if (attrib == null) { + var pinfo = FindPropertyInfo (method); + if (pinfo != null) + attrib = pinfo.GetCustomAttribute (); + } + return attrib; + } static NSObject IgnoreConstructionError (IntPtr ptr, IntPtr klass, Type type) { diff --git a/src/generator.cs b/src/generator.cs index a348791073..ec6beea733 100644 --- a/src/generator.cs +++ b/src/generator.cs @@ -4778,6 +4778,7 @@ public partial class Generator : IMemberGatherer { sel = ba.Selector; } + PrintBlockProxy (pi.PropertyType); PrintAttributes (pi, platform:true); if (not_implemented_attr == null && (!minfo.is_sealed || !minfo.is_wrapper)) @@ -5101,6 +5102,15 @@ public partial class Generator : IMemberGatherer { } } + void PrintBlockProxy (Type type) + { + type = GetCorrectGenericType (type); + if (type.IsSubclassOf (TypeManager.System_Delegate)) { + var ti = MakeTrampoline (type); + print ("[param: BlockProxy (typeof ({0}.Trampolines.{1}))]", ns.CoreObjCRuntime, ti.NativeInvokerName); + } + } + void PrintExport (MemberInformation minfo) { if (minfo.is_export) @@ -5418,8 +5428,14 @@ public partial class Generator : IMemberGatherer { sb.Append (", IsStatic = ").Append (AttributeManager.HasAttribute (mi) ? "true" : "false"); sb.Append (", Name = \"").Append (mi.Name).Append ("\""); sb.Append (", Selector = \"").Append (attrib.Selector).Append ("\""); - if (mi.ReturnType != TypeManager.System_Void) - sb.Append (", ReturnType = typeof (").Append (RenderType (GetCorrectGenericType (mi.ReturnType))).Append(")"); + if (mi.ReturnType != TypeManager.System_Void) { + var retType = GetCorrectGenericType (mi.ReturnType); + sb.Append (", ReturnType = typeof (").Append (RenderType (retType)).Append (")"); + if (retType.IsSubclassOf (TypeManager.System_Delegate)) { + var ti = MakeTrampoline (retType); + sb.Append ($", ReturnTypeDelegateProxy = typeof ({ns.CoreObjCRuntime}.Trampolines.{ti.StaticName})"); + } + } var parameters = mi.GetParameters (); if (parameters != null && parameters.Length > 0) { sb.Append (", ParameterType = new Type [] { "); @@ -5489,6 +5505,15 @@ public partial class Generator : IMemberGatherer { sb.Append (", SetterSelector = \"").Append (ba != null ? ba.Selector : ea.Selector).Append ("\""); } sb.Append (", ArgumentSemantic = ArgumentSemantic.").Append (attrib.ArgumentSemantic); + // Check for block/delegate proxies + var propType = GetCorrectGenericType (pi.PropertyType); + if (propType.IsSubclassOf (TypeManager.System_Delegate)) { + var ti = MakeTrampoline (propType); + if (pi.SetMethod != null) + sb.Append ($", ParameterBlockProxy = new Type [] {{ typeof ({ns.CoreObjCRuntime}.Trampolines.{ti.NativeInvokerName}) }}"); + if (pi.GetMethod != null) + sb.Append ($", ReturnTypeDelegateProxy = typeof ({ns.CoreObjCRuntime}.Trampolines.{ti.StaticName})"); + } sb.Append (")]"); print (sb.ToString ()); } @@ -5565,6 +5590,7 @@ public partial class Generator : IMemberGatherer { } if (pi.CanWrite) { var setMethod = pi.GetSetMethod (); + PrintBlockProxy (pi.PropertyType); PrintAttributes (setMethod, notImplemented:true); if (!AttributeManager.HasAttribute (setMethod)) PrintExport (minfo, GetSetterExportAttribute (pi)); diff --git a/tests/bindings-test/ApiDefinition.cs b/tests/bindings-test/ApiDefinition.cs index e9a721fb4d..81028abcec 100644 --- a/tests/bindings-test/ApiDefinition.cs +++ b/tests/bindings-test/ApiDefinition.cs @@ -289,6 +289,22 @@ namespace Bindings.Test { [Static] [Export ("optionalStaticCallback:")] void OptionalStaticCallback (Action completionHandler); + + [Abstract] + [Export ("requiredReturnValue")] + Action RequiredReturnValue (); + + [Abstract] + [Static] + [Export ("requiredStaticReturnValue")] + Action RequiredStaticReturnValue (); + + [Export ("optionalReturnValue")] + Action OptionalReturnValue (); + + [Static] + [Export ("optionalStaticReturnValue")] + Action OptionalStaticReturnValue (); } interface IObjCProtocolBlockTest { } @@ -339,6 +355,23 @@ namespace Bindings.Test { [Static] [Export ("freedBlockCount")] int FreedBlockCount { get; } + + [Static] + [Export ("calledBlockCount")] + int CalledBlockCount { get; } + + [Static] + [Export ("callProtocolWithBlockProperties:required:instance:")] + void CallProtocolWithBlockProperties (IProtocolWithBlockProperties obj, bool required, bool instance); + + [Static] + [Export ("callProtocolWithBlockReturnValue:required:instance:")] + void CallProtocolWithBlockReturnValue (IObjCProtocolBlockTest obj, bool required, bool instance); + + [Static] + [Export ("setProtocolWithBlockProperties:required:instance:")] + void SetProtocolWithBlockProperties (IProtocolWithBlockProperties obj, bool required, bool instance); + } delegate void InnerBlock (int magic_number); @@ -350,6 +383,28 @@ namespace Bindings.Test { [Export ("evilCallback")] Action EvilCallback { get; set; } } + + delegate void SimpleCallback (); + [BaseType (typeof (NSObject))] + [Protocol] + interface ProtocolWithBlockProperties { + [Abstract] + [Export ("myRequiredProperty")] + SimpleCallback MyRequiredProperty { get; set; } + + [Export ("myOptionalProperty")] + SimpleCallback MyOptionalProperty { get; set; } + + [Static] + [Abstract] + [Export ("myRequiredStaticProperty")] + SimpleCallback MyRequiredStaticProperty { get; set; } + + [Static] + [Export ("myOptionalStaticProperty")] + SimpleCallback MyOptionalStaticProperty { get; set; } + } + interface IProtocolWithBlockProperties { } } diff --git a/tests/bindings-test/RegistrarBindingTest.cs b/tests/bindings-test/RegistrarBindingTest.cs index 5c85bc09a6..8da468fcbd 100644 --- a/tests/bindings-test/RegistrarBindingTest.cs +++ b/tests/bindings-test/RegistrarBindingTest.cs @@ -65,6 +65,37 @@ namespace Xamarin.BindingTests { completionHandler (42); } + + public Action RequiredReturnValue () + { + return new Action ((v) => { + Assert.AreEqual (42, v, "RequiredReturnValue"); + }); + } + + [Export ("optionalReturnValue")] + public Action OptionalReturnValue () + { + return new Action ((v) => { + Assert.AreEqual (42, v, "RequiredReturnValue"); + }); + } + + [Export ("requiredStaticReturnValue")] + public static Action RequiredStaticReturnValue () + { + return new Action ((v) => { + Assert.AreEqual (42, v, "RequiredReturnValue"); + }); + } + + [Export ("optionalStaticReturnValue")] + public static Action OptionalStaticReturnValue () + { + return new Action ((v) => { + Assert.AreEqual (42, v, "RequiredReturnValue"); + }); + } } class BlockCallbackClassExplicit : NSObject, IObjCProtocolBlockTest @@ -92,6 +123,38 @@ namespace Xamarin.BindingTests { completionHandler (42); } + + // Explicitly implemented interface member + Action IObjCProtocolBlockTest.RequiredReturnValue () + { + return new Action ((v) => { + Assert.AreEqual (42, v, "RequiredReturnValue"); + }); + } + + [Export ("optionalReturnValue")] + public Action OptionalReturnValue () + { + return new Action ((v) => { + Assert.AreEqual (42, v, "RequiredReturnValue"); + }); + } + + [Export ("requiredStaticReturnValue")] + public static Action RequiredStaticReturnValue () + { + return new Action ((v) => { + Assert.AreEqual (42, v, "RequiredReturnValue"); + }); + } + + [Export ("optionalStaticReturnValue")] + public static Action OptionalStaticReturnValue () + { + return new Action ((v) => { + Assert.AreEqual (42, v, "RequiredReturnValue"); + }); + } } public class BlockCallbackTester : ObjCBlockTester @@ -101,5 +164,86 @@ namespace Xamarin.BindingTests completionHandler (42); } } + + public class PropertyBlock : NSObject, IProtocolWithBlockProperties { + [Export ("myOptionalProperty")] + public SimpleCallback MyOptionalProperty { get; set; } + + public SimpleCallback MyRequiredProperty { get; set; } + + [Export ("myOptionalStaticProperty")] + public static SimpleCallback MyOptionalStaticProperty { get; set; } + + [Export ("myRequiredStaticProperty")] + public static SimpleCallback MyRequiredStaticProperty { get; set; } + } + + [Test] + [TestCase (true, true)] + [TestCase (true, false)] + [TestCase (false, true)] + [TestCase (false, false)] + public void ProtocolWithBlockProperties (bool required, bool instance) + { + using (var pb = new PropertyBlock ()) { + var callbackCalled = false; + SimpleCallback action = () => { + callbackCalled = true; + }; + if (required) { + if (instance) { + pb.MyRequiredProperty = action; + } else { + PropertyBlock.MyRequiredStaticProperty = action; + } + } else { + if (instance) { + pb.MyOptionalProperty = action; + } else { + PropertyBlock.MyOptionalStaticProperty = action; + } + } + ObjCBlockTester.CallProtocolWithBlockProperties (pb, required, instance); + Assert.IsTrue (callbackCalled, "Callback"); + } + } + + [Test] + [TestCase (true, true)] + [TestCase (true, false)] + [TestCase (false, true)] + [TestCase (false, false)] + public void ProtocolWithNativeBlockProperties (bool required, bool instance) + { + using (var pb = new PropertyBlock ()) { + var calledCounter = ObjCBlockTester.CalledBlockCount; + ObjCBlockTester.SetProtocolWithBlockProperties (pb, required, instance); + if (required) { + if (instance) { + pb.MyRequiredProperty (); + } else { + PropertyBlock.MyRequiredStaticProperty (); + } + } else { + if (instance) { + pb.MyOptionalProperty (); + } else { + PropertyBlock.MyOptionalStaticProperty (); + } + } + Assert.AreEqual (calledCounter + 1, ObjCBlockTester.CalledBlockCount, "Blocks called"); + } + } + [Test] + [TestCase (true, true)] + [TestCase (true, false)] + [TestCase (false, true)] + [TestCase (false, false)] + public void ProtocolWithReturnValues (bool required, bool instance) + { + using (var pb = new BlockCallbackClass ()) { + ObjCBlockTester.CallProtocolWithBlockReturnValue (pb, required, instance); + } + } } } diff --git a/tests/common/BundlerTest.cs b/tests/common/BundlerTest.cs index 0179c91eaf..4086c12af8 100644 --- a/tests/common/BundlerTest.cs +++ b/tests/common/BundlerTest.cs @@ -259,8 +259,11 @@ class D : NSObject { bundler.AssertWarning (4173, "The registrar can't compute the block signature for the delegate of type System.Delegate in the method D.D4 because System.Delegate doesn't have a specific signature.", "testApp.cs", 24); bundler.AssertWarning (4173, "The registrar can't compute the block signature for the delegate of type System.MulticastDelegate in the method D.D5 because System.MulticastDelegate doesn't have a specific signature.", "testApp.cs", 30); bundler.AssertWarning (4174, "Unable to locate the block to delegate conversion method for the method D.D3's parameter #1.", "testApp.cs", 18); + bundler.AssertWarning (4176, "Unable to locate the delegate to block conversion type for the return value of the method D.D4.", "testApp.cs", 24); + bundler.AssertWarning (4176, "Unable to locate the delegate to block conversion type for the return value of the method D.D5.", "testApp.cs", 30); + bundler.AssertWarning (4176, "Unable to locate the delegate to block conversion type for the return value of the method D.D6.", "testApp.cs", 36); bundler.AssertErrorCount (2); - bundler.AssertWarningCount (3); + bundler.AssertWarningCount (6); } } diff --git a/tests/mtouch/RegistrarTest.cs b/tests/mtouch/RegistrarTest.cs index 7d86da5b72..4809840ebc 100644 --- a/tests/mtouch/RegistrarTest.cs +++ b/tests/mtouch/RegistrarTest.cs @@ -1182,6 +1182,45 @@ namespace NS { } } + [Test] + public void MT4176 () + { + using (var mtouch = new MTouchTool ()) { + var code = @" +namespace NS { + using System; + using Foundation; + using ObjCRuntime; + + public class Consumer : NSObject + { + [Export (""getAction"")] + public Action GetFunction () + { + throw new NotImplementedException (); + } + + [Export (""getProperty"")] + public Action GetProperty { + get { + throw new NotImplementedException (); + } + } + } +} + +"; + mtouch.Linker = MTouchLinker.DontLink; // faster + mtouch.Registrar = MTouchRegistrar.Static; + mtouch.CreateTemporaryApp (extraCode: code, extraArg: "-debug"); + mtouch.WarnAsError = new int [] { 4176 }; + mtouch.AssertExecuteFailure ("build"); + mtouch.AssertError (4176, "Unable to locate the delegate to block conversion type for the return value of the method NS.Consumer.GetFunction.", "testApp.cs", 11); + mtouch.AssertError (4176, "Unable to locate the delegate to block conversion type for the return value of the method NS.Consumer.get_GetProperty.", "testApp.cs", 17); + mtouch.AssertErrorCount (2); + } + } + [Test] public void NoWarnings () { diff --git a/tests/test-libraries/libtest.h b/tests/test-libraries/libtest.h index 725aec1fe7..83b26c81ae 100644 --- a/tests/test-libraries/libtest.h +++ b/tests/test-libraries/libtest.h @@ -148,15 +148,29 @@ typedef unsigned int (^RegistrarTestBlock) (unsigned int magic); -(void) idAsIntPtr: (id)p1; @end +typedef void (^int_callback)(int32_t magic_number); @protocol ObjCProtocolBlockTest @required - -(void) requiredCallback: (void (^)(int32_t magic_number))completionHandler; - +(void) requiredStaticCallback: (void (^)(int32_t magic_number))completionHandler; + -(void) requiredCallback: (int_callback)completionHandler; + +(void) requiredStaticCallback: (int_callback)completionHandler; + -(int_callback) requiredReturnValue; + +(int_callback) requiredStaticReturnValue; @optional - -(void) optionalCallback: (void (^)(int32_t magic_number))completionHandler; - +(void) optionalStaticCallback: (void (^)(int32_t magic_number))completionHandler; + -(void) optionalCallback: (int_callback)completionHandler; + +(void) optionalStaticCallback: (int_callback)completionHandler; + -(int_callback) optionalReturnValue; + +(int_callback) optionalStaticReturnValue; @end +typedef void (^simple_callback)(); +@protocol ProtocolWithBlockProperties +@required + @property simple_callback myRequiredProperty; + @property (class) simple_callback myRequiredStaticProperty; +@optional + @property simple_callback myOptionalProperty; + @property (class) simple_callback myOptionalStaticProperty; +@end @interface ObjCBlockTester : NSObject { } @property (retain) NSObject* TestObject; @@ -175,6 +189,12 @@ typedef void (^outerBlock) (innerBlock callback); -(void) testFreedBlocks; +(int) freedBlockCount; + ++(void) callProtocolWithBlockProperties: (id) obj required: (bool) required instance: (bool) instance; ++(void) callProtocolWithBlockReturnValue: (id) obj required: (bool) required instance: (bool) instance; + ++(void) setProtocolWithBlockProperties: (id) obj required: (bool) required instance: (bool) instance; ++(int) calledBlockCount; @end @interface FreedNotifier : NSObject { diff --git a/tests/test-libraries/libtest.m b/tests/test-libraries/libtest.m index 35495d487a..624a7f3d56 100644 --- a/tests/test-libraries/libtest.m +++ b/tests/test-libraries/libtest.m @@ -527,6 +527,7 @@ static UltimateMachine *shared; @end static volatile int freed_blocks = 0; +static volatile int called_blocks = 0; @implementation ObjCBlockTester static Class _TestClass = NULL; @@ -654,6 +655,73 @@ static Class _TestClass = NULL; { return freed_blocks; } ++(int) calledBlockCount +{ + return called_blocks; +} + +static void block_called () +{ + OSAtomicIncrement32 (&called_blocks); +} + ++(void) callProtocolWithBlockProperties: (id) obj required: (bool) required instance: (bool) instance; +{ + if (required) { + if (instance) { + obj.myRequiredProperty (); + } else { + [[(NSObject *) obj class] myRequiredStaticProperty] (); + } + } else { + if (instance) { + obj.myOptionalProperty (); + } else { + [[(NSObject *) obj class] myOptionalStaticProperty] (); + } + } +} + ++(void) callProtocolWithBlockReturnValue: (id) obj required: (bool) required instance: (bool) instance; +{ + if (required) { + if (instance) { + [obj requiredReturnValue] (42); + } else { + [[(NSObject *) obj class] requiredStaticReturnValue] (42); + } + } else { + if (instance) { + [obj optionalReturnValue] (42); + } else { + [[(NSObject *) obj class] optionalStaticReturnValue] (42); + } + } +} + ++(void) callProtocolWithBlockPropertiesRequired: (id) obj +{ + obj.myRequiredProperty (); +} + ++(void) setProtocolWithBlockProperties: (id) obj required: (bool) required instance: (bool) instance +{ + simple_callback callback = ^{ block_called (); }; + if (required) { + if (instance) { + obj.myRequiredProperty = callback; + } else { + [[(NSObject *) obj class] setMyRequiredStaticProperty: callback]; + } + } else { + if (instance) { + obj.myOptionalProperty = callback; + } else { + [[(NSObject *) obj class] setMyOptionalStaticProperty: callback]; + } + } +} + @end @implementation FreedNotifier diff --git a/tools/common/StaticRegistrar.cs b/tools/common/StaticRegistrar.cs index 515d2d8afc..bb6bcfd3f3 100644 --- a/tools/common/StaticRegistrar.cs +++ b/tools/common/StaticRegistrar.cs @@ -1397,12 +1397,64 @@ namespace Registrar { return rv; } + public DelegateProxyAttribute GetDelegateProxyAttribute (MethodDefinition method) + { + if (!TryGetAttribute (method.MethodReturnType, ObjCRuntime, "DelegateProxyAttribute", out var attrib)) + return null; + + var rv = new DelegateProxyAttribute (); + + switch (attrib.ConstructorArguments.Count) { + case 1: + rv.DelegateType = ((TypeReference) attrib.ConstructorArguments [0].Value).Resolve (); + break; + default: + throw ErrorHelper.CreateError (4124, "Invalid DelegateProxyAttribute found on '{0}'. Please file a bug report at https://bugzilla.xamarin.com", ((MethodReference) method)?.FullName); + } + + return rv; + } + protected override string PlatformName { get { return App.PlatformName; } } + ProtocolMemberAttribute GetProtocolMemberAttribute (TypeReference type, string selector, ObjCMethod obj_method, MethodDefinition method) + { + var memberAttributes = GetProtocolMemberAttributes (type); + foreach (var attrib in memberAttributes) { + if (attrib.IsStatic != method.IsStatic) + continue; + + if (attrib.IsProperty) { + if (method.IsSetter && attrib.SetterSelector != selector) + continue; + else if (method.IsGetter && attrib.GetterSelector != selector) + continue; + } else { + if (attrib.Selector != selector) + continue; + } + + if (!obj_method.IsPropertyAccessor) { + var attribParameters = new TypeReference [attrib.ParameterType?.Length ?? 0]; + for (var i = 0; i < attribParameters.Length; i++) { + attribParameters [i] = attrib.ParameterType [i]; + if (attrib.ParameterByRef [i]) + attribParameters [i] = new ByReferenceType (attribParameters [i]); + } + if (!ParametersMatch (method.Parameters, attribParameters)) + continue; + } + + return attrib; + } + + return null; + } + protected override IEnumerable GetProtocolMemberAttributes (TypeReference type) { var td = type.Resolve (); @@ -1434,6 +1486,9 @@ namespace Registrar { case "ReturnType": rv.ReturnType = (TypeReference)prop.Argument.Value; break; + case "ReturnTypeDelegateProxy": + rv.ReturnTypeDelegateProxy = (TypeReference) prop.Argument.Value; + break; case "ParameterType": if (prop.Argument.Value != null) { var arr = (CustomAttributeArgument[])prop.Argument.Value; @@ -3071,8 +3126,7 @@ namespace Registrar { sb.WriteLine (map.ToString ()); sb.WriteLine (map_init.ToString ()); - if (exceptions.Count > 0) - throw new AggregateException (exceptions); + ErrorHelper.ThrowIfErrors (exceptions); } static bool HasIntPtrBoolCtor (TypeDefinition type) @@ -3680,7 +3734,7 @@ namespace Registrar { if (creatorMethod != null) { token = $"0x{CreateTokenReference (creatorMethod, TokenType.Method):X} /* {creatorMethod.FullName} */ "; } else { - ErrorHelper.Show (ErrorHelper.CreateWarning (App, 4174, method.Method, "Unable to locate the block to delegate conversion method for the method {0}'s parameter #{1}.", + exceptions.Add (ErrorHelper.CreateWarning (App, 4174, method.Method, "Unable to locate the block to delegate conversion method for the method {0}'s parameter #{1}.", method.DescriptiveMethodName, i + 1)); } } @@ -3832,6 +3886,7 @@ namespace Registrar { setup_return.AppendLine ("res = nsstr;"); } else if (IsDelegate (type.Resolve ())) { var signature = "NULL"; + var token = "INVALID_TOKEN_REF"; if (App.Optimizations.OptimizeBlockLiteralSetupBlock == true) { if (type.Is ("System", "Delegate") || type.Is ("System", "MulticastDelegate")) { ErrorHelper.Show (ErrorHelper.CreateWarning (App, 4173, method.Method, $"The registrar can't compute the block signature for the delegate of type {type.FullName} in the method {descriptiveMethodName} because {type.FullName} doesn't have a specific signature.")); @@ -3843,8 +3898,14 @@ namespace Registrar { signature = "\"" + ComputeSignature (method.DeclaringType.Type, null, method, isBlockSignature: true) + "\""; } } + var delegateProxyType = GetDelegateProxyType (method); + if (delegateProxyType != null) { + token = $"0x{CreateTokenReference (delegateProxyType, TokenType.TypeDef):X} /* {delegateProxyType.FullName} */ "; + } else { + exceptions.Add (ErrorHelper.CreateWarning (App, 4176, method.Method, "Unable to locate the delegate to block conversion type for the return value of the method {0}.", method.DescriptiveMethodName)); + } } - setup_return.AppendLine ("res = xamarin_get_block_for_delegate (managed_method, retval, {0}, &exception_gchandle);", signature); + setup_return.AppendLine ("res = xamarin_get_block_for_delegate (managed_method, retval, {0}, {1}, &exception_gchandle);", signature, token); setup_return.AppendLine ("if (exception_gchandle != 0) goto exception_handling;"); } else { throw ErrorHelper.CreateError (4104, @@ -4049,6 +4110,52 @@ namespace Registrar { } } + TypeDefinition GetDelegateProxyType (ObjCMethod obj_method) + { + // A mirror of this method is also implemented in BlockLiteral:GetDelegateProxyType + // If this method is changed, that method will probably have to be updated too (tests!!!) + MethodDefinition method = obj_method.Method; + MethodDefinition first = method; + MethodDefinition last = null; + while (method != last) { + last = method; + var delegateProxyType = GetDelegateProxyAttribute (method); + if (delegateProxyType?.DelegateType != null) + return delegateProxyType.DelegateType; + + method = GetBaseMethodInTypeHierarchy (method); + } + + // Might be the implementation of an interface method, so find the corresponding + // MethodDefinition for the interface, and check for DelegateProxy attributes there as well. + var map = PrepareMethodMapping (first.DeclaringType); + if (map != null && map.TryGetValue (first, out var list)) { + if (list.Count != 1) + throw Shared.GetMT4127 (first, list); + var delegateProxyType = GetDelegateProxyAttribute (list [0]); + if (delegateProxyType?.DelegateType != null) + return delegateProxyType.DelegateType; + } + + // Might be an implementation of an optional protocol member. + if (obj_method.DeclaringType.Protocols != null) { + string selector = null; + + foreach (var proto in obj_method.DeclaringType.Protocols) { + // We store the DelegateProxy type in the ProtocolMemberAttribute, so check those. + if (selector == null) + selector = obj_method.Selector ?? string.Empty; + if (selector != null) { + var attrib = GetProtocolMemberAttribute (proto.Type, selector, obj_method, method); + if (attrib?.ReturnTypeDelegateProxy != null) + return attrib.ReturnTypeDelegateProxy.Resolve (); + } + } + } + + return null; + } + MethodDefinition GetBlockWrapperCreator (ObjCMethod obj_method, int parameter) { // A mirror of this method is also implemented in Runtime:GetBlockWrapperCreator @@ -4087,48 +4194,35 @@ namespace Registrar { if (selector == null) selector = obj_method.Selector ?? string.Empty; if (selector != null) { - var memberAttributes = GetProtocolMemberAttributes (proto.Type); - foreach (var attrib in memberAttributes) { - if (attrib.ParameterBlockProxy == null || attrib.ParameterBlockProxy.Length <= parameter || attrib.ParameterBlockProxy [parameter] == null) - continue; // no need to check anything if what we want isn't there - if (attrib.Selector != selector) - continue; - if (attrib.IsStatic != method.IsStatic) - continue; - var attribParameters = new TypeReference [attrib.ParameterType?.Length ?? 0]; - for (var i = 0; i < attribParameters.Length; i++) { - attribParameters [i] = attrib.ParameterType [i]; - if (attrib.ParameterByRef [i]) - attribParameters [i] = new ByReferenceType (attribParameters [i]); - } - if (!ParametersMatch (method.Parameters, attribParameters)) - continue; - + var attrib = GetProtocolMemberAttribute (proto.Type, selector, obj_method, method); + if (attrib?.ParameterBlockProxy?.Length > parameter && attrib.ParameterBlockProxy [parameter] != null) return attrib.ParameterBlockProxy [parameter].Resolve ().Methods.First ((v) => v.Name == "Create"); + } + + if (proto.Methods != null) { + foreach (var pMethod in proto.Methods) { + if (!pMethod.IsOptional) + continue; + if (pMethod.Name != method.Name) + continue; + if (!TypeMatch (pMethod.ReturnType, method.ReturnType)) + continue; + if (ParametersMatch (method.Parameters, pMethod.Parameters)) + continue; + + MethodDefinition extensionMethod = pMethod.Method; + if (extensionMethod == null) { + MapProtocolMember (obj_method.Method, out extensionMethod); + if (extensionMethod == null) + return null; + } + + var createMethod = GetBlockProxyAttributeMethod (extensionMethod, parameter + 1); + if (createMethod != null) + return createMethod; } } - foreach (var pMethod in proto.Methods) { - if (!pMethod.IsOptional) - continue; - if (pMethod.Name != method.Name) - continue; - if (!TypeMatch (pMethod.ReturnType, method.ReturnType)) - continue; - if (ParametersMatch (method.Parameters, pMethod.Parameters)) - continue; - - MethodDefinition extensionMethod = pMethod.Method; - if (extensionMethod == null) { - MapProtocolMember (obj_method.Method, out extensionMethod); - if (extensionMethod == null) - return null; - } - - var createMethod = GetBlockProxyAttributeMethod (extensionMethod, parameter + 1); - if (createMethod != null) - return createMethod; - } } } @@ -4843,6 +4937,11 @@ namespace Registrar { public TypeDefinition Type { get; set; } } + class DelegateProxyAttribute : Attribute + { + public TypeDefinition DelegateType { get; set; } + } + class BindAsAttribute : Attribute { public BindAsAttribute (TypeReference type) @@ -4863,6 +4962,7 @@ namespace Registrar { public string Name { get; set; } public string Selector { get; set; } public TypeReference ReturnType { get; set; } + public TypeReference ReturnTypeDelegateProxy { get; set; } public TypeReference[] ParameterType { get; set; } public bool[] ParameterByRef { get; set; } public TypeReference [] ParameterBlockProxy { get; set; }