-
Notifications
You must be signed in to change notification settings - Fork 64
Description
Scenarios
There are two scenarios for which a "generalized" type & member remapping solution would be useful:
Desugaring
See also: dotnet/android#4574 (comment)
See also: dotnet/android#6142 (comment)
The Android Runtime is responsible for running the Dalvik bytecode within e.g. classes.dex. The Android runtime and Dalvik bytecode formats have apparently changed over time in order to support newer Java language features, such as support for static interface methods:
package org.webrtc;
public /* partial */ interface EGLBase {
public static EGLBase create() { /* … */}
}Support for static interface methods was added in Java 8 and in Android v8.0 (API-26). If you want to use static interface methods on an Android device running API-25 or earlier, you must desugar the Java bytecode. The result of desugaring the above org.webrtc.EGLBase type is that the static methods are moved to a new type, with a $-CC suffix, "as if" the Java code were instead:
package org.webrtc;
public /* partial */ interface EGLBase {
// …
}
public final /* partial */ class EGLBase$-CC {
public static EGLBase create { /* … */ }
}While this works for pure Java code, this does not work with Xamarin.Android, because our bindings currently expect (require) that the types & members our binding process detects don't "move":
// "Binding" for EGLBase
namespace Org.Webrtc {
public partial interface IEGLBase {
private static readonly JniPeerMembers _members = new JniPeerMembers ("org/webrtc/EGLBase", typeof (IEGLBase));
public static IEGLBase Create()
{
const string __id = "create.()Lorg/webrtc/EglBase;"
var __rm = _members.StaticMethods.InvokeObjectMethod (__id, null);
return Java.Lang.Object.GetObject<IEGLBase>(__rm.Handle, JniHandleOwnership.TransferLocalRef);
}
}
}The new JniPeerMembers(…) invocation will use JNIEnv::FindClass() to lookup the org/webrtc/EGLBase type, and IEGLBase.Create() will use JNIEnv::GetStaticMethodID() and JNIEnv::CallStaticObjectMethod() to lookup and invoke the EGLBase.create() method.
However, when desugaring is used, there is no EGLBase.create() method. Consequently, in a "desugared" environment, a Java.Lang.NoSuchMethodError will be thrown when IEGLBase.Create() is invoked, as EGLBase.create() doesn't exist; it "should" instead be looking for EGLBase$-CC.create()!
Microsoft Intune MAM
See also: https://www.nuget.org/packages/Microsoft.Intune.MAM.Remapper.Tasks/
See also: https://docs.microsoft.com/en-us/mem/intune/developer/app-sdk-xamarin#remapper
The Microsoft Intune App SDK Xamarin Bindings is in a similar-yet-different position: it involves Java & Dalvik bytecode manipulation for various security purposes, and in order to make that work reasonably from Xamarin.Android, they also rewrite Xamarin.Android IL so that it's consistent with the manipulated Dalvik bytecode.
See readme.md and content/MonoAndroid10/remapping-config.json within the Microsoft.Intune.MAM.Remapper.Tasks NuGet package for some additional details/tidbits such as:
This task operates on assemblies to replace the base type classes which Microsoft Intune requires to implement its SDK (for example, Intune requires using a
MAMActivityin place ofActivityand methods such asOnMAMCreateinstead ofOnCreate).
"Class": {
"From": "android.app.Activity",
"To": "com.microsoft.intune.mam.client.app.MAMActivity"
},
"Methods": [
{
"MakeStatic": false,
"OriginalName": "onCreate",
"NewName": "onMAMCreate",
"OriginalParams": [
"android.os.Bundle"
]
},Discussion
Unfortunately use cases (1) and (2) differ in two significant ways:
- Desugaring requires a "primary" type and a "fallback" type for member lookup purposes. MAM doesn't require more than one type be involved, once the correct type has been looked up.
- Desugaring doesn't change method names or signatures, just the declaring type (as currently known…?). MAM does change method names and signatures.
Regardless, from the binding side, the primary API of interest is Java.Interop.JniPeerMembers: this is the type through which member bindings "happen" (see IEGLBase type, above, for an example, or see unit tests).
The problem with JniPeerMembers is that it isn't modifiable; assuming that IL rewriting isn't employed, there's no way to change the behavior of JniPeerMembers at runtime.
For class lookup, there is an "extensibility point", kinda: JniRuntime.ClassLoader_LoadClass: https://github.com/xamarin/java.interop/blob/ff2714200107fb616828b9d1013f69605791d2ba/src/Java.Interop/Java.Interop/JniRuntime.cs#L176 ; see also commit b4d44e4.
The execution path is:
-
JniPeerMembers.JniPeerTypeusesJniType.GetCachedJniType(): https://github.com/xamarin/java.interop/blob/ff2714200107fb616828b9d1013f69605791d2ba/src/Java.Interop/Java.Interop/JniPeerMembers.cs#L80-L86 -
JniType.GetCachedJniType()usesJniEnvironment.Types.FindClass():
JniEnvironmentt.Types.FindClass()can hitJniRuntime.ClassLoader_LoadClass: https://github.com/xamarin/java.interop/blob/ff2714200107fb616828b9d1013f69605791d2ba/src/Java.Interop/Java.Interop/JniEnvironment.Types.cs#L63-L86
However, this isn't a generalized lookup path; it exists because, on (older versions of?) Android, JNIEnv::FindClass() would only reliably lookup types from the main thread. Other threads wouldn't find types in classes.dex, and thus ClassLoader.loadClass() was needed instead.
Additionally, the use of ClassLoader.loadClass() is only done after trying JNIEnv::FindClass(). This isn't a (current) fix. However, it could be the "path" toward an actual fix.
Additionally, the above is only for Managed-to-Java invocation purposes. We also need to consider Java-to-Managed method registration support, as the Intune MAM tooling will e.g. rename the Java Activity.onCreate() method to Activity.onMAMCreate(). However (?), given that our normal Java Callable Wrapper generation output uses a private final method, this might not in fact matter, as we only register the native methods, not the public methods:
i.e. Intune MAM would (presumably) only update IndirectApplication.onCreate(), but not IndirectApplication.onCreate.n_onCreate(). TODO: verify this.
So long as the native methods aren't changed and parameter types aren't changed, Java-to-Managed method registration shouldn't require any changes.
Binding Background
Given an API description of:
The resulting "new style" binding is:
Note in particular the binding for SomeObject.VoidMethodWithParams():