Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 54 additions & 9 deletions src/Java.Interop/Java.Interop/JniEnvironment.Types.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Collections.Generic;
using System.Runtime.ExceptionServices;
using System.Runtime.InteropServices;
Expand Down Expand Up @@ -256,7 +257,7 @@ public static void RegisterNatives (JniObjectReference type, JniNativeMethodRegi
RegisterNatives (type, methods, methods == null ? 0 : methods.Length);
}

public static void RegisterNatives (JniObjectReference type, JniNativeMethodRegistration [] methods, int numMethods)
public static unsafe void RegisterNatives (JniObjectReference type, JniNativeMethodRegistration [] methods, int numMethods)
{
if ((numMethods < 0) ||
(numMethods > (methods?.Length ?? 0))) {
Expand All @@ -275,22 +276,57 @@ public static void RegisterNatives (JniObjectReference type, JniNativeMethodRegi
}
#endif // DEBUG

int r = _RegisterNatives (type, methods ?? Array.Empty<JniNativeMethodRegistration>(), numMethods);
if (numMethods == 0 || methods == null) {
return;
}

if (r != 0) {
throw new InvalidOperationException (
string.Format ("Could not register native methods for class '{0}'; JNIEnv::RegisterNatives() returned {1}.", GetJniTypeNameFromClass (type), r));
// Marshal the non-blittable JniNativeMethodRegistration[] into blittable JniNativeMethod
// values and dispatch to the blittable overload, instead of invoking the JNI
// `RegisterNatives` function pointer with a non-blittable managed-array parameter.
// The runtime marshalling stub synthesized for such a `delegate* unmanaged<>` call is
// miscompiled by crossgen2 under composite ReadyToRun + PGO: the JniNativeMethod `name`
// pointers end up referencing the managed `string` objects instead of marshalled UTF-8
// data, which corrupts the registered method names. See https://github.com/dotnet/android/issues/11633.
var natives = new JniNativeMethod [numMethods];
var unmanagedStrings = new IntPtr [numMethods * 2];
try {
for (int i = 0; i < numMethods; ++i) {
var m = methods [i];
IntPtr name = Marshal.StringToCoTaskMemUTF8 (m.Name);
IntPtr sig = Marshal.StringToCoTaskMemUTF8 (m.Signature);
unmanagedStrings [i * 2] = name;
unmanagedStrings [i * 2 + 1] = sig;
natives [i] = new JniNativeMethod ((byte*) name, (byte*) sig, GetFunctionPointerForDelegate (m.Marshaler));
}
RegisterNatives (type, new ReadOnlySpan<JniNativeMethod> (natives, 0, numMethods));
// Keep the Marshaler delegates alive at least until JNI has consumed the function pointers.
GC.KeepAlive (methods);
} finally {
Comment thread
simonrozsival marked this conversation as resolved.
for (int i = 0; i < unmanagedStrings.Length; ++i) {
Marshal.ZeroFreeCoTaskMemUTF8 (unmanagedStrings [i]);
}
}
}

// Native method registration via JniNativeMethodRegistration[] only runs on JIT-capable
// runtimes (MonoVM/CoreCLR). Under NativeAOT, native methods are registered through the
// trimmable type map using statically-compiled function pointers, so this path is never reached.
[UnconditionalSuppressMessage ("AOT", "IL3050", Justification = "Not reached under NativeAOT; only JIT-capable runtimes register via JniNativeMethodRegistration[].")]
static IntPtr GetFunctionPointerForDelegate (Delegate marshaler) =>
Marshal.GetFunctionPointerForDelegate (marshaler);

/// <summary>
/// Registers JNI native methods using blittable <see cref="JniNativeMethod"/> structs
/// with raw function pointers and UTF-8 name/signature pointers.
/// Calls the JNI RegisterNatives function directly without delegate marshaling.
/// </summary>
public static unsafe void RegisterNatives (JniObjectReference type, ReadOnlySpan<JniNativeMethod> methods)
{
if (!type.IsValid)
throw new ArgumentException ("Handle must be valid.", nameof (type));

IntPtr env = JniEnvironment.EnvironmentPointer;
int r;
fixed (JniNativeMethod* methodsPtr = methods) {
#if FEATURE_JNIENVIRONMENT_JI_FUNCTION_POINTERS
var registerNatives = (delegate* unmanaged<IntPtr, IntPtr, JniNativeMethod*, int, int>)
Expand All @@ -299,10 +335,19 @@ public static unsafe void RegisterNatives (JniObjectReference type, ReadOnlySpan
var registerNatives = (delegate* unmanaged<IntPtr, IntPtr, JniNativeMethod*, int, int>)
JniEnvironment.CurrentInfo.Invoker.env.RegisterNatives;
#endif
int r = registerNatives (env, type.Handle, methodsPtr, methods.Length);
if (r != 0) {
throw new InvalidOperationException ($"Could not register native methods for class '{GetJniTypeNameFromClass (type)}'; JNIEnv::RegisterNatives() returned {r}.");
}
r = registerNatives (env, type.Handle, methodsPtr, methods.Length);
}

// Surface (and clear) any pending Java exception raised by JNI::RegisterNatives()
// — e.g. NoSuchMethodError — before falling back to the return-code check, matching
// the behavior of the generated `_RegisterNatives` wrapper. Leaving a pending
// exception in the JNIEnv would make subsequent JNI calls fail or abort.
var thrown = JniEnvironment.GetExceptionForLastThrowable ();
if (thrown != null)
ExceptionDispatchInfo.Capture (thrown).Throw ();

if (r != 0) {
throw new InvalidOperationException ($"Could not register native methods for class '{GetJniTypeNameFromClass (type)}'; JNIEnv::RegisterNatives() returned {r}.");
}
}

Expand Down