[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:
Rolf Bjarne Kvinge 2022-02-15 22:48:23 +01:00 коммит произвёл GitHub
Родитель d7cd0f04df
Коммит 97afd484d2
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
5 изменённых файлов: 126 добавлений и 9 удалений

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

@ -1206,7 +1206,11 @@ namespace ObjCRuntime {
var ctorArguments = new object [1];
#if NET
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
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,14 +3194,20 @@ 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 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) {

9
tools/mtouch/Errors.designer.cs сгенерированный
Просмотреть файл

@ -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&apos;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 &apos;{0}&apos; compiler. Please install Xcode &apos;Command-Line Tools&apos; 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>