diff --git a/docs/website/mtouch-errors.md b/docs/website/mtouch-errors.md
index 7faab8252854..034dd76d5bd9 100644
--- a/docs/website/mtouch-errors.md
+++ b/docs/website/mtouch-errors.md
@@ -3749,3 +3749,28 @@ This exception will have an inner exception which gives the reason for the failu
### MT8036: Failed to convert the value at index {index} from {type} to {type}.
This exception will have an inner exception which gives the reason for the failure.
+
+
+
+### MX8056: Failed to marshal the Objective-C object {handle} (type: {objc_type}). Could not find an existing managed instance for this object, nor was it possible to create a new managed instance of generic type {managed_type}.
+
+This occurs when the Xamarin.iOS runtime finds an Objective-C object without a
+corresponding managed wrapper object, and when trying to create that managed
+wrapper, it turns out it's not possible. This error is specific to the managed
+static registrar.
+
+There are a few reasons this may happen:
+
+* A managed wrapper existed at some point, but was collected by the GC. If the
+ native object is still alive, and later resurfaces to managed code, the
+ Xamarin.iOS runtime will try to re-create a managed wrapper instance. In
+ most cases the problem here is that the managed wrapper shouldn't have been
+ collected by the GC in the first place.
+
+ Possible causes include:
+
+ * Manually calling Dispose too early on the managed wrapper.
+ * Incorrect bindings for third-party libraries.
+ * Reference-counting bugs in third-party libraries.
+
+* This indicates a bug in Xamarin.iOS. Please file a new issue on [github](https://github.com/xamarin/xamarin-macios/issues/new).
diff --git a/src/Foundation/NSObject2.cs b/src/Foundation/NSObject2.cs
index cce28c98309a..271971ec8e06 100644
--- a/src/Foundation/NSObject2.cs
+++ b/src/Foundation/NSObject2.cs
@@ -69,6 +69,16 @@ public class NSObjectFlag {
}
#endif
+#if NET
+ // This interface will be made public when the managed static registrar is used.
+ internal interface INSObjectFactory {
+ // The method will be implemented via custom linker step if the managed static registrar is used
+ // for NSObject subclasses which have an (NativeHandle) or (IntPtr) constructor.
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ virtual static NSObject _Xamarin_ConstructNSObject (NativeHandle handle) => null;
+ }
+#endif
+
#if NET && !COREBUILD
[ObjectiveCTrackedType]
[SupportedOSPlatform ("ios")]
@@ -81,6 +91,9 @@ public partial class NSObject : INativeObject
#if !COREBUILD
, IEquatable
, IDisposable
+#endif
+#if NET
+ , INSObjectFactory
#endif
{
#if !COREBUILD
diff --git a/src/ObjCRuntime/IManagedRegistrar.cs b/src/ObjCRuntime/IManagedRegistrar.cs
index 1c2c370d78f5..426fdf9563e1 100644
--- a/src/ObjCRuntime/IManagedRegistrar.cs
+++ b/src/ObjCRuntime/IManagedRegistrar.cs
@@ -6,7 +6,6 @@
//
// Copyright 2023 Microsoft Corp
-
#if NET
#nullable enable
@@ -34,6 +33,10 @@ interface IManagedRegistrar {
// This method will be called once per assembly, and the implementation has to
// add all the interface -> wrapper type mappings to the dictionary.
void RegisterWrapperTypes (Dictionary type);
+ // Create an instance of a managed NSObject subclass for an existing Objective-C object.
+ INativeObject? ConstructNSObject (RuntimeTypeHandle typeHandle, NativeHandle nativeHandle);
+ // Create an instance of a managed NSObject subclass for an existing Objective-C object.
+ INativeObject? ConstructINativeObject (RuntimeTypeHandle typeHandle, NativeHandle nativeHandle, bool owns);
}
}
diff --git a/src/ObjCRuntime/INativeObject.cs b/src/ObjCRuntime/INativeObject.cs
index 022b1ac868b5..58c085400740 100644
--- a/src/ObjCRuntime/INativeObject.cs
+++ b/src/ObjCRuntime/INativeObject.cs
@@ -1,6 +1,7 @@
#nullable enable
using System;
+using System.Runtime.CompilerServices;
using Foundation;
#if !NET
@@ -15,6 +16,14 @@ NativeHandle Handle {
get;
}
#endif
+
+#if NET
+ // The method will be implemented via custom linker step if the managed static registrar is used
+ // for classes which have an (NativeHandle, bool) or (IntPtr, bool) constructor.
+ // This method will be made public when the managed static registrar is used.
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ internal static virtual INativeObject? _Xamarin_ConstructINativeObject (NativeHandle handle, bool owns) => null;
+#endif
}
#if !COREBUILD
diff --git a/src/ObjCRuntime/RegistrarHelper.cs b/src/ObjCRuntime/RegistrarHelper.cs
index 6b764a7dfe1f..a790af23ec24 100644
--- a/src/ObjCRuntime/RegistrarHelper.cs
+++ b/src/ObjCRuntime/RegistrarHelper.cs
@@ -208,6 +208,22 @@ internal static uint LookupRegisteredTypeId (Type type)
return entry.Registrar.LookupTypeId (type.TypeHandle);
}
+ internal static T? ConstructNSObject (Type type, NativeHandle nativeHandle)
+ where T : class, INativeObject
+ {
+ if (!TryGetMapEntry (type.Assembly.GetName ().Name!, out var entry))
+ return null;
+ return (T?) entry.Registrar.ConstructNSObject (type.TypeHandle, nativeHandle);
+ }
+
+ internal static T? ConstructINativeObject (Type type, NativeHandle nativeHandle, bool owns)
+ where T : class, INativeObject
+ {
+ if (!TryGetMapEntry (type.Assembly.GetName ().Name!, out var entry))
+ return null;
+ return (T?) entry.Registrar.ConstructINativeObject (type.TypeHandle, nativeHandle, owns);
+ }
+
// helper functions for converting between native and managed objects
static NativeHandle ManagedArrayToNSArray (object array, bool retain)
{
diff --git a/src/ObjCRuntime/Runtime.cs b/src/ObjCRuntime/Runtime.cs
index 34916f7e1611..3db1cc5e3b48 100644
--- a/src/ObjCRuntime/Runtime.cs
+++ b/src/ObjCRuntime/Runtime.cs
@@ -1260,38 +1260,65 @@ static void MissingCtor (IntPtr ptr, IntPtr klass, Type type, MissingCtorResolut
}
msg.Append (").");
+
if (sel != IntPtr.Zero || method_handle.Value != IntPtr.Zero) {
- msg.AppendLine ();
- msg.AppendLine ("Additional information:");
- if (sel != IntPtr.Zero)
- msg.Append ("\tSelector: ").Append (Selector.GetName (sel)).AppendLine ();
- if (method_handle.Value != IntPtr.Zero) {
- try {
- var method = MethodBase.GetMethodFromHandle (method_handle);
- msg.Append ($"\tMethod: ");
- if (method is not null) {
- // there's no good built-in function to format a MethodInfo :/
- msg.Append (method.DeclaringType?.FullName ?? string.Empty);
- msg.Append (".");
- msg.Append (method.Name);
- msg.Append ("(");
- var parameters = method.GetParameters ();
- for (var i = 0; i < parameters.Length; i++) {
- if (i > 0)
- msg.Append (", ");
- msg.Append (parameters [i].ParameterType.FullName);
- }
- msg.Append (")");
- } else {
- msg.Append ($"Unable to resolve RuntimeMethodHandle 0x{method_handle.Value.ToString ("x")}");
+ AppendAdditionalInformation (msg, sel, method_handle);
+ }
+
+ throw ErrorHelper.CreateError (8027, msg.ToString ());
+ }
+
+#if NET
+ static void CannotCreateManagedInstanceOfGenericType (IntPtr ptr, IntPtr klass, Type type, MissingCtorResolution resolution, IntPtr sel, RuntimeMethodHandle method_handle)
+ {
+ if (resolution == MissingCtorResolution.Ignore)
+ return;
+
+ if (klass == IntPtr.Zero)
+ klass = Class.GetClassForObject (ptr);
+
+ var msg = new StringBuilder ();
+ msg.AppendFormat (Xamarin.Bundler.Errors.MX8056 /* Failed to marshal the Objective-C object 0x{0} (type: {1}). Could not find an existing managed instance for this object, nor was it possible to create a new managed instance of generic type {2}. */, ptr.ToString ("x"), new Class (klass).Name, type.FullName);
+
+ if (sel != IntPtr.Zero || method_handle.Value != IntPtr.Zero) {
+ AppendAdditionalInformation (msg, sel, method_handle);
+ }
+
+ throw ErrorHelper.CreateError (8056, msg.ToString ());
+ }
+#endif
+
+ static void AppendAdditionalInformation (StringBuilder msg, IntPtr sel, RuntimeMethodHandle method_handle)
+ {
+ msg.AppendLine ();
+ msg.AppendLine ("Additional information:");
+ if (sel != IntPtr.Zero)
+ msg.Append ("\tSelector: ").Append (Selector.GetName (sel)).AppendLine ();
+ if (method_handle.Value != IntPtr.Zero) {
+ try {
+ var method = MethodBase.GetMethodFromHandle (method_handle);
+ msg.Append ($"\tMethod: ");
+ if (method is not null) {
+ // there's no good built-in function to format a MethodInfo :/
+ msg.Append (method.DeclaringType?.FullName ?? string.Empty);
+ msg.Append (".");
+ msg.Append (method.Name);
+ msg.Append ("(");
+ var parameters = method.GetParameters ();
+ for (var i = 0; i < parameters.Length; i++) {
+ if (i > 0)
+ msg.Append (", ");
+ msg.Append (parameters [i].ParameterType.FullName);
}
- msg.AppendLine ();
- } catch (Exception ex) {
- msg.Append ($"\tMethod: Unable to resolve RuntimeMethodHandle 0x{method_handle.Value.ToString ("x")}: {ex.Message}");
+ msg.Append (")");
+ } else {
+ msg.Append ($"Unable to resolve RuntimeMethodHandle 0x{method_handle.Value.ToString ("x")}");
}
+ msg.AppendLine ();
+ } catch (Exception ex) {
+ msg.Append ($"\tMethod: Unable to resolve RuntimeMethodHandle 0x{method_handle.Value.ToString ("x")}: {ex.Message}");
}
}
- throw ErrorHelper.CreateError (8027, msg.ToString ());
}
static NSObject? ConstructNSObject (IntPtr ptr, IntPtr klass, MissingCtorResolution missingCtorResolution)
@@ -1310,19 +1337,61 @@ static void MissingCtor (IntPtr ptr, IntPtr klass, Type type, MissingCtorResolut
return ConstructNSObject (ptr, typeof (T), MissingCtorResolution.ThrowConstructor1NotFound);
}
- // The generic argument T is only used to cast the return value.
// The 'selector' and 'method' arguments are only used in error messages.
- static T? ConstructNSObject (IntPtr ptr, Type type, MissingCtorResolution missingCtorResolution) where T : class, INativeObject
+ static T? ConstructNSObject (IntPtr ptr, Type type, MissingCtorResolution missingCtorResolution) where T : NSObject
{
return ConstructNSObject (ptr, type, missingCtorResolution, IntPtr.Zero, default (RuntimeMethodHandle));
}
- // The generic argument T is only used to cast the return value.
// The 'selector' and 'method' arguments are only used in error messages.
+#if NET
+ static T? ConstructNSObject (IntPtr ptr, Type type, MissingCtorResolution missingCtorResolution, IntPtr sel, RuntimeMethodHandle method_handle) where T : NSObject, INSObjectFactory
+#else
static T? ConstructNSObject (IntPtr ptr, Type type, MissingCtorResolution missingCtorResolution, IntPtr sel, RuntimeMethodHandle method_handle) where T : class, INativeObject
+#endif
{
if (type is null)
throw new ArgumentNullException (nameof (type));
+#if NET
+ if (Runtime.IsManagedStaticRegistrar) {
+ T? instance = default;
+ var nativeHandle = new NativeHandle (ptr);
+
+ // We want to create an instance of `type` and if we have the chance to use the factory method
+ // on the generic type, we will prefer it to using the lookup table.
+ if (typeof (T) == type
+ && typeof (T) != typeof (NSObject)
+ && !(typeof (T).IsInterface || typeof (T).IsAbstract)) {
+ instance = ConstructNSObjectViaFactoryMethod (nativeHandle);
+ }
+
+ // Generic types can only be instantiated through the factory method and if that failed, we can't
+ // fall back to the lookup tables and we need to stop here.
+ if (type.IsGenericType && instance is null) {
+ CannotCreateManagedInstanceOfGenericType (ptr, IntPtr.Zero, type, missingCtorResolution, sel, method_handle);
+ return null;
+ }
+
+ // If we couldn't create an instance of T through the factory method, we'll use the lookup table
+ // based on the RuntimeTypeHandle.
+ //
+ // This isn't possible for generic types - we don't know the type arguments at compile time
+ // (otherwise we would be able to create an instance of T through the factory method).
+ // For non-generic types, we can call the NativeHandle constructor based on the RuntimeTypeHandle.
+
+ if (instance is null) {
+ instance = RegistrarHelper.ConstructNSObject (type, nativeHandle);
+ }
+
+ if (instance is null) {
+ // If we couldn't create an instance using the lookup table either, it means `type` doesn't contain
+ // a suitable constructor.
+ MissingCtor (ptr, IntPtr.Zero, type, missingCtorResolution, sel, method_handle);
+ }
+
+ return instance;
+ }
+#endif
var ctor = GetIntPtrConstructor (type);
@@ -1343,6 +1412,17 @@ static void MissingCtor (IntPtr ptr, IntPtr klass, Type type, MissingCtorResolut
#endif
return (T) ctor.Invoke (ctorArguments);
+
+#if NET
+ // It isn't possible to call T._Xamarin_ConstructNSObject (...) directly from the parent function. For some
+ // types, the app crashes with a SIGSEGV:
+ //
+ // error: * Assertion at /Users/runner/work/1/s/src/mono/mono/mini/mini-generic-sharing.c:2283, condition `m_class_get_vtable (info->klass)' not met
+ //
+ // When the same call is made from a separate function, it works fine.
+ static T? ConstructNSObjectViaFactoryMethod (NativeHandle handle)
+ => T._Xamarin_ConstructNSObject (handle) as T;
+#endif
}
// The generic argument T is only used to cast the return value.
@@ -1354,6 +1434,56 @@ static void MissingCtor (IntPtr ptr, IntPtr klass, Type type, MissingCtorResolut
if (type.IsByRef)
type = type.GetElementType ()!;
+#if NET
+ if (Runtime.IsManagedStaticRegistrar) {
+ var nativeHandle = new NativeHandle (ptr);
+ T? instance = null;
+
+ // We want to create an instance of `type` and if we have the chance to use the factory method
+ // on the generic type, we will prefer it to using the lookup table.
+ if (typeof (T) == type
+ && typeof (T) != typeof (INativeObject)
+ && typeof (T) != typeof (NSObject)
+ && !(typeof (T).IsInterface || typeof (T).IsAbstract)) {
+ instance = ConstructINativeObjectViaFactoryMethod (nativeHandle, owns);
+ }
+
+ // Generic types can only be instantiated through the factory method and if that failed, we can't
+ // fall back to the lookup tables and we need to stop here.
+ if (type.IsGenericType && instance is null) {
+ CannotCreateManagedInstanceOfGenericType (ptr, IntPtr.Zero, type, missingCtorResolution, sel, method_handle);
+ return null;
+ }
+
+ // If we couldn't create an instance of T through the factory method, we'll use the lookup table
+ // based on the RuntimeTypeHandle.
+ //
+ // This isn't possible for generic types - we don't know the type arguments at compile time
+ // (otherwise we would be able to create an instance of T through the factory method).
+ // For non-generic types, we can call the NativeHandle constructor based on the RuntimeTypeHandle.
+
+ // If type is an NSObject, we prefer the NSObject lookup table
+ if (instance is null && type != typeof (NSObject) && type.IsSubclassOf (typeof (NSObject))) {
+ instance = (T?)(INativeObject?) RegistrarHelper.ConstructNSObject (type, nativeHandle);
+ if (instance is not null && owns) {
+ Runtime.TryReleaseINativeObject (instance);
+ }
+ }
+
+ if (instance is null && type != typeof (INativeObject)) {
+ instance = RegistrarHelper.ConstructINativeObject (type, nativeHandle, owns);
+ }
+
+ if (instance is null) {
+ // If we couldn't create an instance using the lookup table either, it means `type` doesn't contain
+ // a suitable constructor.
+ MissingCtor (ptr, IntPtr.Zero, type, missingCtorResolution, sel, method_handle);
+ }
+
+ return instance;
+ }
+#endif
+
var ctor = GetIntPtr_BoolConstructor (type);
if (ctor is null) {
@@ -1374,6 +1504,17 @@ static void MissingCtor (IntPtr ptr, IntPtr klass, Type type, MissingCtorResolut
ctorArguments [1] = owns;
return (T?) ctor.Invoke (ctorArguments);
+
+#if NET
+ // It isn't possible to call T._Xamarin_ConstructINativeObject (...) directly from the parent function. For some
+ // types, the app crashes with a SIGSEGV:
+ //
+ // error: * Assertion at /Users/runner/work/1/s/src/mono/mono/mini/mini-generic-sharing.c:2283, condition `m_class_get_vtable (info->klass)' not met
+ //
+ // When the same call is made from a separate function, it works fine.
+ static T? ConstructINativeObjectViaFactoryMethod (NativeHandle nativeHandle, bool owns)
+ => T._Xamarin_ConstructINativeObject (nativeHandle, owns) as T;
+#endif
}
static IntPtr CreateNSObject (IntPtr type_gchandle, IntPtr handle, NSObject.Flags flags)
@@ -1753,7 +1894,7 @@ static Type LookupINativeObjectImplementation (IntPtr ptr, Type target_type, Typ
// native objects and NSObject instances.
throw ErrorHelper.CreateError (8004, $"Cannot create an instance of {implementation.FullName} for the native object 0x{ptr:x} (of type '{Class.class_getName (Class.GetClassForObject (ptr))}'), because another instance already exists for this native object (of type {o.GetType ().FullName}).");
}
- return ConstructNSObject (ptr, implementation, MissingCtorResolution.ThrowConstructor1NotFound, sel, method_handle);
+ return ConstructNSObject (ptr, implementation!, MissingCtorResolution.ThrowConstructor1NotFound, sel, method_handle);
}
return ConstructINativeObject (ptr, owns, implementation, MissingCtorResolution.ThrowConstructor2NotFound, sel, method_handle);
@@ -1810,10 +1951,22 @@ static Type LookupINativeObjectImplementation (IntPtr ptr, Type target_type, Typ
// native objects and NSObject instances.
throw ErrorHelper.CreateError (8004, $"Cannot create an instance of {implementation.FullName} for the native object 0x{ptr:x} (of type '{Class.class_getName (Class.GetClassForObject (ptr))}'), because another instance already exists for this native object (of type {o.GetType ().FullName}).");
}
- var rv = (T?) ConstructNSObject (ptr, implementation, MissingCtorResolution.ThrowConstructor1NotFound);
+#if NET
+ if (!Runtime.IsManagedStaticRegistrar) {
+ // For other registrars other than managed-static the generic parameter of ConstructNSObject is used
+ // only to cast the return value so we can safely pass NSObject here to satisfy the constraints of the
+ // generic parameter.
+ var rv = (T?)(INativeObject?) ConstructNSObject (ptr, implementation, MissingCtorResolution.ThrowConstructor1NotFound, sel, method_handle);
+ if (owns)
+ TryReleaseINativeObject (rv);
+ return rv;
+ }
+#else
+ var rv = ConstructNSObject (ptr, implementation, MissingCtorResolution.ThrowConstructor1NotFound, sel, method_handle);
if (owns)
TryReleaseINativeObject (rv);
return rv;
+#endif
}
return ConstructINativeObject (ptr, owns, implementation, MissingCtorResolution.ThrowConstructor2NotFound, sel, method_handle);
diff --git a/tools/common/StaticRegistrar.cs b/tools/common/StaticRegistrar.cs
index 6ce1973a8d9c..5a0bb3144b1c 100644
--- a/tools/common/StaticRegistrar.cs
+++ b/tools/common/StaticRegistrar.cs
@@ -839,6 +839,9 @@ protected override bool ContainsPlatformReference (AssemblyDefinition assembly)
}
protected override IEnumerable CollectTypes (AssemblyDefinition assembly)
+ => GetAllTypes (assembly);
+
+ internal static IEnumerable GetAllTypes (AssemblyDefinition assembly)
{
var queue = new Queue ();
diff --git a/tools/dotnet-linker/AppBundleRewriter.cs b/tools/dotnet-linker/AppBundleRewriter.cs
index 92f6384e547b..3f5ae20bdb5a 100644
--- a/tools/dotnet-linker/AppBundleRewriter.cs
+++ b/tools/dotnet-linker/AppBundleRewriter.cs
@@ -383,6 +383,12 @@ public TypeReference ObjCRuntime_IManagedRegistrar {
}
}
+ public TypeReference Foundation_INSObjectFactory {
+ get {
+ return GetTypeReference (PlatformAssembly, "Foundation.INSObjectFactory", out var _);
+ }
+ }
+
public TypeReference ObjCRuntime_INativeObject {
get {
return GetTypeReference (PlatformAssembly, "ObjCRuntime.INativeObject", out var _);
@@ -749,6 +755,49 @@ public MethodReference IManagedRegistrar_LookupTypeId {
}
}
+ public MethodReference IManagedRegistrar_ConstructNSObject {
+ get {
+ return GetMethodReference (PlatformAssembly,
+ ObjCRuntime_IManagedRegistrar, "ConstructNSObject",
+ isStatic: false,
+ System_RuntimeTypeHandle,
+ ObjCRuntime_NativeHandle);
+ }
+ }
+
+ public MethodReference INSObjectFactory__Xamarin_ConstructNSObject {
+ get {
+ return GetMethodReference (PlatformAssembly,
+ Foundation_INSObjectFactory, "_Xamarin_ConstructNSObject",
+ nameof (INSObjectFactory__Xamarin_ConstructNSObject),
+ isStatic: true,
+ ObjCRuntime_NativeHandle);
+ }
+ }
+
+ public MethodReference IManagedRegistrar_ConstructINativeObject {
+ get {
+ return GetMethodReference (PlatformAssembly,
+ ObjCRuntime_IManagedRegistrar, "ConstructINativeObject",
+ nameof (IManagedRegistrar_ConstructINativeObject),
+ isStatic: false,
+ System_RuntimeTypeHandle,
+ ObjCRuntime_NativeHandle,
+ System_Boolean);
+ }
+ }
+
+ public MethodReference INativeObject__Xamarin_ConstructINativeObject {
+ get {
+ return GetMethodReference (PlatformAssembly,
+ ObjCRuntime_INativeObject, "_Xamarin_ConstructINativeObject",
+ nameof (INativeObject__Xamarin_ConstructINativeObject),
+ isStatic: true,
+ ObjCRuntime_NativeHandle,
+ System_Boolean);
+ }
+ }
+
public MethodReference IManagedRegistrar_RegisterWrapperTypes {
get {
return GetMethodReference (PlatformAssembly, ObjCRuntime_IManagedRegistrar, "RegisterWrapperTypes", (v) =>
@@ -1091,6 +1140,15 @@ public MethodReference Runtime_RetainAndAutoreleaseNativeObject {
}
}
+ public MethodReference Runtime_TryReleaseINativeObject {
+ get {
+ return GetMethodReference (PlatformAssembly,
+ ObjCRuntime_Runtime, "TryReleaseINativeObject",
+ isStatic: true,
+ ObjCRuntime_INativeObject);
+ }
+ }
+
public MethodReference UnmanagedCallersOnlyAttribute_Constructor {
get {
return GetMethodReference (CorlibAssembly, "System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute", ".ctor", (v) => v.IsDefaultConstructor ());
diff --git a/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs b/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs
index 28dc1c22f233..803a063e1de7 100644
--- a/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs
+++ b/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
@@ -123,6 +124,8 @@ void CreateRegistrarType (AssemblyTrampolineInfo info)
GenerateLookupType (info, registrarType, types);
GenerateLookupTypeId (info, registrarType, types);
GenerateRegisterWrapperTypes (registrarType);
+ GenerateConstructNSObject (registrarType);
+ GenerateConstructINativeObject (registrarType);
// Make sure the linker doesn't sweep away anything we just generated.
Annotations.Mark (registrarType);
@@ -201,6 +204,13 @@ List GetTypesToRegister (TypeDefinition registrarType, AssemblyTrampol
return types;
}
+ IEnumerable GetRelevantTypes (Func isRelevant)
+ => StaticRegistrar.GetAllTypes (abr.CurrentAssembly)
+ .Cast ()
+ .Where (type => !IsTrimmed (type))
+ .Where (type => type.Module.Assembly == abr.CurrentAssembly)
+ .Where (isRelevant);
+
bool IsTrimmed (MemberReference type)
{
return StaticRegistrar.IsTrimmed (type, Annotations);
@@ -281,6 +291,245 @@ void GenerateLookupType (AssemblyTrampolineInfo infos, TypeDefinition registrarT
il.Emit (OpCodes.Ret);
}
+ void GenerateConstructNSObject (TypeDefinition registrarType)
+ {
+ var createInstanceMethod = registrarType.AddMethod ("ConstructNSObject", MethodAttributes.Private | MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.HideBySig, abr.ObjCRuntime_INativeObject);
+ var typeHandleParameter = createInstanceMethod.AddParameter ("typeHandle", abr.System_RuntimeTypeHandle);
+ var nativeHandleParameter = createInstanceMethod.AddParameter ("nativeHandle", abr.ObjCRuntime_NativeHandle);
+ createInstanceMethod.Overrides.Add (abr.IManagedRegistrar_ConstructNSObject);
+ var body = createInstanceMethod.CreateBody (out var il);
+
+ // We generate something like this:
+ // if (RuntimeTypeHandle.Equals (typeHandle, typeof (TypeA).TypeHandle))
+ // return new TypeA (nativeHandle);
+ // if (RuntimeTypeHandle.Equals (typeHandle, typeof (TypeB).TypeHandle))
+ // return new TypeB (nativeHandle);
+ // return null;
+
+ var types = GetRelevantTypes (type => type.IsNSObject (DerivedLinkContext) && !type.IsAbstract && !type.IsInterface);
+
+ foreach (var type in types) {
+ var ctorRef = FindNSObjectConstructor (type);
+ if (ctorRef is null) {
+ Driver.Log (9, $"Cannot include {type.FullName} in ConstructNSObject because it doesn't have a suitable constructor");
+ continue;
+ }
+
+ var ctor = abr.CurrentAssembly.MainModule.ImportReference (ctorRef);
+ if (IsTrimmed (ctor))
+ Annotations.Mark (ctor.Resolve ());
+
+ // We can only add a type to the table if it's not an open type.
+ if (!ManagedRegistrarStep.IsOpenType (type)) {
+ EnsureVisible (createInstanceMethod, ctor);
+
+ il.Emit (OpCodes.Ldarga_S, typeHandleParameter);
+ il.Emit (OpCodes.Ldtoken, type);
+ il.Emit (OpCodes.Call, abr.RuntimeTypeHandle_Equals);
+ var falseTarget = il.Create (OpCodes.Nop);
+ il.Emit (OpCodes.Brfalse_S, falseTarget);
+
+ il.Emit (OpCodes.Ldarg, nativeHandleParameter);
+ if (ctor.Parameters [0].ParameterType.Is ("System", "IntPtr"))
+ il.Emit (OpCodes.Call, abr.NativeObject_op_Implicit_IntPtr);
+ il.Emit (OpCodes.Newobj, ctor);
+ il.Emit (OpCodes.Ret);
+
+ il.Append (falseTarget);
+ }
+
+ // In addition to the big lookup method, implement the static factory method on the type:
+ ImplementConstructNSObjectFactoryMethod (type, ctor);
+ }
+
+ // return default (NSObject);
+ il.Emit (OpCodes.Ldnull);
+ il.Emit (OpCodes.Ret);
+ }
+
+ void GenerateConstructINativeObject (TypeDefinition registrarType)
+ {
+ var createInstanceMethod = registrarType.AddMethod ("ConstructINativeObject", MethodAttributes.Private | MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.HideBySig, abr.ObjCRuntime_INativeObject);
+ var typeHandleParameter = createInstanceMethod.AddParameter ("typeHandle", abr.System_RuntimeTypeHandle);
+ var nativeHandleParameter = createInstanceMethod.AddParameter ("nativeHandle", abr.ObjCRuntime_NativeHandle);
+ var ownsParameter = createInstanceMethod.AddParameter ("owns", abr.System_Boolean);
+ createInstanceMethod.Overrides.Add (abr.IManagedRegistrar_ConstructINativeObject);
+ var body = createInstanceMethod.CreateBody (out var il);
+
+ // We generate something like this:
+ // if (RuntimeTypeHandle.Equals (typeHandle, typeof (TypeA).TypeHandle))
+ // return new TypeA (nativeHandle, owns);
+ // if (RuntimeTypeHandle.Equals (typeHandle, typeof (TypeB).TypeHandle))
+ // return new TypeB (nativeHandle, owns);
+ // return null;
+
+ var types = GetRelevantTypes (type => type.IsNativeObject () && !type.IsAbstract && !type.IsInterface);
+
+ foreach (var type in types) {
+ var ctorRef = FindINativeObjectConstructor (type);
+
+ if (ctorRef is not null) {
+ var ctor = abr.CurrentAssembly.MainModule.ImportReference (ctorRef);
+
+ // we need to preserve the constructor because it might not be used anywhere else
+ if (IsTrimmed (ctor))
+ Annotations.Mark (ctor.Resolve ());
+
+ if (!ManagedRegistrarStep.IsOpenType (type)) {
+ EnsureVisible (createInstanceMethod, ctor);
+
+ il.Emit (OpCodes.Ldarga_S, typeHandleParameter);
+ il.Emit (OpCodes.Ldtoken, type);
+ il.Emit (OpCodes.Call, abr.RuntimeTypeHandle_Equals);
+ var falseTarget = il.Create (OpCodes.Nop);
+ il.Emit (OpCodes.Brfalse_S, falseTarget);
+
+ il.Emit (OpCodes.Ldarg, nativeHandleParameter);
+ if (ctor.Parameters [0].ParameterType.Is ("System", "IntPtr"))
+ il.Emit (OpCodes.Call, abr.NativeObject_op_Implicit_IntPtr);
+ il.Emit (OpCodes.Ldarg, ownsParameter);
+ il.Emit (OpCodes.Newobj, ctor);
+ il.Emit (OpCodes.Ret);
+
+ il.Append (falseTarget);
+ }
+ }
+
+ // In addition to the big lookup method, implement the static factory method on the type:
+ ImplementConstructINativeObjectFactoryMethod (type, ctorRef);
+ }
+
+ // return default (NSObject)
+ il.Emit (OpCodes.Ldnull);
+ il.Emit (OpCodes.Ret);
+ }
+
+ void ImplementConstructNSObjectFactoryMethod (TypeDefinition type, MethodReference ctor)
+ {
+ // skip creating the factory for NSObject itself
+ if (type.Is ("Foundation", "NSObject"))
+ return;
+
+ var createInstanceMethod = type.AddMethod ("_Xamarin_ConstructNSObject", MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.NewSlot | MethodAttributes.HideBySig, abr.Foundation_NSObject);
+ var nativeHandleParameter = createInstanceMethod.AddParameter ("nativeHandle", abr.ObjCRuntime_NativeHandle);
+ abr.Foundation_INSObjectFactory.Resolve ().IsPublic = true;
+ createInstanceMethod.Overrides.Add (abr.INSObjectFactory__Xamarin_ConstructNSObject);
+ var body = createInstanceMethod.CreateBody (out var il);
+
+ if (type.HasGenericParameters) {
+ ctor = type.CreateMethodReferenceOnGenericType (ctor, type.GenericParameters.ToArray ());
+ }
+
+ // return new TypeA (nativeHandle); // for NativeHandle ctor
+ // return new TypeA ((IntPtr) nativeHandle); // for IntPtr ctor
+ il.Emit (OpCodes.Ldarg, nativeHandleParameter);
+ if (ctor.Parameters [0].ParameterType.Is ("System", "IntPtr"))
+ il.Emit (OpCodes.Call, abr.NativeObject_op_Implicit_IntPtr);
+ il.Emit (OpCodes.Newobj, ctor);
+ il.Emit (OpCodes.Ret);
+
+ Annotations.Mark (createInstanceMethod);
+ }
+
+ void ImplementConstructINativeObjectFactoryMethod (TypeDefinition type, MethodReference? ctor)
+ {
+ // skip creating the factory for NSObject itself
+ if (type.Is ("Foundation", "NSObject"))
+ return;
+
+ // If the type is a subclass of NSObject, we prefer the NSObject "IntPtr" constructor
+ var nsobjectConstructor = type.IsNSObject (DerivedLinkContext) ? FindNSObjectConstructor (type) : null;
+ if (nsobjectConstructor is null && ctor is null)
+ return;
+
+ var createInstanceMethod = type.AddMethod ("_Xamarin_ConstructINativeObject", MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.NewSlot | MethodAttributes.HideBySig, abr.ObjCRuntime_INativeObject);
+ var nativeHandleParameter = createInstanceMethod.AddParameter ("nativeHandle", abr.ObjCRuntime_NativeHandle);
+ var ownsParameter = createInstanceMethod.AddParameter ("owns", abr.System_Boolean);
+ abr.INativeObject__Xamarin_ConstructINativeObject.Resolve ().IsPublic = true;
+ createInstanceMethod.Overrides.Add (abr.INativeObject__Xamarin_ConstructINativeObject);
+ var body = createInstanceMethod.CreateBody (out var il);
+
+ if (nsobjectConstructor is not null) {
+ // var instance = new TypeA (nativeHandle);
+ // // alternatively with a cast: new TypeA ((IntPtr) nativeHandle);
+ // if (instance is not null && owns)
+ // Runtime.TryReleaseINativeObject (instance);
+ // return instance;
+
+ if (type.HasGenericParameters) {
+ nsobjectConstructor = type.CreateMethodReferenceOnGenericType (nsobjectConstructor, type.GenericParameters.ToArray ());
+ }
+
+ il.Emit (OpCodes.Ldarg, nativeHandleParameter);
+ if (nsobjectConstructor.Parameters [0].ParameterType.Is ("System", "IntPtr"))
+ il.Emit (OpCodes.Call, abr.NativeObject_op_Implicit_IntPtr);
+ il.Emit (OpCodes.Newobj, nsobjectConstructor);
+
+ var falseTarget = il.Create (OpCodes.Nop);
+ il.Emit (OpCodes.Dup);
+ il.Emit (OpCodes.Ldnull);
+ il.Emit (OpCodes.Cgt_Un);
+ il.Emit (OpCodes.Ldarg, ownsParameter);
+ il.Emit (OpCodes.And);
+ il.Emit (OpCodes.Brfalse_S, falseTarget);
+
+ il.Emit (OpCodes.Dup);
+ il.Emit (OpCodes.Call, abr.Runtime_TryReleaseINativeObject);
+
+ il.Append (falseTarget);
+
+ il.Emit (OpCodes.Ret);
+ } else if (ctor is not null) {
+ // return new TypeA (nativeHandle, owns); // for NativeHandle ctor
+ // return new TypeA ((IntPtr) nativeHandle, owns); // IntPtr ctor
+
+ if (type.HasGenericParameters) {
+ ctor = type.CreateMethodReferenceOnGenericType (ctor, type.GenericParameters.ToArray ());
+ }
+
+ il.Emit (OpCodes.Ldarg, nativeHandleParameter);
+ if (ctor.Parameters [0].ParameterType.Is ("System", "IntPtr"))
+ il.Emit (OpCodes.Call, abr.NativeObject_op_Implicit_IntPtr);
+ il.Emit (OpCodes.Ldarg, ownsParameter);
+ il.Emit (OpCodes.Newobj, ctor);
+ il.Emit (OpCodes.Ret);
+ } else {
+ throw new UnreachableException ();
+ }
+
+ Annotations.Mark (createInstanceMethod);
+ }
+
+ static MethodReference? FindNSObjectConstructor (TypeDefinition type)
+ {
+ return FindConstructorWithOneParameter ("ObjCRuntime", "NativeHandle")
+ ?? FindConstructorWithOneParameter ("System", "IntPtr");
+
+ MethodReference? FindConstructorWithOneParameter (string ns, string cls)
+ => type.Methods.SingleOrDefault (method =>
+ method.IsConstructor
+ && !method.IsStatic
+ && method.HasParameters
+ && method.Parameters.Count == 1
+ && method.Parameters [0].ParameterType.Is (ns, cls));
+ }
+
+
+ static MethodReference? FindINativeObjectConstructor (TypeDefinition type)
+ {
+ return FindConstructorWithTwoParameters ("ObjCRuntime", "NativeHandle", "System", "Boolean")
+ ?? FindConstructorWithTwoParameters ("System", "IntPtr", "System", "Boolean");
+
+ MethodReference? FindConstructorWithTwoParameters (string ns1, string cls1, string ns2, string cls2)
+ => type.Methods.SingleOrDefault (method =>
+ method.IsConstructor
+ && !method.IsStatic
+ && method.HasParameters
+ && method.Parameters.Count == 2
+ && method.Parameters [0].ParameterType.Is (ns1, cls1)
+ && method.Parameters [1].ParameterType.Is (ns2, cls2));
+ }
+
void GenerateRegisterWrapperTypes (TypeDefinition type)
{
var method = type.AddMethod ("RegisterWrapperTypes", MethodAttributes.Private | MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.HideBySig, abr.System_Void);
@@ -473,13 +722,32 @@ static string GetMethodSignature (MethodDefinition method)
return $"{method?.ReturnType?.FullName ?? "(null)"} {method?.DeclaringType?.FullName ?? "(null)"}::{method?.Name ?? "(null)"} ({string.Join (", ", method?.Parameters?.Select (v => v?.ParameterType?.FullName + " " + v?.Name) ?? Array.Empty ())})";
}
- void EnsureVisible (MethodDefinition caller, TypeDefinition type)
+ static void EnsureVisible (MethodDefinition caller, MethodReference methodRef)
{
+ var method = methodRef.Resolve ();
+ var type = method.DeclaringType.Resolve ();
if (type.IsNested) {
- type.IsNestedPublic = true;
+ if (!method.IsPublic) {
+ method.IsFamilyOrAssembly = true;
+ }
+
+ EnsureVisible (caller, type);
+ } else if (!method.IsPublic) {
+ method.IsFamilyOrAssembly = true;
+ }
+ }
+
+ static void EnsureVisible (MethodDefinition caller, TypeReference typeRef)
+ {
+ var type = typeRef.Resolve ();
+ if (type.IsNested) {
+ if (!type.IsNestedPublic) {
+ type.IsNestedAssembly = true;
+ }
+
EnsureVisible (caller, type.DeclaringType);
- } else {
- type.IsPublic = true;
+ } else if (!type.IsPublic) {
+ type.IsNotPublic = true;
}
}
diff --git a/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs b/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs
index 0e89314fa655..8b459d0cf43c 100644
--- a/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs
+++ b/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs
@@ -1030,7 +1030,7 @@ bool EmitConversion (MethodDefinition method, ILProcessor il, TypeReference type
return false;
}
- bool IsOpenType (TypeReference tr)
+ internal static bool IsOpenType (TypeReference tr)
{
if (tr is GenericParameter)
return true;
@@ -1052,13 +1052,13 @@ bool IsOpenType (TypeReference tr)
return IsOpenType (tr.Resolve ());
}
- void EnsureVisible (MethodDefinition caller, FieldDefinition field)
+ static void EnsureVisible (MethodDefinition caller, FieldDefinition field)
{
field.IsPublic = true;
EnsureVisible (caller, field.DeclaringType);
}
- void EnsureVisible (MethodDefinition caller, TypeDefinition type)
+ static void EnsureVisible (MethodDefinition caller, TypeDefinition type)
{
if (type.IsNested) {
type.IsNestedPublic = true;
@@ -1068,7 +1068,7 @@ void EnsureVisible (MethodDefinition caller, TypeDefinition type)
}
}
- void EnsureVisible (MethodDefinition caller, MethodReference method)
+ static void EnsureVisible (MethodDefinition caller, MethodReference method)
{
var md = method.Resolve ();
md.IsPublic = true;
diff --git a/tools/mtouch/Errors.designer.cs b/tools/mtouch/Errors.designer.cs
index d770fa6bddc0..797627573c54 100644
--- a/tools/mtouch/Errors.designer.cs
+++ b/tools/mtouch/Errors.designer.cs
@@ -4292,5 +4292,14 @@ public static string MX8055 {
return ResourceManager.GetString("MX8055", resourceCulture);
}
}
+
+ ///
+ /// Looks up a localized string similar to Failed to marshal the Objective-C object 0x{0} (type: {1}). Could not find an existing managed instance for this object, nor was it possible to create a new managed instance of generic type {2}..
+ ///
+ public static string MX8056 {
+ get {
+ return ResourceManager.GetString("MX8056", resourceCulture);
+ }
+ }
}
}
diff --git a/tools/mtouch/Errors.resx b/tools/mtouch/Errors.resx
index 8ebe9f75b579..e88b0756970f 100644
--- a/tools/mtouch/Errors.resx
+++ b/tools/mtouch/Errors.resx
@@ -2254,4 +2254,8 @@
Could not find the type 'ObjCRuntime.__Registrar__' in the assembly '{0}'.
+
+
+ Failed to marshal the Objective-C object 0x{0} (type: {1}). Could not find an existing managed instance for this object, nor was it possible to create a new managed instance of generic type {2}.
+