Skip to content

[WIP] turn NativeAOT sample into a .NET MAUI app - #9747

Closed
jonathanpeppers wants to merge 4 commits into
dotnet:mainfrom
jonathanpeppers:dev/peppers/maui/nativeaot
Closed

[WIP] turn NativeAOT sample into a .NET MAUI app#9747
jonathanpeppers wants to merge 4 commits into
dotnet:mainfrom
jonathanpeppers:dev/peppers/maui/nativeaot

Conversation

@jonathanpeppers

@jonathanpeppersjonathanpeppers commented Feb 3, 2025

Copy link
Copy Markdown
Member

Unsure yet if this should ever be merged, as it requires a lot of manual steps to even build it. I'm going to leave instructions here for my future self or others.

These are the list of steps to test a .NET MAUI app on NativeAOT:

Steps already done in this PR

Steps to build the sample from here

D:\src\dotnet\maui\.dotnet\dotnet build -c Release -p:PublishAot=true

It launches!

image

@jonathanpeppersjonathanpeppers added the do-not-merge PR should not be merged. label Feb 3, 2025
Comment threadsamples/NativeAOT/NativeAotTypeManager.cs Outdated
jonpryor added a commit to dotnet/java-interop that referenced this pull request Feb 4, 2025
Context: dotnet/android#9747
Context: https://discord.com/channels/732297728826277939/732297837953679412/1336353039031734352
Context: https://discord.com/channels/732297728826277939/732297837953679412/1336358257769316372
The `[Register]` attribute provides "connector method" names, and for
interface methods this will also include the name of the type which
declares the method, which itself may be in a nested type:
namespace Android.App {
public partial class Application {
public partial interface IActivityLifecycleCallbacks : IJavaObject, IJavaPeerable {
[Register (
name: "onActivityCreated",
signature: "(Landroid/app/Activity;Landroid/os/Bundle;)V",
connector: "GetOnActivityCreated_Landroid_app_Activity_Landroid_os_Bundle_Handler:Android.App.Application/IActivityLifecycleCallbacksInvoker, Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null")]
void OnActivityCreated (Android.App.Activity activity, Android.OS.Bundle? savedInstanceState);
// …
}
}
}
This output has been largely unchanged for *years*, but there is a
problem with it: the `connector` parameter contains a nested type,
and uses `/` to separate the "outer" type from the "inner" type.
This works on MonoVM.
This *fails* on NativeAOT and CoreCLR:
Could not resolve type 'Android.App.Application/IActivityLifecycleCallbacksInvoker' in assembly 'Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=84e04ff9cfb79065'.
The `/` needs to be a `+` in order for `Type.GetType()` to find it:
[Register (
name: "onActivityCreated",
signature: "(Landroid/app/Activity;Landroid/os/Bundle;)V",
connector: "GetOnActivityCreated_Landroid_app_Activity_Landroid_os_Bundle_Handler:Android.App.Application+IActivityLifecycleCallbacksInvoker, Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null")]
void OnActivityCreated (Android.App.Activity activity, Android.OS.Bundle? savedInstanceState);
Update `generator` so that `+` is used within the `connector`
parameter.
However, to maintain compatibility with existing binding assemblies,
update `Java.Interop.Tools.JavaCallableWrappers` so that it replaces
`/` with `+` on import, so that the resulting Java Callable Wrappers
contain `+` and not `/`:
// Java Callable Wrapper
/* partial */ class MauiApplication_ActivityLifecycleCallbacks
{
public static final String __md_methods;
static {
__md_methods =
// …
"n_onActivityCreated:(Landroid/app/Activity;Landroid/os/Bundle;)V:GetOnActivityCreated_Landroid_app_Activity_Landroid_os_Bundle_Handler:Android.App.Application+IActivityLifecycleCallbacksInvoker, Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null\n" +
// …
"";
mono.android.Runtime.register ("Microsoft.Maui.MauiApplication+ActivityLifecycleCallbacks, Microsoft.Maui", MauiApplication_ActivityLifecycleCallbacks.class, __md_methods);
}
}
jonpryor added a commit to dotnet/java-interop that referenced this pull request Feb 6, 2025
Context: dotnet/android#9747
Context: https://discord.com/channels/732297728826277939/732297837953679412/1336353039031734352
Context: https://discord.com/channels/732297728826277939/732297837953679412/1336358257769316372
Context: #1302
Context: dotnet/android#9750
The `[Register]` attribute provides "connector method" names, and for
interface methods this will also include the name of the type which
declares the method, which itself may be in a nested type:
namespace Android.App {
public partial class Application {
public partial interface IActivityLifecycleCallbacks : IJavaObject, IJavaPeerable {
[Register (
name: "onActivityCreated",
signature: "(Landroid/app/Activity;Landroid/os/Bundle;)V",
connector: "GetOnActivityCreated_Landroid_app_Activity_Landroid_os_Bundle_Handler:Android.App.Application/IActivityLifecycleCallbacksInvoker, Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null")]
void OnActivityCreated (Android.App.Activity activity, Android.OS.Bundle? savedInstanceState);
// …
}
}
}
The `connector` value is copied as-is into Java Callable Wrappers,
as part of the `__md_methods` value and `Runtime.register()` call.
Given the C# type:
partial class MauiApplication : Application {
partial class ActivityLifecycleCallbacks : Java.Lang.Object, Application.IActivityLifecycleCallbacks {
public void OnActivityCreated (Activity activity, Bundle? savedInstanceState) => …
}
}
then `Java.Interop.Tools.JavaCallableWrappers` will produce:
// Java Callable Wrapper
/* partial */ class MauiApplication_ActivityLifecycleCallbacks
{
public static final String __md_methods;
static {
__md_methods =
// …
"n_onActivityCreated:(Landroid/app/Activity;Landroid/os/Bundle;)V:GetOnActivityCreated_Landroid_app_Activity_Landroid_os_Bundle_Handler:Android.App.Application/IActivityLifecycleCallbacksInvoker, Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null\n" +
// …
"";
mono.android.Runtime.register ("Microsoft.Maui.MauiApplication+ActivityLifecycleCallbacks, Microsoft.Maui", MauiApplication_ActivityLifecycleCallbacks.class, __md_methods);
}
}
The `signature` and `connector` values from the `[Register(…)]` on
the method declaration are copied as-is into `__md_methods`.
As the `connector` value contains a `/`, `__md_methods` does as well.
This has worked fine for nearly 15+ years…on Mono/MonoVM.
This *fails* on NativeAOT and CoreCLR, as:
Type.GetType ("Android.App.Application/IActivityLifecycleCallbacksInvoker, Mono.Android, …", throwOnError:true)
fails with:
TypeLoadException: Could not resolve type 'Android.App.Application/IActivityLifecycleCallbacksInvoker' in assembly 'Mono.Android, …'.
The reason for the failure is that when using Reflection APIs such as
`Type.GetType()`, the [`Type.AssemblyQualifiedName` grammar][0] must
be followed, and that grammar uses `+` to "Precede a nested class",
*not* `/`. (`/` isn't even in the Reflection grammar!)
(Aside: where does `/` come from then? It's the *IL* separator for
nested types!)
For eventual CoreCLR and NativeAOT support, then, we need to replace
`/` with `+` *somewhere* before it hits `Type.GetType()`.
There are (at least?) three places to do so:
1. Within `JniRuntime.JniTypeManager.RegisterNativeMembers()` or
equivalent override.
2. Within `generator`, updating the contents of `[Register]`.
3. Within `Java.Interop.Tools.JavaCallableWrappers`.
(1) is rejected out of hand as it would be additional work done on-
device at runtime. Why do that if we don't need to?
(2) was attempted in #1302 & dotnet/android#9750.
It turned into a bit of a boondoggle, because there are lots of
linker steps which interpret the `connector` value on `[Register]`,
and it "just worked" that the `connector` value contained IL names,
as the linker steps deal in IL! Trying to update `connector` to
instead contain Reflection syntax required finding all the places in
the linker that used `connector` values, which was tedious & brittle.
"Just" (2) is also inadequate, as it would require new binding
assemblies to take advantage of, so (2) *also* needed (3).
Which brings us to (3), the current approach: *don't* alter the
semantics of the `connect` value within `[Register]`, and instead
require that `Java.Interop.Tools.JavaCallableWrappers` replace all
instances of `/` with `+` within `__md_methods`. This is needed
*anyway*, for compatibility with existing bindings, and also avoids
lots of pain that (2) encountered.
With this approach, `generator` output is unchanged, and
`jcw-gen` output for `MauiApplication.ActivityLifecycleCallbacks`
becomes:
// Java Callable Wrapper
/* partial */ class MauiApplication_ActivityLifecycleCallbacks
{
public static final String __md_methods;
static {
__md_methods =
// …
"n_onActivityCreated:(Landroid/app/Activity;Landroid/os/Bundle;)V:GetOnActivityCreated_Landroid_app_Activity_Landroid_os_Bundle_Handler:Android.App.Application+IActivityLifecycleCallbacksInvoker, Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null\n" +
// …
"";
mono.android.Runtime.register ("Microsoft.Maui.MauiApplication+ActivityLifecycleCallbacks, Microsoft.Maui", MauiApplication_ActivityLifecycleCallbacks.class, __md_methods);
}
}
[0]: https://learn.microsoft.com/en-us/dotnet/api/system.type.assemblyqualifiedname?view=net-9.0#remarks
jonpryor added a commit that referenced this pull request Feb 7, 2025
Context: #9747
Changes: dotnet/java-interop@dd3c1d0...6bc87e8
* dotnet/java-interop@6bc87e8b: [jcw-gen] Use `+` for nested types, not `/` (dotnet/java-interop#1304)
dotnet/java-interop@6bc87e8b is needed to unblock parts of #9747.
Additionally, two "quality of life" changes:
Firstly, update the `src/Mono.Android` build so that if there are
API breaks reported, the breakage is collated into `src/Mono.Android/ApiCompatLinesToAdd.txt` in a format that can be
directly copied into `tests/api-compatibility/acceptable-breakages-vReference-*.txt` if
deemed useful.
Previously, *all* changes were printed to the build log, which was
annoying to deal with because (1) there may be duplicates, and
(2) the lines would contain `TaskId`/etc. "noise" that would need to
be removed in order to be used.
Secondly, update `build-tools/xaprepare` to improve reliability when
running `Step_InstallDotNetPreview`. `Step_InstallDotNetPreview`
will cache e.g. `dotnet-install.sh` into `$HOME/android-archives`,
but there was no logic to verify that it was still *valid*.
We've been seeing some recurring build failures in **macOS > Build**
such as:
Downloading dotnet-install script...
Warning: Using cached installation script found in '/Users/builder/android-archives/dotnet-install.sh'
Discovering download URLs for dotnet SDK '10.0.100-preview.2.25102.3'...
Downloading dotnet archive...
dotnet archive URL https://dotnetcli.azureedge.net/dotnet/Sdk/10.0.100-preview.2.25102.3/dotnet-sdk-10.0.100-preview.2.25102.3-osx-arm64.tar.gz not found
Downloading dotnet archive...
dotnet archive URL https://dotnetcli.azureedge.net/dotnet/Sdk/10.0.100-preview.2.25102.3/dotnet-dev-osx-arm64.10.0.100-preview.2.25102.3.tar.gz not found
Downloading dotnet archive...
Warning: Failed to obtain dotnet archive size. HTTP status code: InternalServerError (500)
Downloading dotnet archive...
Warning: Failed to obtain dotnet archive size. HTTP status code: InternalServerError (500)
Error: Installation of dotnet SDK '10.0.100-preview.2.25102.3' failed.
Step Xamarin.Android.Prepare.Step_InstallDotNetPreview failed
System.InvalidOperationException: Step Xamarin.Android.Prepare.Step_InstallDotNetPreview failed
at Xamarin.Android.Prepare.Scenario.Run(Context context, Log log) in /Users/builder/azdo/_work/8/s/xamarin-android/build-tools/xaprepare/xaprepare/Application/Scenario.cs:line 50
at Xamarin.Android.Prepare.Context.Execute() in /Users/builder/azdo/_work/8/s/xamarin-android/build-tools/xaprepare/xaprepare/Application/Context.cs:line 488
at Xamarin.Android.Prepare.App.Run(String[] args) in /Users/builder/azdo/_work/8/s/xamarin-android/build-tools/xaprepare/xaprepare/Main.cs:line 155
Indeed, [`dotnet-sdk-10.0.100-preview.2.25102.3-osx-arm64.tar.gz`][0]
no longer exists on <https://dotnetcli.azureedge.net>.
The problem, though, is that .NET changed the CDN that is used in the
past month, and that's not the correct URL. A newer `dotnet-install.sh`
reports:
% bash "…/dotnet-install.sh" "--version" "10.0.100-preview.2.25102.3" "--install-dir" "/Volumes/Xamarin-Work/src/dotnet/android/bin/Release/dotnet" "--verbose" "--dry-run
…
dotnet-install: Link 0: primary, 10.0.100-preview.2.25102.3, https://builds.dotnet.microsoft.com/dotnet/Sdk/10.0.100-preview.2.25102.3/dotnet-sdk-10.0.100-preview.2.25102.3-osx-x64.tar.gz
dotnet-install: Link 1: legacy, 10.0.100-preview.2.25102.3, https://builds.dotnet.microsoft.com/dotnet/Sdk/10.0.100-preview.2.25102.3/dotnet-dev-osx-x64.10.0.100-preview.2.25102.3.tar.gz
dotnet-install: Link 2: primary, 10.0.100-preview.2.25102.3, https://ci.dot.net/public/Sdk/10.0.100-preview.2.25102.3/dotnet-sdk-10.0.100-preview.2.25102.3-osx-x64.tar.gz
dotnet-install: Link 3: legacy, 10.0.100-preview.2.25102.3, https://ci.dot.net/public/Sdk/10.0.100-preview.2.25102.3/dotnet-dev-osx-x64.10.0.100-preview.2.25102.3.tar.gz
Note the different domain, <https://builds.dotnet.microsoft.com>!
Update `Step_InstallDotNetPreview` to try to install .NET potentially
*twice*: the first time using the cached `dotnet-install.sh`, and
*if that fails*, it tries again after downloading a *new*
`dotnet-install.sh`.
Hopefully this will fix the build failure on this machine!
[0]: https://dotnetcli.azureedge.net/dotnet/Sdk/10.0.100-preview.2.25102.3/dotnet-sdk-10.0.100-preview.2.25102.3-osx-arm64.tar.gz
@jonathanpeppers
jonathanpeppersforce-pushed the dev/peppers/maui/nativeaot branch from 1f83a81 to bd0d43cCompareFebruary 7, 2025 16:39
jonpryor pushed a commit that referenced this pull request Feb 11, 2025
…9779)
Context: #9747
Calling `Android.App.Application.Context` currently reads the
`mono.MonoPackageManager.Context` field, which is set on startup by a
`ContentProvider`. `mono.MonoPackageManager` does not exist in
NativeAOT (#9747), so let's move the field to the
`ApplicationRegistration` type, which *does* exist.
@jonathanpeppersjonathanpeppers mentioned this pull request Feb 12, 2025
20 tasks
@jonathanpeppers
jonathanpeppersforce-pushed the dev/peppers/maui/nativeaot branch from d6ee645 to e42a080CompareFebruary 12, 2025 16:28
jonpryor added a commit to dotnet/java-interop that referenced this pull request Feb 14, 2025
Context: dotnet/android#9747
Context: https://discord.com/channels/732297728826277939/732297837953679412/1339638847864176640
Context: https://discord.com/channels/732297728826277939/732297837953679412/1340011105510101063
While trying to get a MAUI sample running atop NativeAOT, we're
observing the following crash:
E AndroidRuntime: net.dot.jni.internal.JavaProxyThrowable: System.InvalidCastException: Arg_InvalidCastException
E AndroidRuntime: at Java.Lang.Object._GetObject[T](IntPtr, JniHandleOwnership) + 0x64
E AndroidRuntime: at Microsoft.Maui.WindowOverlay.Initialize() + 0x168
Further investigation shows that the crash is from accessing the
`Activity.WindowManager` property:
namespace Android.App;
partial class Activity {
public virtual unsafe Android.Views.IWindowManager? WindowManager {
// Metadata.xml XPath method reference: path="/api/package[@name='android.app']/class[@name='Activity']/method[@name='getWindowManager' and count(parameter)=0]"
[Register ("getWindowManager", "()Landroid/view/WindowManager;", "GetGetWindowManagerHandler")]
get {
const string __id = "getWindowManager.()Landroid/view/WindowManager;";
try {
var __rm = _members.InstanceMethods.InvokeVirtualObjectMethod (__id, this, null);
return global::Java.Lang.Object.GetObject<Android.Views.IWindowManager> (__rm.Handle, JniHandleOwnership.TransferLocalRef);
} finally {
}
}
}
}
`Object.GetObject<T>()` is now a wrapper around
`JniRuntime.JniValueManager.GetPeer()`, so the problem, rephrased,
is that this:
var peer = JniEnvironment.Runtime.ValueManager.GetPeer (
ref h,
JniObjectReferenceOptions.CopyAndDispose,
targetType:typeof (IWindowManager));
returns a value which *does not implement* `IWindowManager`.
It was, in fact, returning a `Java.Lang.Object` instance (!).
The cause of the bug is that `JniRuntime.JniValueManager.CreatePeer()`
was not checking that the `Type` returned form
`Runtime.TypeManager.GetType(JniTypeSignature)` was compatible with
`targetType`; instead, it returned the first type in the inheritance
chain that had an activation constructor. This was `Java.Lang.Object`.
Later, when `_GetObject<T>()` tried to cast the return value of
`JniRuntime.JniValueManager.GetPeer()` to `IWindowManager`, it failed.
Fix this by updating `CreatePeer()` to check that the `Type` from
`JniRuntime.JniTypeManager.GetType(JniTypeSignature)` is assignable
to `targetType`. This ensures that we *don't* return a
`Java.Lang.Object` instance, allowing the cast to succeed.
Update `JniRuntimeJniValueManagerContract` to test these new semantics.
jonpryor added a commit to dotnet/java-interop that referenced this pull request Feb 15, 2025
Context: dotnet/android#9747
Context: https://discord.com/channels/732297728826277939/732297837953679412/1339638847864176640
Context: https://discord.com/channels/732297728826277939/732297837953679412/1340011105510101063
While trying to get a MAUI sample running atop NativeAOT, we're
observing the following crash:
E AndroidRuntime: net.dot.jni.internal.JavaProxyThrowable: System.InvalidCastException: Arg_InvalidCastException
E AndroidRuntime: at Java.Lang.Object._GetObject[T](IntPtr, JniHandleOwnership) + 0x64
E AndroidRuntime: at Microsoft.Maui.WindowOverlay.Initialize() + 0x168
Further investigation shows that the crash is from accessing the
`Activity.WindowManager` property:
namespace Android.App;
partial class Activity {
public virtual unsafe Android.Views.IWindowManager? WindowManager {
// Metadata.xml XPath method reference: path="/api/package[@name='android.app']/class[@name='Activity']/method[@name='getWindowManager' and count(parameter)=0]"
[Register ("getWindowManager", "()Landroid/view/WindowManager;", "GetGetWindowManagerHandler")]
get {
const string __id = "getWindowManager.()Landroid/view/WindowManager;";
try {
var __rm = _members.InstanceMethods.InvokeVirtualObjectMethod (__id, this, null);
return global::Java.Lang.Object.GetObject<Android.Views.IWindowManager> (__rm.Handle, JniHandleOwnership.TransferLocalRef);
} finally {
}
}
}
}
`Object.GetObject<T>()` is now a wrapper around
`JniRuntime.JniValueManager.GetPeer()`, so the problem, rephrased,
is that in this:
var peer = JniEnvironment.Runtime.ValueManager.GetPeer (
ref h,
JniObjectReferenceOptions.CopyAndDispose,
targetType:typeof (IWindowManager));
var wm = (IWindowManager) peer;
`peer` is a value which *does not implement* `IWindowManager`.
It was, in fact, returning a `Java.Lang.Object` instance (!).
Consequently, the cast to `IWindowManager` throws.
The cause of the bug is that `JniRuntime.JniValueManager.CreatePeer()`
was not checking that the `Type` returned form
`Runtime.TypeManager.GetType(JniTypeSignature)` was compatible with
`targetType`; instead, it returned the first type in the inheritance
chain that had an activation constructor. This was `Java.Lang.Object`.
Later, when `_GetObject<T>()` tried to cast the return value of
`JniRuntime.JniValueManager.GetPeer()` to `IWindowManager`, it failed.
Fix this by updating `CreatePeer()` to check that the `Type` from
`JniRuntime.JniTypeManager.GetType(JniTypeSignature)` is assignable
to `targetType`. This ensures that we *don't* return a
`Java.Lang.Object` instance, allowing the cast to succeed.
Update `JniRuntimeJniValueManagerContract` to test these new semantics.
jonpryor added a commit that referenced this pull request Feb 15, 2025
@jonathanpeppers
jonathanpeppersforce-pushed the dev/peppers/maui/nativeaot branch from e42a080 to 846479aCompareFebruary 18, 2025 14:44
jonpryor pushed a commit that referenced this pull request Feb 18, 2025
Context: 70bd636
Context: #9747
Context: dotnet/java-interop@2a7183a
Context: 7acf328
Commit 70bd636 introduced an "Minimum Viable Product" for a managed
typemap for NativeAOT.
Unfortunately, a `dotnet new maui` project template (#9747) still
crashed with:
AndroidRuntime: Process: net.dot.hellonativeaot, PID: 16075
AndroidRuntime: net.dot.jni.internal.JavaProxyThrowable: System.InvalidCastException: Arg_InvalidCastException
AndroidRuntime: at Java.Lang.Object._GetObject[T](IntPtr, JniHandleOwnership) + 0x64
AndroidRuntime: at Microsoft.Maui.WindowOverlay.Initialize() + 0x168
AndroidRuntime: at Microsoft.Maui.Handlers.WindowHandler.OnRootViewChanged(Object sender, EventArgs e) + 0x8c
AndroidRuntime: at Microsoft.Maui.Platform.NavigationRootManager.SetContentView(IView) + 0x17c
AndroidRuntime: at Microsoft.Maui.Platform.NavigationRootManager.Connect(IView, IMauiContext) + 0xf0
AndroidRuntime: at Microsoft.Maui.Handlers.WindowHandler.CreateRootViewFromContent(IWindowHandler, IWindow) + 0x4c
AndroidRuntime: at Microsoft.Maui.Handlers.WindowHandler.MapContent(IWindowHandler handler, IWindow window) + 0x20
AndroidRuntime: at Microsoft.Maui.PropertyMapper`2.<>c__DisplayClass5_0.<Add>b__0(IElementHandler h, IElement v) + 0x130
AndroidRuntime: at Microsoft.Maui.PropertyMapper.UpdatePropertyCore(String, IElementHandler, IElement) + 0x58
AndroidRuntime: at Microsoft.Maui.PropertyMapper.UpdateProperties(IElementHandler, IElement) + 0x74
AndroidRuntime: at Microsoft.Maui.Handlers.ElementHandler.SetVirtualView(IElement) + 0x15c
AndroidRuntime: at Microsoft.Maui.Controls.Element.SetHandler(IElementHandler) + 0x124
AndroidRuntime: at Microsoft.Maui.Controls.Element.set_Handler(IElementHandler value) + 0xc
AndroidRuntime: at Microsoft.Maui.Platform.ElementExtensions.SetHandler(Context, IElement, IMauiContext) + 0xfc
AndroidRuntime: at Microsoft.Maui.Platform.ApplicationExtensions.CreatePlatformWindow(Activity, IApplication, Bundle) + 0x184
AndroidRuntime: at Microsoft.Maui.MauiAppCompatActivity.OnCreate(Bundle) + 0x74
AndroidRuntime: at Android.App.Activity.n_OnCreate_Landroid_os_Bundle_(IntPtr jnienv, IntPtr native__this, IntPtr native_savedInstanceState) + 0xc0
AndroidRuntime: at my.MainActivity.n_onCreate(Native Method)
AndroidRuntime: at my.MainActivity.onCreate(MainActivity.java:36)
AndroidRuntime: at android.app.Activity.performCreate(Activity.java:8960)
AndroidRuntime: at android.app.Activity.performCreate(Activity.java:8938)
AndroidRuntime: at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1526)
AndroidRuntime: at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3975)
AndroidRuntime: at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:4173)
AndroidRuntime: at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:114)
AndroidRuntime: at android.app.servertransaction.TransactionExecutor.executeNonLifecycleItem(TransactionExecutor.java:231)
AndroidRuntime: at android.app.servertransaction.TransactionExecutor.executeTransactionItems(TransactionExecutor.java:152)
AndroidRuntime: at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:93)
AndroidRuntime: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2595)
AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:107)
AndroidRuntime: at android.os.Looper.loopOnce(Looper.java:232)
AndroidRuntime: at android.os.Looper.loop(Looper.java:317)
AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:8592)
AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)
AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:580)
AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:878)
The actual cause of the above `InvalidCastException` was fixed in
dotnet/java-interop@2a7183a1.
However, we also determined that the typemap associated
`java/lang/Object` with `Java.Interop.JavaObject`, *not*
to `Java.Lang.Object`, because 70bd636 processed `Java.Interop.dll`
before `Mono.Android.dll` and other assemblies.
(Related: commit 7acf328, which began treating `Java.Interop.dll` as
an assembly that could contain type mappings, and had to be special-
cased in order to ensure `java/lang/Object` was mapped to
`Java.Lang.Object, Mono.Android`.)
The typemap implementation was missing two edge cases:
1. Types in `Mono.Android.dll` should be preferred over types in
other assemblies.
2. Abstract or interface types should be preferred over their
equivalent `*Invoker` types.
There was also some missing logic regarding `*Invoker` types and
abstract types.
I fixed these cases in `TypeMappingStep.cs` and added a "test" that
can validates the typemap during a build.
jonpryor added a commit to dotnet/java-interop that referenced this pull request Feb 20, 2025
Context: dotnet/android#9747
Context: dotnet/android#9812
In the ongoing epic to get MAUI running atop NativeAOT, we hit our
longstanding NativeAOT nemesis: P/Invoke:
E AndroidRuntime: net.dot.jni.internal.JavaProxyThrowable: System.InvalidProgramException: InvalidProgram_Specific, IntPtr Android.Runtime.JNIEnv.monodroid_typemap_managed_to_java(System.Type, Byte*)
E AndroidRuntime: at Internal.Runtime.TypeLoaderExceptionHelper.CreateInvalidProgramException(ExceptionStringID, String) + 0x4c
E AndroidRuntime: at Android.Runtime.JNIEnv.monodroid_typemap_managed_to_java(Type, Byte*) + 0x18
E AndroidRuntime: at Android.Runtime.JNIEnv.TypemapManagedToJava(Type) + 0x104
E AndroidRuntime: at Android.Runtime.JNIEnv.GetJniName(Type) + 0x1c
E AndroidRuntime: at Android.Runtime.JNIEnv.FindClass(Type) + 0x38
E AndroidRuntime: at Android.Runtime.JNIEnv.NewArray(IJavaObject[]) + 0x28
E AndroidRuntime: at Android.Runtime.JNIEnv.NewArray[T](T[]) + 0x94
E AndroidRuntime: at Android.Graphics.Drawables.LayerDrawable..ctor(Drawable[] layers) + 0xd4
E AndroidRuntime: at Microsoft.Maui.Platform.MauiRippleDrawableExtensions.UpdateMauiRippleDrawableBackground(View, Paint, IButtonStroke, Func`1, Func`1, Action) + 0x2ac
(`JNIEnv.monodroid_typemap_managed_to_java()` is P/Invoke.)
The reasonable fix/workaround: update `JNIEnv.FindClass(Type)` to
instead use `JniRuntime.JniTypeManager.GetTypeSignature(Type)`.
(Also known as "embrace more JniRuntime abstractions!".)
Unfortunately, this straightforward idea hits a minor schism between
the .NET for Android and builtin java-interop world orders:
How should Java `byte` be bound?
For starters, what *is* a [Java `byte`][0]?
> The values of the integral types are integers in the following ranges:
>
> * For `byte`, from -128 to 127, inclusive
The Java `byte` is *signed*! Because of that, and because
java-interop originated as a Second System Syndrome rebuild of
Xamarin.Android, *of course* java-interop bound Java `byte` as
`System.SByte`.
.NET for Android, though, bound Java `byte` as `System.Byte`.
This "minor" change meant that lots of unit tests started failing, e.g.
[`NetworkInterfacesTest.DotNetInterfacesShouldEqualJavaInterfaces()`][2]:
System.ArgumentException : Could not determine Java type corresponding to System.Byte[], System.Private.CoreLib, Version=10.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e. Arg_ParamName_Name, type
at Android.Runtime.JNIEnv.FindClass(Type )
at Android.Runtime.JNIEnv.AssertCompatibleArrayTypes(IntPtr , Type )
at Android.Runtime.JNIEnv._GetArray(IntPtr , Type )
at Android.Runtime.JNIEnv.GetArray(IntPtr , JniHandleOwnership , Type )
at Java.Net.NetworkInterface.GetHardwareAddress()
at System.NetTests.NetworkInterfacesTest.GetInfos(IEnumeration interfaces)
at System.NetTests.NetworkInterfacesTest.DotNetInterfacesShouldEqualJavaInterfaces()
at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object , BindingFlags )
Rephrased, `runtime.TypeManager.GetTypeSignature(typeof(byte[]))`
returned a "default" `JniTypeSignature` instance.
It's time to reduce the size of this schism.
Update `JniBuiltinMarshalers.GetBuiltInTypeSignature()` so that
`TypeCode.Byte` is treated as a synonym for `TypeCode.SByte`.
This is in fact all that's needed in order to add support for `byte[]`!
It's *not* all that's necessary to fix all unit tests.
Update `JniRuntime.JniTypeManager.GetTypeSignature()` and
`.GetTypeSignatures()` so that if the type is an open generic type
a `System.NotSupportedException` is thrown instead of a
`System.ArgumentException`. This fixes
[`Java.InteropTests.JnienvTest.NewOpenGenericTypeThrows()`][3].
Also, `JniBuiltinMarshalers.cs` got some hand-made changes, rendering
it out of sync with `JniBuiltinMarshalers.tt`. Regenerate it.
[0]: https://docs.oracle.com/javase/specs/jls/se21/html/jls-4.html#jls-4.2.1
[1]: https://github.com/dotnet/java-interop/blob/f30e420a1638dc013302e85dcf76642c10c26832/Documentation/Motivation.md
[2]: https://github.com/dotnet/android/blob/1b1f1452f6b05707418d6605c06e106e6a2a6381/tests/Mono.Android-Tests/Mono.Android-Tests/System.Net/NetworkInterfaces.cs#L107-L137
[3]: https://github.com/dotnet/android/blob/1b1f1452f6b05707418d6605c06e106e6a2a6381/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/JnienvTest.cs#L107-L116
jonpryor added a commit to dotnet/java-interop that referenced this pull request Feb 21, 2025
Context: dotnet/android#9747
Context: dotnet/android#9812
Context: 71afce5
Context: dotnet/android@aa5e597
Context: dotnet/android@f800c1a
In the ongoing epic to get MAUI running atop NativeAOT, we hit our
longstanding NativeAOT nemesis: a P/Invoke:
E AndroidRuntime: net.dot.jni.internal.JavaProxyThrowable: System.InvalidProgramException: InvalidProgram_Specific, IntPtr Android.Runtime.JNIEnv.monodroid_typemap_managed_to_java(System.Type, Byte*)
E AndroidRuntime: at Internal.Runtime.TypeLoaderExceptionHelper.CreateInvalidProgramException(ExceptionStringID, String) + 0x4c
E AndroidRuntime: at Android.Runtime.JNIEnv.monodroid_typemap_managed_to_java(Type, Byte*) + 0x18
E AndroidRuntime: at Android.Runtime.JNIEnv.TypemapManagedToJava(Type) + 0x104
E AndroidRuntime: at Android.Runtime.JNIEnv.GetJniName(Type) + 0x1c
E AndroidRuntime: at Android.Runtime.JNIEnv.FindClass(Type) + 0x38
E AndroidRuntime: at Android.Runtime.JNIEnv.NewArray(IJavaObject[]) + 0x28
E AndroidRuntime: at Android.Runtime.JNIEnv.NewArray[T](T[]) + 0x94
E AndroidRuntime: at Android.Graphics.Drawables.LayerDrawable..ctor(Drawable[] layers) + 0xd4
E AndroidRuntime: at Microsoft.Maui.Platform.MauiRippleDrawableExtensions.UpdateMauiRippleDrawableBackground(View, Paint, IButtonStroke, Func`1, Func`1, Action) + 0x2ac
(`JNIEnv.monodroid_typemap_managed_to_java()` is P/Invoke. Why are
P/Invokes bad? See dotnet/android@f800c1a6.)
The reasonable fix/workaround: update `JNIEnv.FindClass(Type)` to
instead use `JniRuntime.JniTypeManager.GetTypeSignature(Type)`.
(Also known as "embrace more JniRuntime abstractions!".)
Unfortunately, this straightforward idea hits a minor schism between
the .NET for Android and builtin java-interop world orders:
How should Java `byte` be bound?
For starters, what *is* a [Java `byte`][0]?
> The values of the integral types are integers in the following ranges:
>
> * For `byte`, from -128 to 127, inclusive
The Java `byte` is *signed*! Because of that, and because
java-interop originated as a Second System Syndrome rebuild of
Xamarin.Android, *of course* java-interop bound Java `byte` as
`System.SByte`.
.NET for Android, though, bound Java `byte` as `System.Byte`.
This "minor" change meant that lots of unit tests started failing, e.g.
[`NetworkInterfacesTest.DotNetInterfacesShouldEqualJavaInterfaces()`][2]:
System.ArgumentException : Could not determine Java type corresponding to System.Byte[], System.Private.CoreLib, Version=10.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e. Arg_ParamName_Name, type
at Android.Runtime.JNIEnv.FindClass(Type )
at Android.Runtime.JNIEnv.AssertCompatibleArrayTypes(IntPtr , Type )
at Android.Runtime.JNIEnv._GetArray(IntPtr , Type )
at Android.Runtime.JNIEnv.GetArray(IntPtr , JniHandleOwnership , Type )
at Java.Net.NetworkInterface.GetHardwareAddress()
at System.NetTests.NetworkInterfacesTest.GetInfos(IEnumeration interfaces)
at System.NetTests.NetworkInterfacesTest.DotNetInterfacesShouldEqualJavaInterfaces()
at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object , BindingFlags )
Rephrased, `runtime.TypeManager.GetTypeSignature(typeof(byte[]))`
returned a "default" `JniTypeSignature` instance.
It's time to reduce the size of this schism.
Update `JniBuiltinMarshalers.GetBuiltInTypeSignature()` so that
`TypeCode.Byte` is treated as a synonym for `TypeCode.SByte`.
This is in fact all that's needed in order to add support for `byte[]`!
Repeat this exercise for all other unsigned types: `ushort`, `uint`,
and `ulong`, as Kotlin unsigned types require it; see also 71afce5
and dotnet/android@aa5e597eba. This fixes the exception:
System.InvalidCastException : Unable to cast from '[I' to '[Ljava/lang/Object;'.
at Android.Runtime.JNIEnv.AssertCompatibleArrayTypes(IntPtr , Type )
at Android.Runtime.JNIEnv._GetArray(IntPtr , Type )
at Android.Runtime.JNIEnv.GetArray(IntPtr , JniHandleOwnership , Type )
at Foo.UnsignedInstanceMethods.UintArrayInstanceMethod(UInt32[] value)
at Xamarin.Android.JcwGenTests.KotlinUnsignedTypesTests.TestUnsignedArrayTypeMembers()
at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object , BindingFlags )
This is *not* all that's necessary to fix all dotnet/android tests.
Update `JniRuntime.JniTypeManager.GetTypeSignature()` and
`.GetTypeSignatures()` so that if the type is an open generic type
a `System.NotSupportedException` is thrown instead of a
`System.ArgumentException`. This fixes
[`Java.InteropTests.JnienvTest.NewOpenGenericTypeThrows()`][3].
Also, `JniBuiltinMarshalers.cs` has some hand-made changes, rendering
it out of sync with `JniBuiltinMarshalers.tt`. Update
`JniBuiltinMarshalers.tt` appropriately and regenerate it.
[0]: https://docs.oracle.com/javase/specs/jls/se21/html/jls-4.html#jls-4.2.1
[1]: https://github.com/dotnet/java-interop/blob/f30e420a1638dc013302e85dcf76642c10c26832/Documentation/Motivation.md
[2]: https://github.com/dotnet/android/blob/1b1f1452f6b05707418d6605c06e106e6a2a6381/tests/Mono.Android-Tests/Mono.Android-Tests/System.Net/NetworkInterfaces.cs#L107-L137
[3]: https://github.com/dotnet/android/blob/1b1f1452f6b05707418d6605c06e106e6a2a6381/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/JnienvTest.cs#L107-L116
@jonathanpeppers
jonathanpeppersforce-pushed the dev/peppers/maui/nativeaot branch from 69e3c76 to 86d52fcCompareFebruary 21, 2025 16:47
jonpryor pushed a commit that referenced this pull request Feb 21, 2025
Context: #9747
A .NET MAUI + NativeAOT project fails with:
E AndroidRuntime: net.dot.jni.internal.JavaProxyThrowable: System.DllNotFoundException: DllNotFound_Linux, xa-internal-api,
E AndroidRuntime: dlopen failed: library "xa-internal-api.so" not found
E AndroidRuntime: dlopen failed: library "libxa-internal-api.so" not found
E AndroidRuntime: dlopen failed: library "xa-internal-api" not found
E AndroidRuntime: dlopen failed: library "libxa-internal-api" not found
E AndroidRuntime:
E AndroidRuntime: at System.Runtime.InteropServices.NativeLibrary.LoadLibErrorTracker.Throw(String) + 0x4c
E AndroidRuntime: at Internal.Runtime.CompilerHelpers.InteropHelpers.FixupModuleCell(InteropHelpers.ModuleFixupCell*) + 0xe4
E AndroidRuntime: at Internal.Runtime.CompilerHelpers.InteropHelpers.ResolvePInvokeSlow(InteropHelpers.MethodFixupCell*) + 0x40
E AndroidRuntime: at Android.Runtime.RuntimeNativeMethods.monodroid_TypeManager_get_java_class_name(IntPtr) + 0x2c
E AndroidRuntime: at Java.Interop.TypeManager.GetClassName(IntPtr) + 0x10
E AndroidRuntime: at Android.Runtime.JNIEnv.GetClassNameFromInstance(IntPtr) + 0x1c
E AndroidRuntime: at Android.Runtime.JNIEnv.GetConverter[TValue](Dictionary`2, Type, IntPtr) + 0x6c
E AndroidRuntime: at Android.Runtime.JNIEnv.CopyArray[T](IntPtr, T[]) + 0x90
E AndroidRuntime: at Android.Graphics.Drawables.LayerDrawable..ctor(Drawable[] layers) + 0x1a4
E AndroidRuntime: at Microsoft.Maui.Platform.MauiRippleDrawableExtensions.UpdateMauiRippleDrawableBackground(View, Paint, IButtonStroke, Func`1, Func`1, Action) + 0x2ac
E AndroidRuntime: at Microsoft.Maui.PropertyMapper.UpdateProperties(IElementHandler, IElement) + 0x64
E AndroidRuntime: at Microsoft.Maui.Controls.Element.SetHandler(IElementHandler) + 0x124
E AndroidRuntime: at Microsoft.Maui.Platform.ElementExtensions.ToHandler(IElement, IMauiContext) + 0x160
E AndroidRuntime: at Microsoft.Maui.Platform.ElementExtensions.ToPlatform(IElement, IMauiContext) + 0x28
E AndroidRuntime: at Microsoft.Maui.Handlers.LayoutHandler.SetVirtualView(IView) + 0xd4
E AndroidRuntime: at Microsoft.Maui.Controls.Element.SetHandler(IElementHandler) + 0x124
E AndroidRuntime: at Microsoft.Maui.Platform.ElementExtensions.ToHandler(IElement, IMauiContext) + 0x160
E AndroidRuntime: at Microsoft.Maui.Platform.ElementExtensions.ToPlatform(IElement, IMauiContext) + 0x28
E AndroidRuntime: at Microsoft.Maui.Handlers.ScrollViewHandler.UpdateInsetView(IScrollView, IScrollViewHandler, ICrossPlatformLayout) + 0x34
E AndroidRuntime: at Microsoft.Maui.PropertyMapper.UpdateProperties(IElementHandler, IElement) + 0x64
E AndroidRuntime: at Microsoft.Maui.Controls.Element.SetHandler(IElementHandler) + 0x124
E AndroidRuntime: at Microsoft.Maui.Platform.ElementExtensions.ToHandler(IElement, IMauiContext) + 0x160
E AndroidRuntime: at Microsoft.Maui.Platform.ElementExtensions.ToPlatform(IElement, IMauiContext) + 0x28
E AndroidRuntime: at Microsoft.Maui.Handlers.ContentViewHandler.UpdateContent(IContentViewHandler) + 0xb4
E AndroidRuntime: at Microsoft.Maui.PropertyMapper.UpdateProperties(IElementHandler, IElement) + 0x64
E AndroidRuntime: at Microsoft.Maui.Handlers.ContentViewHandler.SetVirtualView(IView) + 0x18
E AndroidRuntime: at Microsoft.Maui.Controls.Element.SetHandler(IElementHandler) + 0x124
E AndroidRuntime: at Microsoft.Maui.Platform.ElementExtensions.ToHandler(IElement, IMauiContext) + 0x160
E AndroidRuntime: at Microsoft.Maui.Platform.ElementExtensions.ToPlatform(IElement, IMauiContext) + 0x28
E AndroidRuntime: at Microsoft.Maui.Controls.Platform.Compatibility.ShellFragmentContainer.OnCreateView(LayoutInflater, ViewGroup, Bundle) + 0x64
E AndroidRuntime: at AndroidX.Fragment.App.Fragment.n_OnCreateView_Landroid_view_LayoutInflater_Landroid_view_ViewGroup_Landroid_os_Bundle_(IntPtr jnienv, IntPtr native__this, IntPtr native_inflater, IntPtr native_container, IntPtr native_savedInstanceState) + 0x104
E AndroidRuntime: at crc640ec207abc449b2ca.ShellFragmentContainer.n_onCreateView(Native Method)
E AndroidRuntime: at crc640ec207abc449b2ca.ShellFragmentContainer.onCreateView(ShellFragmentContainer.java:38)
E AndroidRuntime: at androidx.fragment.app.Fragment.performCreateView(Fragment.java:3119)
E AndroidRuntime: at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:577)
E AndroidRuntime: at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:286)
E AndroidRuntime: at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:2214)
E AndroidRuntime: at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:2109)
E AndroidRuntime: at androidx.fragment.app.FragmentManager.execSingleAction(FragmentManager.java:2002)
E AndroidRuntime: at androidx.fragment.app.BackStackRecord.commitNow(BackStackRecord.java:317)
E AndroidRuntime: at androidx.viewpager2.adapter.FragmentStateAdapter.placeFragmentInViewHolder(FragmentStateAdapter.java:342)
E AndroidRuntime: at androidx.viewpager2.adapter.FragmentStateAdapter.onViewAttachedToWindow(FragmentStateAdapter.java:273)
E AndroidRuntime: at androidx.viewpager2.adapter.FragmentStateAdapter.onViewAttachedToWindow(FragmentStateAdapter.java:73)
E AndroidRuntime: at androidx.recyclerview.widget.RecyclerView.dispatchChildAttached(RecyclerView.java:8377)
E AndroidRuntime: at androidx.recyclerview.widget.RecyclerView$5.addView(RecyclerView.java:954)
E AndroidRuntime: at androidx.recyclerview.widget.ChildHelper.addView(ChildHelper.java:131)
E AndroidRuntime: at androidx.recyclerview.widget.RecyclerView$LayoutManager.addViewInt(RecyclerView.java:9430)
E AndroidRuntime: at androidx.recyclerview.widget.RecyclerView$LayoutManager.addView(RecyclerView.java:9388)
E AndroidRuntime: at androidx.recyclerview.widget.RecyclerView$LayoutManager.addView(RecyclerView.java:9375)
E AndroidRuntime: at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1676)
E AndroidRuntime: at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1622)
E AndroidRuntime: at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:687)
E AndroidRuntime: at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:4645)
E AndroidRuntime: at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:4348)
E AndroidRuntime: at androidx.recyclerview.widget.RecyclerView.onLayout(RecyclerView.java:4919)
E AndroidRuntime: at android.view.View.layout(View.java:25159)
E AndroidRuntime: at android.view.ViewGroup.layout(ViewGroup.java:6460)
E AndroidRuntime: at androidx.viewpager2.widget.ViewPager2.onLayout(ViewPager2.java:535)
E AndroidRuntime: at android.view.View.layout(View.java:25159)
E AndroidRuntime: at android.view.ViewGroup.layout(ViewGroup.java:6460)
E AndroidRuntime: at com.google.android.material.appbar.HeaderScrollingViewBehavior.layoutChild(HeaderScrollingViewBehavior.java:149)
E AndroidRuntime: at com.google.android.material.appbar.ViewOffsetBehavior.onLayoutChild(ViewOffsetBehavior.java:43)
E AndroidRuntime: at com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior.onLayoutChild(AppBarLayout.java:2365)
E AndroidRuntime: at androidx.coordinatorlayout.widget.CoordinatorLayout.onLayout(CoordinatorLayout.java:953)
E AndroidRuntime: at android.view.View.layout(View.java:25159)
E AndroidRuntime: at android.view.ViewGroup.layout(ViewGroup.java:6460)
E AndroidRuntime: at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
E AndroidRuntime: at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
E AndroidRuntime: at android.view.View.layout(View.java:25159)
E AndroidRuntime: at android.view.ViewGroup.layout(ViewGroup.java:6460)
E AndroidRuntime: at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1891)
E AndroidRuntime: at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1729)
E AndroidRuntime: at android.widget.LinearLayout.onLayout(LinearLayout.java:1638)
E AndroidRuntime: at android.view.View.layout(View.java:25159)
E AndroidRuntime: at android.view.ViewGroup.layout(ViewGroup.java:6460)
E AndroidRuntime: at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
E AndroidRuntime: at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
E AndroidRuntime: at android.view.View.layout(View.java:25159)
E AndroidRuntime: at android.view.ViewGroup.layout(ViewGroup.java:6460)
E AndroidRuntime: at androidx.drawerlayout.widget.DrawerLayout.onLayout(DrawerLayout.java:1273)
E AndroidRuntime: at android.view.View.layout(View.java:25159)
E AndroidRuntime: at android.view.ViewGroup.layout(ViewGroup.java:6460)
E AndroidRuntime: at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1891)
E AndroidRuntime: at android.widget.LinearLayout.layoutHorizontal(LinearLayout.java:1880)
E AndroidRuntime: at android.widget.LinearLayout.onLayout(LinearLayout.java:1640)
E AndroidRuntime: at android.view.View.layout(View.java:25159)
E AndroidRuntime: at android.view.ViewGroup.layout(ViewGroup.java:6460)
E AndroidRuntime: at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
E AndroidRuntime: at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
E AndroidRuntime: at android.view.View.layout(View.java:25159)
E AndroidRuntime: at android.view.ViewGroup.layout(ViewGroup.java:6460)
E AndroidRuntime: at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
E AndroidRuntime: at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
E AndroidRuntime: at android.view.View.layout(View.java:25159)
E AndroidRuntime: at android.view.ViewGroup.layout(ViewGroup.java:6460)
E AndroidRuntime: at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
E AndroidRuntime: at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
E AndroidRuntime: at android.view.View.layout(View.java:25159)
E AndroidRuntime: at android.view.ViewGroup.layout(ViewGroup.java:6460)
E AndroidRuntime: at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1891)
E AndroidRuntime: at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1729)
E AndroidRuntime: at android.widget.LinearLayout.onLayout(LinearLayout.java:1638)
E AndroidRuntime: at android.view.View.layout(View.java:25159)
E AndroidRuntime: at android.view.ViewGroup.layout(ViewGroup.java:6460)
E AndroidRuntime: at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
E AndroidRuntime: at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
E AndroidRuntime: at com.android.internal.policy.DecorView.onLayout(DecorView.java:807)
E AndroidRuntime: at android.view.View.layout(View.java:25159)
E AndroidRuntime: at android.view.ViewGroup.layout(ViewGroup.java:6460)
E AndroidRuntime: at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:4562)
E AndroidRuntime: at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3830)
E AndroidRuntime: at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2718)
E AndroidRuntime: at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:9937)
E AndroidRuntime: at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1406)
E AndroidRuntime: at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1415)
E AndroidRuntime: at android.view.Choreographer.doCallbacks(Choreographer.java:1015)
E AndroidRuntime: at android.view.Choreographer.doFrame(Choreographer.java:945)
E AndroidRuntime: at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1389)
E AndroidRuntime: at android.os.Handler.handleCallback(Handler.java:959)
E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:100)
E AndroidRuntime: at android.os.Looper.loopOnce(Looper.java:232)
E AndroidRuntime: at android.os.Looper.loop(Looper.java:317)
E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:8592)
E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)
E AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:580)
E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:878)
Update `Android.Runtime.JNIEnv.GetClassNameFromInstance()` to call
`JniEnvironment.Types.GetJniTypeNameFromInstance()` instead of
`Java.Interop.TypeManager.GetClassName()`. This avoids the P/Invoke,
fixing the `DllNotFoundException`.
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jan 15, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

do-not-mergePR should not be merged.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@jonathanpeppers@jonpryor