Skip to content

Commit

Permalink
[NativeAOT] Integrate TryCreatePeer (#9746)
Browse files Browse the repository at this point in the history
Context: dbb0b92
Context: dotnet/java-interop@dd3c1d0

Commit dbb0b92 had to copy significant parts of
`JniRuntime.JniValueManager.CreatePeer()` in order to get the
desired semantics.  dotnet/java-interop@dd3c1d05 added a
`TryCreatePeer()` virtual method in an effort to reduce the
amount of copied code required.

Update `NativeAotValueManager` to override the new method, allowing
us to remove most of the `CreatePeer()` code added in dbb0b92.
Additionally, override `GetInvokerTypeCore()` within
`NativeAotTypeManager` so that .NET for Android -style
`Invoker` types can be appropriately resolved.
  • Loading branch information
jonpryor authored Feb 4, 2025
1 parent b09d51a commit 70a4f93
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 143 deletions.
40 changes: 40 additions & 0 deletions samples/NativeAOT/NativeAotTypeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace NativeAOT;

partial class NativeAotTypeManager : JniRuntime.JniTypeManager {

const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors;
internal const DynamicallyAccessedMemberTypes Methods = DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods;
internal const DynamicallyAccessedMemberTypes MethodsAndPrivateNested = Methods | DynamicallyAccessedMemberTypes.NonPublicNestedTypes;

Expand All @@ -28,6 +29,45 @@ public NativeAotTypeManager ()
AndroidLog.Print (AndroidLogLevel.Info, "NativeAotTypeManager", $"# jonp: NativeAotTypeManager()");
}

protected override Type? GetInvokerTypeCore (Type type)
{
const string suffix = "Invoker";

// https://github.com/xamarin/xamarin-android/blob/5472eec991cc075e4b0c09cd98a2331fb93aa0f3/src/Microsoft.Android.Sdk.ILLink/MarkJavaObjects.cs#L176-L186
const string assemblyGetTypeMessage = "'Invoker' types are preserved by the MarkJavaObjects trimmer step.";
const string makeGenericTypeMessage = "Generic 'Invoker' types are preserved by the MarkJavaObjects trimmer step.";

[UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = assemblyGetTypeMessage)]
[UnconditionalSuppressMessage ("Trimming", "IL2073", Justification = assemblyGetTypeMessage)]
[return: DynamicallyAccessedMembers (Constructors)]
static Type? AssemblyGetType (Assembly assembly, string typeName) =>
assembly.GetType (typeName);

[UnconditionalSuppressMessage ("Trimming", "IL2055", Justification = makeGenericTypeMessage)]
[return: DynamicallyAccessedMembers (Constructors)]
static Type MakeGenericType (
[DynamicallyAccessedMembers (Constructors)]
Type type,
Type [] arguments) =>
// FIXME: https://github.com/dotnet/java-interop/issues/1192
#pragma warning disable IL3050
type.MakeGenericType (arguments);
#pragma warning restore IL3050

Type[] arguments = type.GetGenericArguments ();
if (arguments.Length == 0)
return AssemblyGetType (type.Assembly, type + suffix) ?? base.GetInvokerTypeCore (type);
Type definition = type.GetGenericTypeDefinition ();
int bt = definition.FullName!.IndexOf ("`", StringComparison.Ordinal);
if (bt == -1)
throw new NotSupportedException ("Generic type doesn't follow generic type naming convention! " + type.FullName);
Type? suffixDefinition = AssemblyGetType (definition.Assembly,
definition.FullName.Substring (0, bt) + suffix + definition.FullName.Substring (bt));
if (suffixDefinition == null)
return base.GetInvokerTypeCore (type);
return MakeGenericType (suffixDefinition, arguments);
}

public override void RegisterNativeMembers (
JniType nativeClass,
[DynamicallyAccessedMembers (MethodsAndPrivateNested)]
Expand Down
145 changes: 2 additions & 143 deletions samples/NativeAOT/NativeAotValueManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -253,142 +253,11 @@ public override List<JniSurfacedPeerInfo> GetSurfacedPeers ()
}
}

public override IJavaPeerable? CreatePeer (
ref JniObjectReference reference,
JniObjectReferenceOptions transfer,
[DynamicallyAccessedMembers (Constructors)]
Type? targetType)
{
if (!reference.IsValid) {
return null;
}

targetType = targetType ?? typeof (global::Java.Interop.JavaObject);
targetType = GetPeerType (targetType);

if (!typeof (IJavaPeerable).IsAssignableFrom (targetType))
throw new ArgumentException ($"targetType `{targetType.AssemblyQualifiedName}` must implement IJavaPeerable!", nameof (targetType));

var targetSig = Runtime.TypeManager.GetTypeSignature (targetType);
if (!targetSig.IsValid || targetSig.SimpleReference == null) {
throw new ArgumentException ($"Could not determine Java type corresponding to `{targetType.AssemblyQualifiedName}`.", nameof (targetType));
}

var refClass = JniEnvironment.Types.GetObjectClass (reference);
JniObjectReference targetClass;
try {
targetClass = JniEnvironment.Types.FindClass (targetSig.SimpleReference);
} catch (Exception e) {
JniObjectReference.Dispose (ref refClass);
throw new ArgumentException ($"Could not find Java class `{targetSig.SimpleReference}`.",
nameof (targetType),
e);
}

if (!JniEnvironment.Types.IsAssignableFrom (refClass, targetClass)) {
JniObjectReference.Dispose (ref refClass);
JniObjectReference.Dispose (ref targetClass);
return null;
}

JniObjectReference.Dispose (ref targetClass);

var proxy = CreatePeerProxy (ref refClass, targetType, ref reference, transfer);

if (proxy == null) {
throw new NotSupportedException (string.Format ("Could not find an appropriate constructable wrapper type for Java type '{0}', targetType='{1}'.",
JniEnvironment.Types.GetJniTypeNameFromInstance (reference), targetType));
}

proxy.SetJniManagedPeerState (proxy.JniManagedPeerState | JniManagedPeerStates.Replaceable);
return proxy;
}

