diff --git a/src/ObjCRuntime/Blocks.cs b/src/ObjCRuntime/Blocks.cs index e596e45ad311..5dc0e62b2e73 100644 --- a/src/ObjCRuntime/Blocks.cs +++ b/src/ObjCRuntime/Blocks.cs @@ -58,7 +58,13 @@ struct XamarinBlockDescriptor { } [StructLayout (LayoutKind.Sequential)] - public unsafe struct BlockLiteral { +#if XAMCORE_5_0 + // The presence of this Dispose method is enough to be able to do a 'using var block = new BlockLiteral ()' in C# due to pattern-based using for 'ref structs': + // Ref: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/using#pattern-based-using + public unsafe ref struct BlockLiteral +#else + public unsafe struct BlockLiteral : IDisposable { +#endif #pragma warning disable 169 IntPtr isa; BlockFlags flags; @@ -82,8 +88,61 @@ static IntPtr NSConcreteStackBlock { [DllImport ("__Internal")] static extern IntPtr xamarin_get_block_descriptor (); + static MethodInfo FindTrampoline (Type trampolineType, string trampolineMethod) + { + var rv = trampolineType.GetMethod (trampolineMethod, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); + + if (rv is null) + throw new InvalidOperationException ($"Unable to find the method {trampolineMethod} in the type {trampolineType}"); + + return rv; + + } + + public BlockLiteral (void *trampoline, Delegate target, Type trampolineType, string trampolineMethod) + : this (trampoline, target, FindTrampoline (trampolineType, trampolineMethod)) + { + } + + public BlockLiteral (void *trampoline, Delegate target, MethodInfo trampolineMethod) + { + isa = IntPtr.Zero; + flags = (BlockFlags) 0; + reserved = 0; + invoke = IntPtr.Zero; + block_descriptor = IntPtr.Zero; + local_handle = IntPtr.Zero; + global_handle = IntPtr.Zero; + SetupBlock (trampoline, target, trampolineMethod); + } + + // trampoline: a function pointer to a method with the UnmanagedCallersOnly attribute. + // target: can be looked up using the first argument to the trampoline (which is a BlockLiteral*/IntPtr). + public void SetupBlock (void* trampoline, Delegate target, MethodInfo trampolineMethod) + { + var functionPointer = trampolineMethod.MethodHandle.GetFunctionPointer (); + if (functionPointer != (IntPtr) trampoline) + throw new ArgumentException ($"BlockLiteral.SetupBlock (): The trampoline and the trampolineMethod refer to different methods."); + + // TODO: assert that trampoline has an UnmanagedCallersOnly attribute (by looking at trampolineMethod) + that the first argument is either BlockLiteral* or IntPtr. + var signature = Runtime.ComputeSignature (trampolineMethod, true); + SetupBlockImpl ((IntPtr) trampoline, target, System.Text.Encoding.UTF8.GetBytes (signature)); + } + + // trampoline: a function pointer to a method with the UnmanagedCallersOnly attribute. + public void SetupBlock (void* trampoline, Delegate target, string signature) + { + SetupBlockImpl ((IntPtr) trampoline, target, System.Text.Encoding.UTF8.GetBytes (signature)); + } + + // trampoline: a function pointer to a method with the UnmanagedCallersOnly attribute. + public void SetupBlock (void* trampoline, Delegate target, byte[] utf8Signature) + { + SetupBlockImpl ((IntPtr) trampoline, target, utf8Signature); + } + [BindingImpl (BindingImplOptions.Optimizable)] - void SetupBlock (Delegate trampoline, Delegate userDelegate, bool safe) + void SetupBlock (Delegate trampoline, Delegate target, bool safe) { if (!Runtime.DynamicRegistrationSupported) throw ErrorHelper.CreateError (8026, "BlockLiteral.SetupBlock is not supported when the dynamic registrar has been linked away."); @@ -110,23 +169,37 @@ void SetupBlock (Delegate trampoline, Delegate userDelegate, bool safe) } var signature = Runtime.ComputeSignature (userMethod, blockSignature); - SetupBlockImpl (trampoline, userDelegate, safe, signature); + SetupBlockImpl (trampoline, target, safe, System.Text.Encoding.UTF8.GetBytes (signature)); + } + + unsafe void SetupBlockImpl (Delegate trampoline, Delegate target, bool safe, string signature) + { + SetupBlockImpl (trampoline, target, safe, System.Text.Encoding.UTF8.GetBytes (signature)); + } + + unsafe void SetupBlockImpl (Delegate trampoline, Delegate target, bool safe, byte[] utf8Signature) + { + var invoke = Marshal.GetFunctionPointerForDelegate (trampoline); + SetupBlockImpl (invoke, GetContext (trampoline, target, safe), utf8Signature); + } + + static object GetContext (Delegate trampoline, Delegate target, bool safe) + { + if (safe) { + return new Tuple (trampoline, target); + } else { + return target; + } } // This method is not to be called manually by user code. // This is enforced by making it private. If the SetupBlock optimization is enabled, // the linker will make it public so that it's callable from optimized user code. - unsafe void SetupBlockImpl (Delegate trampoline, Delegate userDelegate, bool safe, string signature) + unsafe void SetupBlockImpl (IntPtr invokeMethod, object context, byte[] utf8Signature) { isa = NSConcreteStackBlock; - invoke = Marshal.GetFunctionPointerForDelegate (trampoline); - object delegates; - if (safe) { - delegates = new Tuple (trampoline, userDelegate); - } else { - delegates = userDelegate; - } - local_handle = (IntPtr) GCHandle.Alloc (delegates); + invoke = invokeMethod; + local_handle = (IntPtr) GCHandle.Alloc (context); global_handle = IntPtr.Zero; flags = BlockFlags.BLOCK_HAS_COPY_DISPOSE | BlockFlags.BLOCK_HAS_SIGNATURE; @@ -137,8 +210,9 @@ unsafe void SetupBlockImpl (Delegate trampoline, Delegate userDelegate, bool saf // for the signature if we can avoid it). One descriptor is allocated for every // Block; this is potentially something the static registrar can fix, since it // should know every possible trampoline signature. - var bytes = System.Text.Encoding.UTF8.GetBytes (signature); - var desclen = sizeof (XamarinBlockDescriptor) + bytes.Length + 1 /* null character */; + var bytes = utf8Signature; + var hasNull = utf8Signature [utf8Signature.Length - 1] == 0; + var desclen = sizeof (XamarinBlockDescriptor) + bytes.Length + (hasNull ? 0 : 1 /* null character */); var descptr = Marshal.AllocHGlobal (desclen); block_descriptor = descptr; @@ -147,7 +221,8 @@ unsafe void SetupBlockImpl (Delegate trampoline, Delegate userDelegate, bool saf xblock_descriptor->descriptor.signature = descptr + sizeof (BlockDescriptor) + 4 /* signature_length */; xblock_descriptor->ref_count = 1; Marshal.Copy (bytes, 0, xblock_descriptor->descriptor.signature, bytes.Length); - Marshal.WriteByte (xblock_descriptor->descriptor.signature + bytes.Length, 0); // null terminate string + if (!hasNull) + Marshal.WriteByte (xblock_descriptor->descriptor.signature + bytes.Length, 0); // null terminate string } // trampoline must be static, and someone else needs to keep a ref to it @@ -202,23 +277,36 @@ public void SetupBlock (Delegate trampoline, Delegate userDelegate) public void CleanupBlock () { - GCHandle.FromIntPtr (local_handle).Free (); - var xblock_descriptor = (XamarinBlockDescriptor *) block_descriptor; -#pragma warning disable 420 - // CS0420: A volatile field references will not be treated as volatile - // Documentation says: "A volatile field should not normally be passed using a ref or out parameter, since it will not be treated as volatile within the scope of the function. There are exceptions to this, such as when calling an interlocked API." - // So ignoring the warning, since it's a documented exception. - var rc = Interlocked.Decrement (ref xblock_descriptor->ref_count); -#pragma warning restore 420 - - if (rc == 0) - Marshal.FreeHGlobal (block_descriptor); + if (local_handle != IntPtr.Zero) { + GCHandle.FromIntPtr (local_handle).Free (); + local_handle = IntPtr.Zero; + } + + if (block_descriptor != IntPtr.Zero) { + var xblock_descriptor = (XamarinBlockDescriptor *) block_descriptor; + #pragma warning disable 420 + // CS0420: A volatile field references will not be treated as volatile + // Documentation says: "A volatile field should not normally be passed using a ref or out parameter, since it will not be treated as volatile within the scope of the function. There are exceptions to this, such as when calling an interlocked API." + // So ignoring the warning, since it's a documented exception. + var rc = Interlocked.Decrement (ref xblock_descriptor->ref_count); + #pragma warning restore 420 + + if (rc == 0) + Marshal.FreeHGlobal (block_descriptor); + block_descriptor = IntPtr.Zero; + } } - public object Target { + public object Context { get { var handle = global_handle != IntPtr.Zero ? global_handle : local_handle; - var target = GCHandle.FromIntPtr (handle).Target; + return GCHandle.FromIntPtr (handle).Target; + } + } + + public object Target { + get { + var target = Context; var tuple = target as Tuple; if (tuple != null) return tuple.Item2; @@ -376,7 +464,19 @@ internal unsafe static void SimpleCall (Action callbackToInvoke, Action