Skip to content

Commit da9f188

Browse files
committed
Use [UnmanagedFunctionPointer]
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).
1 parent a08dce8 commit da9f188

File tree

8 files changed

+33
-137
lines changed

8 files changed

+33
-137
lines changed

build-tools/jnienv-gen/Generator.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -758,7 +758,7 @@ abstract class TypeInfo
758758
{ "void*", new BuiltinTypeInfo ("void*", "IntPtr") },
759759
{ "const jchar*", new StringTypeInfo ("const jchar*") },
760760
{ "const char*", new StringTypeInfo ("const char*") },
761-
{ "const JNINativeMethod*", new BuiltinTypeInfo ("const JNINativeMethod*", "IntPtr") },
761+
{ "const JNINativeMethod*", new BuiltinTypeInfo ("const JNINativeMethod*", "JniNativeMethodRegistration []") },
762762
{ "jobjectRefType", new BuiltinTypeInfo ("jobjectRefType", "JniObjectReferenceType") },
763763
{ "jfieldID", new InstanceFieldTypeInfo ("jfieldID") },
764764
{ "jstaticfieldID", new StaticFieldTypeInfo ("jstaticfieldID") },

samples/Hello-NativeAOTFromJNI/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ Time Elapsed 00:00:00.83
5656
% (cd bin/Release/osx-x64/publish ; java -cp hello-from-java.jar:java-interop.jar com/microsoft/hello_from_jni/App)
5757
Hello from Java!
5858
Hello from .NET NativeAOT!
59+
String returned to Java: Hello from .NET NativeAOT!
5960
```
6061

6162
Note the use of `(cd …; java …)` so that `libHello-NativeAOTFromJNI.dylib` is

src/Java.Interop/Java.Interop.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
<OutputPath>$(ToolOutputFullPath)</OutputPath>
2727
<DocumentationFile>$(ToolOutputFullPath)Java.Interop.xml</DocumentationFile>
2828
<JNIEnvGenPath>$(BuildToolOutputFullPath)</JNIEnvGenPath>
29-
<LangVersion Condition=" '$(JIBuildingForNetCoreApp)' == 'True' ">11.0</LangVersion>
29+
<LangVersion Condition=" '$(JIBuildingForNetCoreApp)' == 'True' ">9.0</LangVersion>
3030
<LangVersion Condition=" '$(LangVersion)' == '' ">8.0</LangVersion>
3131
<Version>$(JICoreLibVersion)</Version>
3232
<Standalone Condition=" '$(Standalone)' == '' ">true</Standalone>

src/Java.Interop/Java.Interop/JniEnvironment.Types.cs

+1-38
Original file line numberDiff line numberDiff line change
@@ -250,9 +250,6 @@ public static void RegisterNatives (JniObjectReference type, JniNativeMethodRegi
250250
throw new ArgumentOutOfRangeException (nameof (numMethods), numMethods,
251251
$"`numMethods` must be between 0 and `methods.Length` ({methods?.Length ?? 0})!");
252252
}
253-
if (methods == null || numMethods == 0) {
254-
return;
255-
}
256253
#if DEBUG && NETCOREAPP
257254
for (int i = 0; methods != null && i < numMethods; ++i) {
258255
var m = methods [i];
@@ -265,42 +262,8 @@ public static void RegisterNatives (JniObjectReference type, JniNativeMethodRegi
265262
}
266263
#endif // DEBUG && NETCOREAPP
267264

268-
Span<JniBlittableNativeMethodRegistration> blittableMethods
269-
= stackalloc JniBlittableNativeMethodRegistration [numMethods];
270-
Span<IntPtr> freeStrings
271-
= stackalloc IntPtr [numMethods * 2];
272-
try {
273-
for (int i = 0; i < numMethods; ++i) {
274-
var name = methods [i].Name == null ? IntPtr.Zero : Marshal.StringToCoTaskMemUTF8 (methods [i].Name);
275-
var sig = methods [i].Signature == null ? IntPtr.Zero : Marshal.StringToCoTaskMemUTF8 (methods [i].Signature);
276-
277-
freeStrings [(i*2)+0] = name;
278-
freeStrings [(i*2)+1] = sig;
279-
280-
blittableMethods [i] = new JniBlittableNativeMethodRegistration (
281-
name,
282-
sig,
283-
Marshal.GetFunctionPointerForDelegate (methods [i].Marshaler)
284-
);
285-
}
286-
RegisterNatives (type, blittableMethods);
287-
}
288-
finally {
289-
for (int i = 0; i < freeStrings.Length; ++i) {
290-
Marshal.ZeroFreeCoTaskMemUTF8 (freeStrings [i]);
291-
}
292-
}
293-
}
265+
int r = _RegisterNatives (type, methods, numMethods);
294266

295-
public static unsafe void RegisterNatives (JniObjectReference type, ReadOnlySpan<JniBlittableNativeMethodRegistration> methods)
296-
{
297-
if (methods.Length == 0) {
298-
return;
299-
}
300-
int r;
301-
fixed (JniBlittableNativeMethodRegistration* pMethods = methods) {
302-
r = _RegisterNatives (type, (IntPtr) pMethods, methods.Length);
303-
}
304267
if (r != 0) {
305268
throw new InvalidOperationException (
306269
string.Format ("Could not register native methods for class '{0}'; JNIEnv::RegisterNatives() returned {1}.", GetJniTypeNameFromClass (type), r));

src/Java.Interop/Java.Interop/JniNativeMethodRegistration.cs

-66
Original file line numberDiff line numberDiff line change
@@ -20,70 +20,4 @@ public JniNativeMethodRegistration (string name, string signature, Delegate mars
2020
Marshaler = marshaler ?? throw new ArgumentNullException (nameof (marshaler));
2121
}
2222
}
23-
24-
public struct JniBlittableNativeMethodRegistration : IEquatable<JniBlittableNativeMethodRegistration> {
25-
26-
IntPtr name, signature, marshaler;
27-
28-
public JniBlittableNativeMethodRegistration (IntPtr name, IntPtr signature, IntPtr marshaler)
29-
{
30-
if (name == IntPtr.Zero)
31-
throw new ArgumentNullException (nameof (name));
32-
if (signature == IntPtr.Zero)
33-
throw new ArgumentNullException (nameof (signature));
34-
if (marshaler == IntPtr.Zero)
35-
throw new ArgumentNullException (nameof (marshaler));
36-
37-
this.name = name;
38-
this.signature = signature;
39-
this.marshaler = marshaler;
40-
}
41-
42-
public JniBlittableNativeMethodRegistration (ReadOnlySpan<byte> name, ReadOnlySpan<byte> signature, IntPtr marshaler)
43-
{
44-
if (name.Length == 0)
45-
throw new ArgumentException ("must not be empty", nameof (name));
46-
if (signature.Length == 0)
47-
throw new ArgumentException ("must not be empty", nameof (signature));
48-
if (marshaler == IntPtr.Zero)
49-
throw new ArgumentNullException (nameof (marshaler));
50-
51-
this.name = FromReadOnlySpan (name);
52-
this.signature = FromReadOnlySpan (signature);
53-
this.marshaler = marshaler;
54-
}
55-
56-
// Dodgy AF, but as the C# compiler guarantees no allocations for u8 strings,
57-
// the "address" of `value` will never move, so this is "fine"…
58-
// *so long as* it's *only* used for "string"u8 values.
59-
// https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-11.0/utf8-string-literals#detailed-design
60-
// > Since the literals would be allocated as global constants, the lifetime of the
61-
// > resulting ReadOnlySpan<byte> would not prevent it from being returned or passed around to elsewhere.
62-
static unsafe IntPtr FromReadOnlySpan (ReadOnlySpan<byte> value)
63-
{
64-
fixed (byte* p = value)
65-
return (IntPtr) p;
66-
}
67-
68-
public override bool Equals (object? obj)
69-
{
70-
if (obj is JniBlittableNativeMethodRegistration other) {
71-
return Equals (other);
72-
}
73-
return false;
74-
}
75-
76-
public override int GetHashCode () =>
77-
HashCode.Combine (name, signature, marshaler);
78-
79-
public bool Equals (JniBlittableNativeMethodRegistration other) =>
80-
name == other.name &&
81-
signature == other.signature &&
82-
marshaler == other.marshaler;
83-
84-
public static bool operator == (JniBlittableNativeMethodRegistration lhs, JniBlittableNativeMethodRegistration rhs) =>
85-
lhs.Equals (rhs);
86-
public static bool operator != (JniBlittableNativeMethodRegistration lhs, JniBlittableNativeMethodRegistration rhs) =>
87-
!lhs.Equals (rhs);
88-
}
8923
}

src/Java.Interop/Java.Interop/JniType.cs

-7
Original file line numberDiff line numberDiff line change
@@ -168,13 +168,6 @@ public void RegisterNativeMethods (params JniNativeMethodRegistration[] methods)
168168
RegisterWithRuntime ();
169169
}
170170

171-
public void RegisterNativeMethods (ReadOnlySpan<JniBlittableNativeMethodRegistration> methods)
172-
{
173-
AssertValid ();
174-
JniEnvironment.Types.RegisterNatives (PeerReference, methods);
175-
RegisterWithRuntime ();
176-
}
177-
178171
public void UnregisterNativeMethods ()
179172
{
180173
AssertValid ();

src/Java.Interop/Java.Interop/ManagedPeer.cs

+28-22
Original file line numberDiff line numberDiff line change
@@ -20,27 +20,18 @@ namespace Java.Interop {
2020

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

23-
static unsafe ManagedPeer ()
23+
static ManagedPeer ()
2424
{
25-
Span<JniBlittableNativeMethodRegistration> registrations = stackalloc JniBlittableNativeMethodRegistration [2];
26-
27-
delegate* unmanaged<IntPtr /* jnienv */, IntPtr /* klass */,
28-
IntPtr /* n_self */, IntPtr /* n_assemblyQualifiedName */, IntPtr /* n_constructorSignature */, IntPtr /* n_constructorArguments */,
29-
void> construct = &Construct;
30-
registrations [0] = new JniBlittableNativeMethodRegistration (
31-
"construct"u8,
32-
"(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)V"u8,
33-
(IntPtr) construct);
34-
35-
delegate* unmanaged<IntPtr /* jnienv */, IntPtr /* klass */,
36-
IntPtr /* n_nativeClass */, IntPtr /* n_assemblyQualifiedName */, IntPtr /* n_methods */,
37-
void> registerNativeMembers = &RegisterNativeMembers;
38-
registrations [1] = new JniBlittableNativeMethodRegistration (
39-
"registerNativeMembers"u8,
40-
"(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;)V"u8,
41-
(IntPtr) registerNativeMembers);
42-
43-
_members.JniPeerType.RegisterNativeMethods (registrations);
25+
_members.JniPeerType.RegisterNativeMethods (
26+
new JniNativeMethodRegistration (
27+
"construct",
28+
ConstructSignature,
29+
new ConstructMarshalMethod (Construct)),
30+
new JniNativeMethodRegistration (
31+
"registerNativeMembers",
32+
RegisterNativeMembersSignature,
33+
new RegisterMarshalMethod (RegisterNativeMembers))
34+
);
4435
}
4536

4637
ManagedPeer ()
@@ -57,8 +48,16 @@ public override JniPeerMembers JniPeerMembers {
5748
get {return _members;}
5849
}
5950

51+
const string ConstructSignature = "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)V";
52+
6053
// TODO: Keep in sync with the code generated by ExportedMemberBuilder
61-
[UnmanagedCallersOnly]
54+
[UnmanagedFunctionPointer (CallingConvention.Winapi)]
55+
delegate void ConstructMarshalMethod (IntPtr jnienv,
56+
IntPtr klass,
57+
IntPtr n_self,
58+
IntPtr n_assemblyQualifiedName,
59+
IntPtr n_constructorSignature,
60+
IntPtr n_constructorArguments);
6261
static void Construct (
6362
IntPtr jnienv,
6463
IntPtr klass,
@@ -178,7 +177,14 @@ static Type[] GetParameterTypes (string? signature)
178177
return pvalues;
179178
}
180179

181-
[UnmanagedCallersOnly]
180+
const string RegisterNativeMembersSignature = "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;)V";
181+
182+
[UnmanagedFunctionPointer (CallingConvention.Winapi)]
183+
delegate void RegisterMarshalMethod (IntPtr jnienv,
184+
IntPtr klass,
185+
IntPtr n_nativeClass,
186+
IntPtr n_assemblyQualifiedName,
187+
IntPtr n_methods);
182188
static unsafe void RegisterNativeMembers (
183189
IntPtr jnienv,
184190
IntPtr klass,

tests/Java.Interop-Tests/Java.Interop/JniTypeTest.cs

+1-2
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,7 @@ public unsafe void Dispose_Exceptions ()
5656
Assert.Throws<ObjectDisposedException> (() => t.IsAssignableFrom (null));
5757
Assert.Throws<ObjectDisposedException> (() => t.IsInstanceOfType (new JniObjectReference ()));
5858
Assert.Throws<ObjectDisposedException> (() => t.RegisterWithRuntime ());
59-
Assert.Throws<ObjectDisposedException> (() => t.RegisterNativeMethods ((JniNativeMethodRegistration[])null));
60-
Assert.Throws<ObjectDisposedException> (() => t.RegisterNativeMethods (new ReadOnlySpan<JniBlittableNativeMethodRegistration> ()));
59+
Assert.Throws<ObjectDisposedException> (() => t.RegisterNativeMethods (null));
6160
Assert.Throws<ObjectDisposedException> (() => t.UnregisterNativeMethods ());
6261

6362
JniFieldInfo jif = null;

0 commit comments

Comments
 (0)