Closed jonpryor closed 9 months ago
It should be explicitly noted that at this time ManagedPeer.cs
is not used by .NET Android. We can thus fix it any way we wish.
That said, the "core" architecture within Java.Interop.dll
and .NET Android rhyme here; .NET Android doesn't use ManagedPeer.registerNativeMembers()
; it instead uses Runtime.register()
, which has the same semantics (just a different parameter ordering). .NET Android doesn't use ManagedPeer.construct()
; it instead uses TypeManager.Activate()
, which has the same semantics (just a different parameter ordering).
The benefit to there being two similar-yet-distinct implementations is that we can prototype things in "core" -- e.g. rethink the ManagedPeer.java
methods and semantics -- without breaking .NET Android, while keeping the "rhyming" concerns in mind (e.g. anything that requires jnimarshalmethod-gen
or LLVM Marshal Methods is a non-starter, which in turn means that native
methods for constructors, while appealing, is likely not viable in the short term).
Context: https://github.com/xamarin/java.interop/pull/1153 Context: https://github.com/xamarin/java.interop/issues/1157
From the PR #1153
README.md
:Which brings us to https://github.com/xamarin/java.interop/issues/1157#issuecomment-1799591016:
https://github.com/xamarin/java.interop/blob/320636df084d377e1fdabef6fa6d28feb9ede6f2/src/Java.Interop/Java.Interop/ManagedPeer.cs#L93
https://github.com/xamarin/java.interop/blob/320636df084d377e1fdabef6fa6d28feb9ede6f2/src/Java.Interop/Java.Interop/ManagedPeer.cs#L156
https://github.com/xamarin/java.interop/blob/320636df084d377e1fdabef6fa6d28feb9ede6f2/src/Java.Interop/Java.Interop/ManagedPeer.cs#L198
How do we fix these? These are
Type.GetType()
invocations for string values which come from Java.Can they be fixed?
We can just add
UnconditionalSuppressMessageAttribute
to entirely ignore the warning. This will cause things to break on NativeAOT, though, and the whole point to fixing things is to make things linker friendly and usable on NativeAOT.PR #1153 implements a "punt" solution to the problem: instead of
Type.GetType()
, introduce aJniRuntime.JniTypeManager.GetTypeFromAssemblyQualifiedName()
method which optionally doesType.GetType()
. This would likely still produce IL2057, but we could also[UncondionalSuppressMessage]
to silence the warning or somehow require that it be overridden within a NativeAOT app. This permits a path which can work with NativeAOT, but it wouldn't work by default, and would require "extra opt-in logic" in the form of a new method override.A "proper by default" solution will require separately considering the
Type.GetType()
calls withinManagedPeer.cs
.Method Registration
Method Registration is done in
ManagedPeer.RegisterNativeMembers()
, which is invoked from Java:Note that
ManagedPeer.registerNativeMembers()
is given three values:java.lang.Class
instance of the type to register methods forWe could have
ManagedPeer.RegisterNativeMembers()
useJniRuntime.JniTypeManager.GetType(JniTypeSignature)
instead ofType.GetType()
, a'la:This also "punts" on the question to
JniRuntime.JniTypeManager.GetType(JniTypeSignature)
, but that's a pre-existing (and required) extension point:https://github.com/xamarin/java.interop/blob/320636df084d377e1fdabef6fa6d28feb9ede6f2/src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs#L270-L275
The problem with this approach is that, at present, performance would be "not great", given that
JniTypeManager.GetType(JniTypeSignature)
uses LINQ.Constructor Invocation
Constructor Invocation is done in
ManagedPeer.Construct()
, which is invoked from Java:ManagedPeer.construct()
is given four separate values:java.lang.Object
instance that we need to construct the .NET side for:
-separated sequence of assembly qualified type names for the constructor signature to invoke(2) and (3) both involve
Type.GetType()
invocations.As with
ManagedPeer.RegisterNativeMembers()
, (2) could be replaced withJniEnvironment.Runtime.TypeManager.GetType()
, usingnew JniTypeSignature(JniEnvironment.Types.GetJniTypeNameFromInstance())
.(3) is more problematic.
We could resolve this through one of two mechanisms:
a. Use a JNI method signature, and parse that into
System.Type
instances at runtime. We would thus invokeManagedPeer.construct(this, "…now ignored…", "(I)V", …)
, and turn(I)V
intonew Type[]{typeof(int)}
, again viaJniEnvironment.Runtime.TypeManager.GetType()
. b. Stop usingManagedPeer.construct()
entirely, and instead use a native method declaration.(b) would result in a Java Callable Wrapper akin to:
(a) has the benefit of being easier to implement:
jcw-gen
already has the JNI method signature, and could emit it. (a) also "feels like" it would have more runtime overhead.(b) has the benefit of (probably) being faster at runtime, but is more complex. It would requires changes to:
jcw-gen
(to declare the newnative
method)JniRuntime.JniTypeManager.RegisterNativeMembers()
overrides/implementations would need to figure out how to handle these newnative
methods. .NET Android relies ongenerator
-emitted methods as part ofRegisterNativeMembers()
, but I don't see how that approach could actually work here. We could pull inSystem.Reflection.Emit
/DynamicMethod
, but we're trying to get away from that.JavaInterop1 could have
jnimarshalmethod-gen
implement it, which has the added benefit of increased efficiency, but that's not a solution for .NET Android. There be complications here.jnimarshalmethod-gen
(to emit & register these new methods), but only for Desktop Java.Base usage.Java.Interop.Export