[ObjCRuntime] Keep accepting IntPtr constructors in .NET as an alternative to NativeHandle constructors. Fixes #14046. (#14145)
As a part of the breaking changes in .NET, we introduced a new type, `ObjCRuntime.NativeHandle`, to represent native handles. This meant that constructors taking taking `IntPtr handle`: ```cs public class MyUIViewController : UIViewController { protected MyUIViewController (IntPtr handle) : base (handle) { } } ``` would have to be ported to take `NativeHandle handle`: ```cs public class MyUIViewController : UIViewController { protected MyUIViewController (NativeHandle handle) : base (handle) { } } ``` The unfortunate part is that there will be no compiler warnings or errors flagging this, so users won't know to do it unless they either read the documentation (🤣) or run into the problem, googles for a while, runs into someone else who had the same problem, and applies their (probably broken) fix. So we change our logic to: 1. Look for and use an `(IntPtr)` (or `(IntPtr, bool)`) constructor in .NET if the `NativeHandle` version isn't found. 2. Show a warning. 3. Some time in the future maybe remove this hack/workaround. Fixes https://github.com/xamarin/xamarin-macios/issues/14046.
This commit is contained in:
Родитель
d7cd0f04df
Коммит
97afd484d2
|
@ -1206,7 +1206,11 @@ namespace ObjCRuntime {
|
|||
|
||||
var ctorArguments = new object [1];
|
||||
#if NET
|
||||
ctorArguments [0] = new NativeHandle (ptr);
|
||||
if (ctor.GetParameters () [0].ParameterType == typeof (IntPtr)) {
|
||||
ctorArguments [0] = ptr;
|
||||
} else {
|
||||
ctorArguments [0] = new NativeHandle (ptr);
|
||||
}
|
||||
#else
|
||||
ctorArguments [0] = ptr;
|
||||
#endif
|
||||
|
@ -1232,7 +1236,11 @@ namespace ObjCRuntime {
|
|||
|
||||
var ctorArguments = new object [2];
|
||||
#if NET
|
||||
ctorArguments [0] = new NativeHandle (ptr);
|
||||
if (ctor.GetParameters () [0].ParameterType == typeof (IntPtr)) {
|
||||
ctorArguments [0] = ptr;
|
||||
} else {
|
||||
ctorArguments [0] = new NativeHandle (ptr);
|
||||
}
|
||||
#else
|
||||
ctorArguments [0] = ptr;
|
||||
#endif
|
||||
|
@ -1253,11 +1261,17 @@ namespace ObjCRuntime {
|
|||
return rv;
|
||||
}
|
||||
var ctors = type.GetConstructors (BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
ConstructorInfo? backupConstructor = null;
|
||||
for (int i = 0; i < ctors.Length; ++i) {
|
||||
var param = ctors[i].GetParameters ();
|
||||
if (param.Length != 1)
|
||||
continue;
|
||||
#if NET
|
||||
if (param [0].ParameterType == typeof (IntPtr)) {
|
||||
backupConstructor = ctors [i];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (param [0].ParameterType != typeof (NativeHandle))
|
||||
#else
|
||||
if (param [0].ParameterType != typeof (IntPtr))
|
||||
|
@ -1268,6 +1282,16 @@ namespace ObjCRuntime {
|
|||
intptr_ctor_cache [type] = ctors [i];
|
||||
return ctors [i];
|
||||
}
|
||||
|
||||
#if NET
|
||||
if (backupConstructor is not null) {
|
||||
Console.WriteLine ("The type {0} does not have a constructor that takes an ObjCRuntime.NativeHandle parameter, but a constructor that takes an System.IntPtr parameter was found instead. It's highly recommended to change the signature of the System.IntPtr constructor to be ObjCRuntime.NativeHandle instead.", type.FullName);
|
||||
lock (intptr_ctor_cache)
|
||||
intptr_ctor_cache [type] = backupConstructor;
|
||||
return backupConstructor;
|
||||
}
|
||||
#endif
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -1278,23 +1302,40 @@ namespace ObjCRuntime {
|
|||
return rv;
|
||||
}
|
||||
var ctors = type.GetConstructors (BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
ConstructorInfo? backupConstructor = null;
|
||||
for (int i = 0; i < ctors.Length; ++i) {
|
||||
var param = ctors[i].GetParameters ();
|
||||
if (param.Length != 2)
|
||||
continue;
|
||||
|
||||
if (param [1].ParameterType != typeof (bool))
|
||||
continue;
|
||||
#if NET
|
||||
if (param [0].ParameterType == typeof (IntPtr)) {
|
||||
backupConstructor = ctors [i];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (param [0].ParameterType != typeof (NativeHandle))
|
||||
#else
|
||||
if (param [0].ParameterType != typeof (IntPtr))
|
||||
#endif
|
||||
continue;
|
||||
if (param [1].ParameterType != typeof (bool))
|
||||
continue;
|
||||
|
||||
lock (intptr_bool_ctor_cache)
|
||||
intptr_bool_ctor_cache [type] = ctors [i];
|
||||
return ctors [i];
|
||||
}
|
||||
|
||||
#if NET
|
||||
if (backupConstructor is not null) {
|
||||
Console.WriteLine ("The type {0} does not have a constructor that takes two (ObjCRuntime.NativeHandle, bool) arguments. However, a constructor that takes two (System.IntPtr, bool) parameters was found (and will be used instead). It's highly recommended to change the signature of the (System.IntPtr, bool) constructor to be (ObjCRuntime.NativeHandle, bool).", type.FullName);
|
||||
lock (intptr_bool_ctor_cache)
|
||||
intptr_bool_ctor_cache [type] = backupConstructor;
|
||||
return backupConstructor;
|
||||
}
|
||||
#endif
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -870,5 +870,62 @@ Additional information:
|
|||
{
|
||||
Assert.That (Runtime.OriginalWorkingDirectory, Is.Not.Null.And.Not.Empty, "OriginalWorkingDirectory");
|
||||
}
|
||||
|
||||
#if NET
|
||||
[Test]
|
||||
public void IntPtrCtor_1 ()
|
||||
{
|
||||
using var obj = Runtime.GetNSObject (IntPtrConstructor.New ());
|
||||
Assert.IsNotNull (obj, "NotNull");
|
||||
Assert.That (obj, Is.TypeOf<IntPtrConstructor> (), "Type");
|
||||
Assert.AreNotEqual (IntPtr.Zero, obj.Handle, "Handle");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IntPtrCtor_2 ()
|
||||
{
|
||||
using var obj = Runtime.GetNSObject<IntPtrConstructor> (IntPtrConstructor.New ());
|
||||
Assert.IsNotNull (obj, "NotNull");
|
||||
Assert.That (obj, Is.TypeOf<IntPtrConstructor> (), "Type");
|
||||
Assert.AreNotEqual (IntPtr.Zero, obj.Handle, "Handle");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IntPtrCtor_3 ()
|
||||
{
|
||||
using var obj = Runtime.GetINativeObject<IntPtrConstructor> (IntPtrConstructor.New (), false);
|
||||
Assert.IsNotNull (obj, "NotNull");
|
||||
Assert.That (obj, Is.TypeOf<IntPtrConstructor> (), "Type");
|
||||
Assert.AreNotEqual (IntPtr.Zero, obj.Handle, "Handle");
|
||||
}
|
||||
|
||||
class IntPtrConstructor : NSObject {
|
||||
IntPtrConstructor (IntPtr handle) : base (handle) {}
|
||||
|
||||
internal static IntPtr New ()
|
||||
{
|
||||
var class_handle = Class.GetHandle (typeof (IntPtrConstructor));
|
||||
var handle = Messaging.IntPtr_objc_msgSend (Messaging.IntPtr_objc_msgSend (class_handle, Selector.GetHandle ("alloc")), Selector.GetHandle ("init"));
|
||||
Messaging.void_objc_msgSend (handle, Selector.GetHandle ("autorelease"));
|
||||
return handle;
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IntPtrBoolCtor_1 ()
|
||||
{
|
||||
using var obj = Runtime.GetINativeObject<IntPtrBoolConstructor> ((IntPtr) 1234, false);
|
||||
Assert.IsNotNull (obj, "NotNull");
|
||||
Assert.That (obj, Is.TypeOf<IntPtrBoolConstructor> (), "Type");
|
||||
Assert.AreNotEqual (IntPtr.Zero, obj.Handle, "Handle");
|
||||
}
|
||||
|
||||
class IntPtrBoolConstructor : DisposableObject {
|
||||
IntPtrBoolConstructor (IntPtr handle, bool owns)
|
||||
: base (handle, owns)
|
||||
{
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3185,7 +3185,7 @@ namespace Registrar {
|
|||
ErrorHelper.ThrowIfErrors (exceptions);
|
||||
}
|
||||
|
||||
static bool HasIntPtrBoolCtor (TypeDefinition type)
|
||||
bool HasIntPtrBoolCtor (TypeDefinition type, List<Exception> exceptions)
|
||||
{
|
||||
if (!type.HasMethods)
|
||||
return false;
|
||||
|
@ -3194,15 +3194,21 @@ namespace Registrar {
|
|||
continue;
|
||||
if (method.Parameters.Count != 2)
|
||||
continue;
|
||||
if (!method.Parameters [1].ParameterType.Is ("System", "Boolean"))
|
||||
continue;
|
||||
if (Driver.IsDotNet) {
|
||||
if (method.Parameters [0].ParameterType.Is ("System", "IntPtr")) {
|
||||
// The registrar found a non-optimal type `{0}`: the type does not have a constructor that takes two (ObjCRuntime.NativeHandle, bool) arguments. However, a constructor that takes two (System.IntPtr, bool) arguments was found (and will be used instead). It's highly recommended to change the signature of the (System.IntPtr, bool) constructor to be (ObjCRuntime.NativeHandle, bool).
|
||||
exceptions.Add (ErrorHelper.CreateWarning (App, 4186, method, Errors.MT4186, type.FullName));
|
||||
return true;
|
||||
}
|
||||
if (!method.Parameters [0].ParameterType.Is ("ObjCRuntime", "NativeHandle"))
|
||||
continue;
|
||||
} else {
|
||||
if (!method.Parameters [0].ParameterType.Is ("System", "IntPtr"))
|
||||
continue;
|
||||
}
|
||||
if (method.Parameters [1].ParameterType.Is ("System", "Boolean"))
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -3672,7 +3678,7 @@ namespace Registrar {
|
|||
}
|
||||
|
||||
// verify that the type has a ctor with two parameters
|
||||
if (!HasIntPtrBoolCtor (nativeObjType))
|
||||
if (!HasIntPtrBoolCtor (nativeObjType, exceptions))
|
||||
throw ErrorHelper.CreateError (4103, Errors.MT4103, nativeObjType.FullName, descriptiveMethodName);
|
||||
|
||||
body_setup.AppendLine ("MonoType *paramtype{0} = NULL;", i);
|
||||
|
@ -3796,7 +3802,7 @@ namespace Registrar {
|
|||
}
|
||||
|
||||
// verify that the type has a ctor with two parameters
|
||||
if (!HasIntPtrBoolCtor (nativeObjType))
|
||||
if (!HasIntPtrBoolCtor (nativeObjType, exceptions))
|
||||
throw ErrorHelper.CreateError (4103, Errors.MT4103, nativeObjType.FullName, descriptiveMethodName);
|
||||
|
||||
if (!td.IsInterface) {
|
||||
|
|
|
@ -3106,6 +3106,15 @@ namespace Xamarin.Bundler {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The registrar found a non-optimal type `{0}`: the type does not have a constructor that takes two (ObjCRuntime.NativeHandle, bool) arguments. However, a constructor that takes two (System.IntPtr, bool) arguments was found (and will be used instead). It's highly recommended to change the signature of the (System.IntPtr, bool) constructor to be (ObjCRuntime.NativeHandle, bool)..
|
||||
/// </summary>
|
||||
public static string MT4186 {
|
||||
get {
|
||||
return ResourceManager.GetString("MT4186", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Missing '{0}' compiler. Please install Xcode 'Command-Line Tools' component
|
||||
/// .
|
||||
|
|
|
@ -1882,6 +1882,10 @@
|
|||
</value>
|
||||
</data>
|
||||
|
||||
<data name="MT4186" xml:space="preserve">
|
||||
<value>The registrar found a non-optimal type `{0}`: the type does not have a constructor that takes two (ObjCRuntime.NativeHandle, bool) arguments. However, a constructor that takes two (System.IntPtr, bool) arguments was found (and will be used instead). It's highly recommended to change the signature of the (System.IntPtr, bool) constructor to be (ObjCRuntime.NativeHandle, bool).</value>
|
||||
</data>
|
||||
|
||||
<data name="MT5101" xml:space="preserve">
|
||||
<value>Missing '{0}' compiler. Please install Xcode 'Command-Line Tools' component
|
||||
</value>
|
||||
|
|
Загрузка…
Ссылка в новой задаче