Skip to content

Commit

Permalink
Use [UnmanagedFunctionPointer]
Browse files Browse the repository at this point in the history
Commit db84cc3 noted that the sample failed to run because
NativeAOT didn't know how to produce the required "stubs" to allow
a `System.Delegate` field to be marshaled.

Commit a32e244 was an attempt to "fix" this by introducing a
`JniBlittableNativeMethodRegistration` and using C#9 function pointers
to register `ManagedPeer` marshal methods instead of delegates.
While this works, this is an extensive change, and leaves various
"corner cases" in how things fit together, such as the
`JniNativeMethodRegistrationArguments` struct used as part of
`jnimarshalmethod-gen`-based method registration.

@lambdageek found an easier solution: place
`[UnmanagedFunctionPointer]` onto the delegate declaration.  This
appears to tell NativeAOT to generate the required stubs, with
significantly fewer changes.  This also could fit nicely into a future
`generator` change to place `[UnmanagedFunctionPointer]` on all the
`_JniMarshal_*` declarations (56955d9).
  • Loading branch information
jonpryor committed Oct 25, 2023
1 parent a08dce8 commit da9f188
Show file tree
Hide file tree
Showing 8 changed files with 33 additions and 137 deletions.
2 changes: 1 addition & 1 deletion build-tools/jnienv-gen/Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -758,7 +758,7 @@ abstract class TypeInfo
{ "void*", new BuiltinTypeInfo ("void*", "IntPtr") },
{ "const jchar*", new StringTypeInfo ("const jchar*") },
{ "const char*", new StringTypeInfo ("const char*") },
{ "const JNINativeMethod*", new BuiltinTypeInfo ("const JNINativeMethod*", "IntPtr") },
{ "const JNINativeMethod*", new BuiltinTypeInfo ("const JNINativeMethod*", "JniNativeMethodRegistration []") },
{ "jobjectRefType", new BuiltinTypeInfo ("jobjectRefType", "JniObjectReferenceType") },
{ "jfieldID", new InstanceFieldTypeInfo ("jfieldID") },
{ "jstaticfieldID", new StaticFieldTypeInfo ("jstaticfieldID") },
Expand Down
1 change: 1 addition & 0 deletions samples/Hello-NativeAOTFromJNI/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ Time Elapsed 00:00:00.83
% (cd bin/Release/osx-x64/publish ; java -cp hello-from-java.jar:java-interop.jar com/microsoft/hello_from_jni/App)
Hello from Java!
Hello from .NET NativeAOT!
String returned to Java: Hello from .NET NativeAOT!
```

Note the use of `(cd …; java …)` so that `libHello-NativeAOTFromJNI.dylib` is
Expand Down
2 changes: 1 addition & 1 deletion src/Java.Interop/Java.Interop.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
<OutputPath>$(ToolOutputFullPath)</OutputPath>
<DocumentationFile>$(ToolOutputFullPath)Java.Interop.xml</DocumentationFile>
<JNIEnvGenPath>$(BuildToolOutputFullPath)</JNIEnvGenPath>
<LangVersion Condition=" '$(JIBuildingForNetCoreApp)' == 'True' ">11.0</LangVersion>
<LangVersion Condition=" '$(JIBuildingForNetCoreApp)' == 'True' ">9.0</LangVersion>
<LangVersion Condition=" '$(LangVersion)' == '' ">8.0</LangVersion>
<Version>$(JICoreLibVersion)</Version>
<Standalone Condition=" '$(Standalone)' == '' ">true</Standalone>
Expand Down
39 changes: 1 addition & 38 deletions src/Java.Interop/Java.Interop/JniEnvironment.Types.cs
Original file line number Diff line number Diff line change
Expand Up @@ -250,9 +250,6 @@ public static void RegisterNatives (JniObjectReference type, JniNativeMethodRegi
throw new ArgumentOutOfRangeException (nameof (numMethods), numMethods,
$"`numMethods` must be between 0 and `methods.Length` ({methods?.Length ?? 0})!");
}
if (methods == null || numMethods == 0) {
return;
}
#if DEBUG && NETCOREAPP
for (int i = 0; methods != null && i < numMethods; ++i) {
var m = methods [i];
Expand All @@ -265,42 +262,8 @@ public static void RegisterNatives (JniObjectReference type, JniNativeMethodRegi
}
#endif // DEBUG && NETCOREAPP

Span<JniBlittableNativeMethodRegistration> blittableMethods
= stackalloc JniBlittableNativeMethodRegistration [numMethods];
Span<IntPtr> freeStrings
= stackalloc IntPtr [numMethods * 2];
try {
for (int i = 0; i < numMethods; ++i) {
var name = methods [i].Name == null ? IntPtr.Zero : Marshal.StringToCoTaskMemUTF8 (methods [i].Name);
var sig = methods [i].Signature == null ? IntPtr.Zero : Marshal.StringToCoTaskMemUTF8 (methods [i].Signature);

freeStrings [(i*2)+0] = name;
freeStrings [(i*2)+1] = sig;

blittableMethods [i] = new JniBlittableNativeMethodRegistration (
name,
sig,
Marshal.GetFunctionPointerForDelegate (methods [i].Marshaler)
);
}
RegisterNatives (type, blittableMethods);
}
finally {
for (int i = 0; i < freeStrings.Length; ++i) {
Marshal.ZeroFreeCoTaskMemUTF8 (freeStrings [i]);
}
}
}
int r = _RegisterNatives (type, methods, numMethods);

public static unsafe void RegisterNatives (JniObjectReference type, ReadOnlySpan<JniBlittableNativeMethodRegistration> methods)
{
if (methods.Length == 0) {
return;
}
int r;
fixed (JniBlittableNativeMethodRegistration* pMethods = methods) {
r = _RegisterNatives (type, (IntPtr) pMethods, methods.Length);
}
if (r != 0) {
throw new InvalidOperationException (
string.Format ("Could not register native methods for class '{0}'; JNIEnv::RegisterNatives() returned {1}.", GetJniTypeNameFromClass (type), r));
Expand Down
66 changes: 0 additions & 66 deletions src/Java.Interop/Java.Interop/JniNativeMethodRegistration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,70 +20,4 @@ public JniNativeMethodRegistration (string name, string signature, Delegate mars
Marshaler = marshaler ?? throw new ArgumentNullException (nameof (marshaler));
}
}

public struct JniBlittableNativeMethodRegistration : IEquatable<JniBlittableNativeMethodRegistration> {

IntPtr name, signature, marshaler;

public JniBlittableNativeMethodRegistration (IntPtr name, IntPtr signature, IntPtr marshaler)
{
if (name == IntPtr.Zero)
throw new ArgumentNullException (nameof (name));
if (signature == IntPtr.Zero)
throw new ArgumentNullException (nameof (signature));
if (marshaler == IntPtr.Zero)
throw new ArgumentNullException (nameof (marshaler));

this.name = name;
this.signature = signature;
this.marshaler = marshaler;
}

public JniBlittableNativeMethodRegistration (ReadOnlySpan<byte> name, ReadOnlySpan<byte> signature, IntPtr marshaler)
{
if (name.Length == 0)
throw new ArgumentException ("must not be empty", nameof (name));
if (signature.Length == 0)
throw new ArgumentException ("must not be empty", nameof (signature));
if (marshaler == IntPtr.Zero)
throw new ArgumentNullException (nameof (marshaler));

this.name = FromReadOnlySpan (name);
this.signature = FromReadOnlySpan (signature);
this.marshaler = marshaler;
}

// Dodgy AF, but as the C# compiler guarantees no allocations for u8 strings,
// the "address" of `value` will never move, so this is "fine"…
// *so long as* it's *only* used for "string"u8 values.
// https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-11.0/utf8-string-literals#detailed-design
// > Since the literals would be allocated as global constants, the lifetime of the
// > resulting ReadOnlySpan<byte> would not prevent it from being returned or passed around to elsewhere.
static unsafe IntPtr FromReadOnlySpan (ReadOnlySpan<byte> value)
{
fixed (byte* p = value)
return (IntPtr) p;
}

public override bool Equals (object? obj)
{
if (obj is JniBlittableNativeMethodRegistration other) {
return Equals (other);
}
return false;
}

public override int GetHashCode () =>
HashCode.Combine (name, signature, marshaler);

public bool Equals (JniBlittableNativeMethodRegistration other) =>
name == other.name &&
signature == other.signature &&
marshaler == other.marshaler;

public static bool operator == (JniBlittableNativeMethodRegistration lhs, JniBlittableNativeMethodRegistration rhs) =>
lhs.Equals (rhs);
public static bool operator != (JniBlittableNativeMethodRegistration lhs, JniBlittableNativeMethodRegistration rhs) =>
!lhs.Equals (rhs);
}
}
7 changes: 0 additions & 7 deletions src/Java.Interop/Java.Interop/JniType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -168,13 +168,6 @@ public void RegisterNativeMethods (params JniNativeMethodRegistration[] methods)
RegisterWithRuntime ();
}

public void RegisterNativeMethods (ReadOnlySpan<JniBlittableNativeMethodRegistration> methods)
{
AssertValid ();
JniEnvironment.Types.RegisterNatives (PeerReference, methods);
RegisterWithRuntime ();
}

public void UnregisterNativeMethods ()
{
AssertValid ();
Expand Down
50 changes: 28 additions & 22 deletions src/Java.Interop/Java.Interop/ManagedPeer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,18 @@ namespace Java.Interop {

static readonly JniPeerMembers _members = new JniPeerMembers (JniTypeName, typeof (ManagedPeer));

static unsafe ManagedPeer ()
static ManagedPeer ()
{
Span<JniBlittableNativeMethodRegistration> registrations = stackalloc JniBlittableNativeMethodRegistration [2];

delegate* unmanaged<IntPtr /* jnienv */, IntPtr /* klass */,
IntPtr /* n_self */, IntPtr /* n_assemblyQualifiedName */, IntPtr /* n_constructorSignature */, IntPtr /* n_constructorArguments */,
void> construct = &Construct;
registrations [0] = new JniBlittableNativeMethodRegistration (
"construct"u8,
"(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)V"u8,
(IntPtr) construct);

delegate* unmanaged<IntPtr /* jnienv */, IntPtr /* klass */,
IntPtr /* n_nativeClass */, IntPtr /* n_assemblyQualifiedName */, IntPtr /* n_methods */,
void> registerNativeMembers = &RegisterNativeMembers;
registrations [1] = new JniBlittableNativeMethodRegistration (
"registerNativeMembers"u8,
"(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;)V"u8,
(IntPtr) registerNativeMembers);

_members.JniPeerType.RegisterNativeMethods (registrations);
_members.JniPeerType.RegisterNativeMethods (
new JniNativeMethodRegistration (
"construct",
ConstructSignature,
new ConstructMarshalMethod (Construct)),
new JniNativeMethodRegistration (
"registerNativeMembers",
RegisterNativeMembersSignature,
new RegisterMarshalMethod (RegisterNativeMembers))
);
}

ManagedPeer ()
Expand All @@ -57,8 +48,16 @@ public override JniPeerMembers JniPeerMembers {
get {return _members;}
}

const string ConstructSignature = "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)V";

// TODO: Keep in sync with the code generated by ExportedMemberBuilder
[UnmanagedCallersOnly]
[UnmanagedFunctionPointer (CallingConvention.Winapi)]
delegate void ConstructMarshalMethod (IntPtr jnienv,
IntPtr klass,
IntPtr n_self,
IntPtr n_assemblyQualifiedName,
IntPtr n_constructorSignature,
IntPtr n_constructorArguments);
static void Construct (
IntPtr jnienv,
IntPtr klass,
Expand Down Expand Up @@ -178,7 +177,14 @@ static Type[] GetParameterTypes (string? signature)
return pvalues;
}

[UnmanagedCallersOnly]
const string RegisterNativeMembersSignature = "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;)V";

[UnmanagedFunctionPointer (CallingConvention.Winapi)]
delegate void RegisterMarshalMethod (IntPtr jnienv,
IntPtr klass,
IntPtr n_nativeClass,
IntPtr n_assemblyQualifiedName,
IntPtr n_methods);
static unsafe void RegisterNativeMembers (
IntPtr jnienv,
IntPtr klass,
Expand Down
3 changes: 1 addition & 2 deletions tests/Java.Interop-Tests/Java.Interop/JniTypeTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,7 @@ public unsafe void Dispose_Exceptions ()
Assert.Throws<ObjectDisposedException> (() => t.IsAssignableFrom (null));
Assert.Throws<ObjectDisposedException> (() => t.IsInstanceOfType (new JniObjectReference ()));
Assert.Throws<ObjectDisposedException> (() => t.RegisterWithRuntime ());
Assert.Throws<ObjectDisposedException> (() => t.RegisterNativeMethods ((JniNativeMethodRegistration[])null));
Assert.Throws<ObjectDisposedException> (() => t.RegisterNativeMethods (new ReadOnlySpan<JniBlittableNativeMethodRegistration> ()));
Assert.Throws<ObjectDisposedException> (() => t.RegisterNativeMethods (null));
Assert.Throws<ObjectDisposedException> (() => t.UnregisterNativeMethods ());

JniFieldInfo jif = null;
Expand Down

0 comments on commit da9f188

Please sign in to comment.