[return: DynamicallyAccessedMembers (Constructors)]
static Type GetPeerType ([DynamicallyAccessedMembers (Constructors)] Type type)
{
if (type == typeof (object))
return typeof (global::Java.Interop.JavaObject);
if (type == typeof (IJavaPeerable))
return typeof (global::Java.Interop.JavaObject);
if (type == typeof (Exception))
return typeof (global::Java.Interop.JavaException);
return type;
}

IJavaPeerable? CreatePeerProxy (
ref JniObjectReference klass,
[DynamicallyAccessedMembers (Constructors)]
Type fallbackType,
ref JniObjectReference reference,
JniObjectReferenceOptions options)
{
var jniTypeName = JniEnvironment.Types.GetJniTypeNameFromClass (klass);

Type? type = null;
while (jniTypeName != null) {
JniTypeSignature sig;
if (!JniTypeSignature.TryParse (jniTypeName, out sig))
return null;

type = Runtime.TypeManager.GetType (sig);

if (type != null) {
var peer = TryCreatePeerProxy (type, ref reference, options);
if (peer != null) {
return peer;
}
}

var super = JniEnvironment.Types.GetSuperclass (klass);
jniTypeName = super.IsValid
? JniEnvironment.Types.GetJniTypeNameFromClass (super)
: null;

JniObjectReference.Dispose (ref klass, JniObjectReferenceOptions.CopyAndDispose);
klass = super;
}
JniObjectReference.Dispose (ref klass, JniObjectReferenceOptions.CopyAndDispose);

return TryCreatePeerProxy (fallbackType, ref reference, options);
}

[return: DynamicallyAccessedMembers (Constructors)]
static Type? GetInvokerType (Type type)
{
// https://github.com/xamarin/xamarin-android/blob/5472eec991cc075e4b0c09cd98a2331fb93aa0f3/src/Microsoft.Android.Sdk.ILLink/MarkJavaObjects.cs#L176-L186
const string makeGenericTypeMessage = "Generic 'Invoker' types are preserved by the MarkJavaObjects trimmer step.";

[UnconditionalSuppressMessage ("Trimming", "IL2055", Justification = makeGenericTypeMessage)]
[return: DynamicallyAccessedMembers (Constructors)]
static Type MakeGenericType (
[DynamicallyAccessedMembers (Constructors)]
Type type,
Type [] arguments) =>
// FIXME: https://github.com/dotnet/java-interop/issues/1192
#pragma warning disable IL3050
type.MakeGenericType (arguments);
#pragma warning restore IL3050

var signature = type.GetCustomAttribute<JniTypeSignatureAttribute> ();
if (signature == null || signature.InvokerType == null) {
return null;
}

Type[] arguments = type.GetGenericArguments ();
if (arguments.Length == 0)
return signature.InvokerType;

return MakeGenericType (signature.InvokerType, arguments);
}

const BindingFlags ActivationConstructorBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;


static readonly Type[] XAConstructorSignature = new Type [] { typeof (IntPtr), typeof (JniHandleOwnership) };
static readonly Type[] JIConstructorSignature = new Type [] { typeof (JniObjectReference).MakeByRefType (), typeof (JniObjectReferenceOptions) };

protected virtual IJavaPeerable? TryCreatePeerProxy (Type type, ref JniObjectReference reference, JniObjectReferenceOptions options)
protected override IJavaPeerable? TryCreatePeer (ref JniObjectReference reference, JniObjectReferenceOptions options, Type type)
{
var c = type.GetConstructor (ActivationConstructorBindingFlags, null, XAConstructorSignature, null);
if (c != null) {
Expand All @@ -400,16 +269,6 @@ static Type MakeGenericType (
JniObjectReference.Dispose (ref reference, options);
return p;
}
c = type.GetConstructor (ActivationConstructorBindingFlags, null, JIConstructorSignature, null);
if (c != null) {
var args = new object[] {
reference,
options,
};
var p = (IJavaPeerable) c.Invoke (args);
reference = (JniObjectReference) args [0];
return p;
}
return null;
return base.TryCreatePeer (ref reference, options, type);
}
}

0 comments on commit 70a4f93

Please sign in to comment.