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.
This commit is contained in:
Rolf Bjarne Kvinge 2018-09-06 16:20:23 +02:00 коммит произвёл GitHub
Родитель 5a75130899
Коммит bd32b74347
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
18 изменённых файлов: 684 добавлений и 101 удалений

Просмотреть файл

@ -378,6 +378,20 @@ will be shown.
Reference: https://github.com/xamarin/xamarin-macios/issues/4072
### <a name="MM4176"/>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

Просмотреть файл

@ -1787,6 +1787,20 @@ will be shown.
Reference: https://github.com/xamarin/xamarin-macios/issues/4072
### <a name="MT4176"/>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

Просмотреть файл

@ -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,

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

@ -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<Exception> 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<Exception> list)
{
List<Exception> exceptions = new List<Exception> ();

Просмотреть файл

@ -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<ProtocolMemberAttribute> ();
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,40 +866,12 @@ 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<ExportAttribute> ()?.Selector ?? string.Empty;
selector = GetExportAttribute (method)?.Selector ?? string.Empty;
if (!string.IsNullOrEmpty (selector)) {
var memberAttributes = iface.GetCustomAttributes<ProtocolMemberAttribute> ();
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.
// We look that up on the corresponding extension method.
@ -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<ExportAttribute> ();
if (attrib == null) {
var pinfo = FindPropertyInfo (method);
if (pinfo != null)
attrib = pinfo.GetCustomAttribute<ExportAttribute> ();
}
return attrib;
}
static NSObject IgnoreConstructionError (IntPtr ptr, IntPtr klass, Type type)
{

Просмотреть файл

@ -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<StaticAttribute> (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<NotImplementedAttribute> (setMethod))
PrintExport (minfo, GetSetterExportAttribute (pi));

Просмотреть файл

@ -289,6 +289,22 @@ namespace Bindings.Test {
[Static]
[Export ("optionalStaticCallback:")]
void OptionalStaticCallback (Action<int> completionHandler);
[Abstract]
[Export ("requiredReturnValue")]
Action<int> RequiredReturnValue ();
[Abstract]
[Static]
[Export ("requiredStaticReturnValue")]
Action<int> RequiredStaticReturnValue ();
[Export ("optionalReturnValue")]
Action<int> OptionalReturnValue ();
[Static]
[Export ("optionalStaticReturnValue")]
Action<int> 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<int> 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 { }
}

Просмотреть файл

@ -65,6 +65,37 @@ namespace Xamarin.BindingTests
{
completionHandler (42);
}
public Action<int> RequiredReturnValue ()
{
return new Action<int> ((v) => {
Assert.AreEqual (42, v, "RequiredReturnValue");
});
}
[Export ("optionalReturnValue")]
public Action<int> OptionalReturnValue ()
{
return new Action<int> ((v) => {
Assert.AreEqual (42, v, "RequiredReturnValue");
});
}
[Export ("requiredStaticReturnValue")]
public static Action<int> RequiredStaticReturnValue ()
{
return new Action<int> ((v) => {
Assert.AreEqual (42, v, "RequiredReturnValue");
});
}
[Export ("optionalStaticReturnValue")]
public static Action<int> OptionalStaticReturnValue ()
{
return new Action<int> ((v) => {
Assert.AreEqual (42, v, "RequiredReturnValue");
});
}
}
class BlockCallbackClassExplicit : NSObject, IObjCProtocolBlockTest
@ -92,6 +123,38 @@ namespace Xamarin.BindingTests
{
completionHandler (42);
}
// Explicitly implemented interface member
Action<int> IObjCProtocolBlockTest.RequiredReturnValue ()
{
return new Action<int> ((v) => {
Assert.AreEqual (42, v, "RequiredReturnValue");
});
}
[Export ("optionalReturnValue")]
public Action<int> OptionalReturnValue ()
{
return new Action<int> ((v) => {
Assert.AreEqual (42, v, "RequiredReturnValue");
});
}
[Export ("requiredStaticReturnValue")]
public static Action<int> RequiredStaticReturnValue ()
{
return new Action<int> ((v) => {
Assert.AreEqual (42, v, "RequiredReturnValue");
});
}
[Export ("optionalStaticReturnValue")]
public static Action<int> OptionalStaticReturnValue ()
{
return new Action<int> ((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);
}
}
}
}

Просмотреть файл

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

Просмотреть файл

@ -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 ()
{

Просмотреть файл

@ -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<ObjCProtocolBlockTest>* TestObject;
@ -175,6 +189,12 @@ typedef void (^outerBlock) (innerBlock callback);
-(void) testFreedBlocks;
+(int) freedBlockCount;
+(void) callProtocolWithBlockProperties: (id<ProtocolWithBlockProperties>) obj required: (bool) required instance: (bool) instance;
+(void) callProtocolWithBlockReturnValue: (id<ObjCProtocolBlockTest>) obj required: (bool) required instance: (bool) instance;
+(void) setProtocolWithBlockProperties: (id<ProtocolWithBlockProperties>) obj required: (bool) required instance: (bool) instance;
+(int) calledBlockCount;
@end
@interface FreedNotifier : NSObject {

Просмотреть файл

@ -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<ProtocolWithBlockProperties>) 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<ObjCProtocolBlockTest>) 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<ProtocolWithBlockProperties>) obj
{
obj.myRequiredProperty ();
}
+(void) setProtocolWithBlockProperties: (id<ProtocolWithBlockProperties>) 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

Просмотреть файл

@ -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<ProtocolMemberAttribute> 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,27 +4194,12 @@ 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;
@ -4130,6 +4222,8 @@ namespace Registrar {
return createMethod;
}
}
}
}
return null;
@ -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; }