Skip to content

Commit e1822f0

Browse files
committed
Make it (mostly) work!
Commit 69c90ba expanded into a "Full(er)" sample, with just one issue: it didn't fully work: Exception in thread "main" com.xamarin.java_interop.internal.JavaProxyThrowable: System.IO.FileNotFoundException: Could not resolve assembly 'Hello-NativeAOTFromJNI'. at System.Reflection.TypeNameParser.ResolveAssembly(String) + 0x97 at System.Reflection.TypeNameParser.GetType(String, ReadOnlySpan`1, String) + 0x32 at System.Reflection.TypeNameParser.NamespaceTypeName.ResolveType(TypeNameParser&, String) + 0x17 at System.Reflection.TypeNameParser.GetType(String, Func`2, Func`4, Boolean, Boolean, Boolean, String) + 0x99 at Java.Interop.ManagedPeer.RegisterNativeMembers(IntPtr jnienv, IntPtr klass, IntPtr n_nativeClass, IntPtr n_assemblyQualifiedName, IntPtr n_methods) + 0x103 at com.xamarin.java_interop.ManagedPeer.registerNativeMembers(Native Method) at example.ManagedType.<clinit>(ManagedType.java:15) at com.microsoft.hello_from_jni.App.main(App.java:13) The problem is that `ManagedPeer.RegisterNativeMembers()` calls `Type.GetType("Example.ManagedType, Hello-NativeAOTFromJNI")`, which throws `FileNotFoundException`. Let's attempt to fix that: Update `MangedPeer.RegisterNativeMembers()` to call the (new!) method: partial class JniRuntime { partial class JniTypeManager { public virtual void RegisterNativeMembers ( JniType nativeClass, ReadOnlySpan<char> assmblyQualifiedTypeName, ReadOnlySpan<char> methods); } } which allows a subclass to *avoid* the `Type.GetType()` call. Add a `NativeAotTypeManager` class which subclasses `JniRuntime.JniTypeManager`, overriding `RegisterNativeMembers()` so as to avoid the `Type.GetType()` call. (It also "fakes" its own "typemap" implementation…) Add `Hello-NativeAOTFromJNI.xml`, a Linker Descriptor, to preserve the `JavaException` constructors, which are needed when things break horrifically. TODO: figure out the appropriate `DynamicDependencyAttribute` incantations to replace `Hello-NativeAOTFromJNI.xml`. Update the `_AddMarshalMethods` build task to *also* update `$(IntermediateOutputPath)$(TargetFileName)`, as the copy in `$(IntermediateOutputPath)` is used by the `IlcCompile` target. *Not* updating the copy in `$(IntermediateOutputPath)` means that we don't get *any* marshal methods, and things break. Rename `ExpressionAssemblyBuilder.CreateRegistrationMethod()` to Rename `ExpressionAssemblyBuilder.AddRegistrationMethod()`, so that the `EmitConsoleWriteLine()` invocation can provide the *full* type name of the `__RegisterNativeMembers()` method, which helps when there is more than one such method running around… Various changes to `JniRuntime.JniTypeManager.cs`, to increase logging verbosity, and to make the optimization effort in `TryLoadJniMarshalMethods()` actually work; `Type.GetRuntimeMethod()` *will not find* non-public methods, and `__RegisterNativeMembers()` is rarely/never public. Thus, it was basically dead code, and we'd always fallback to the "look at all methods and see which ones have `[JniAddNativeMethodRegistration]`" codepath, which is by definition slower. Use `Type.GetMethod()` instead. Update `jnimarshalmethod-gen` & co so that they're consistent with the output of `jcw-gen`. Without these changes, the JCW would declare `n_GetString()`, while `jnimarshalmethod-gen` would try to register `getString`, and Java would thrown an exception because there is no `getString` member to register. Doh! Finally, and the one thing that keeps this from being "perfect", add an "activation constructor" `Example.ManagedType(ref JniObjectReference, JniObjectReferenceOptions)`. This constructor is currently needed because "JavaInterop1"-style Java Callable Wrappers don't contain constructors (?!), so no `Example.ManagedType` instance is created *until* the `ManagedType.n_GetString()` marshal method is executed and attempts to invoke the `ManagedType.GetString()` method. We'll need to update `jcw-gen` & related to address this. Current output: % (cd bin/Release/osx-x64/publish ; java -cp hello-from-java.jar:java-interop.jar com/microsoft/hello_from_jni/App) Hello from Java! C# init() Hello from .NET NativeAOT! String returned to Java: Hello from .NET NativeAOT! C# RegisterNativeMembers(JniType(Name='example/ManagedType' PeerReference=0x7fe545812ed8/G), "Example.ManagedType, Hello-NativeAOTFromJNI", "n_GetString:()Ljava/lang/String;:__export__ ") # jonp: called `Example.ManagedType/__<$>_jni_marshal_methods.__RegisterNativeMembers()` w/ 1 methods to register. mt.getString()=Hello from C#, via Java.Interop!
1 parent a499a1d commit e1822f0

File tree

11 files changed

+112
-24
lines changed

11 files changed

+112
-24
lines changed

samples/Hello-NativeAOTFromJNI/Hello-NativeAOTFromJNI.csproj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@
2121
<ProjectReference Include="..\..\src\Java.Interop.Export\Java.Interop.Export.csproj" />
2222
</ItemGroup>
2323

24+
<ItemGroup>
25+
<TrimmerRootAssembly Include="Hello-NativeAOTFromJNI" />
26+
<TrimmerRootDescriptor Include="Hello-NativeAOTFromJNI.xml" />
27+
</ItemGroup>
28+
2429
<ItemGroup>
2530
<Content Include="$(OutputPath)hello-from-java.jar" CopyToPublishDirectory="PreserveNewest" />
2631
</ItemGroup>

samples/Hello-NativeAOTFromJNI/Hello-NativeAOTFromJNI.targets

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@
4444

4545
<Exec Command="$(DotnetToolPath) $(_JnimarshalmethodGen) &quot;$(TargetPath)&quot; $(_Verbosity) $(_Libpath)" />
4646

47+
<!-- the IlcCompile target uses files from `$(IntermediateOutputPath)`, not `$(TargetPath)`, so… update both? -->
48+
<Copy SourceFiles="$(TargetPath)" DestinationFolder="$(IntermediateOutputPath)" />
49+
4750
<Touch Files="$(IntermediateOutputPath).added-marshal-methods" AlwaysCreate="True" />
4851
</Target>
4952

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<linker>
2+
<assembly fullname="Java.Interop">
3+
<type fullname="Java.Interop.JavaException">
4+
<method name=".ctor" />
5+
</type>
6+
</assembly>
7+
</linker>

samples/Hello-NativeAOTFromJNI/JavaInteropRuntime.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ static void init (IntPtr jnienv, IntPtr klass)
2727
try {
2828
var options = new JreRuntimeOptions {
2929
EnvironmentPointer = jnienv,
30+
TypeManager = new NativeAotTypeManager (),
3031
};
3132
runtime = options.CreateJreVM ();
3233
}

samples/Hello-NativeAOTFromJNI/ManagedType.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ public ManagedType ()
99
{
1010
}
1111

12+
// TODO: remove this
13+
public ManagedType (ref JniObjectReference reference, JniObjectReferenceOptions options)
14+
: base (ref reference, options)
15+
{
16+
}
17+
1218
[JavaCallable ("getString")]
1319
public Java.Lang.String GetString ()
1420
{
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using Java.Interop;
2+
3+
namespace Hello_NativeAOTFromJNI;
4+
5+
class NativeAotTypeManager : JniRuntime.JniTypeManager {
6+
7+
#pragma warning disable IL2026
8+
Dictionary<string, Type> typeMappings = new () {
9+
["com/xamarin/java_interop/internal/JavaProxyThrowable"] = typeof (JniEnvironment).Assembly.GetType ("Java.Interop.JavaProxyThrowable", throwOnError: true)!,
10+
["example/ManagedType"] = typeof (Example.ManagedType),
11+
};
12+
#pragma warning restore IL2026
13+
14+
public override void RegisterNativeMembers (JniType nativeClass, ReadOnlySpan<char> assemblyQualifiedTypeName, ReadOnlySpan<char> methods)
15+
{
16+
Console.WriteLine ($"C# RegisterNativeMembers({nativeClass}, \"{assemblyQualifiedTypeName}\", \"{methods}\")");
17+
if (typeMappings.TryGetValue (nativeClass.Name, out var type)) {
18+
base.TryRegisterNativeMembers (nativeClass, type, methods);
19+
}
20+
else {
21+
throw new NotSupportedException ($"Unsupported registration type: \"{nativeClass.Name}\" <=> \"{assemblyQualifiedTypeName}\"!");
22+
}
23+
}
24+
25+
protected override IEnumerable<Type> GetTypesForSimpleReference (string jniSimpleReference)
26+
{
27+
if (typeMappings.TryGetValue (jniSimpleReference, out var target))
28+
yield return target;
29+
foreach (var t in base.GetTypesForSimpleReference (jniSimpleReference))
30+
yield return t;
31+
}
32+
33+
protected override IEnumerable<string> GetSimpleReferences (Type type)
34+
{
35+
return base.GetSimpleReferences (type)
36+
.Concat (CreateSimpleReferencesEnumerator (type));
37+
}
38+
39+
IEnumerable<string> CreateSimpleReferencesEnumerator (Type type)
40+
{
41+
if (typeMappings == null)
42+
yield break;
43+
foreach (var e in typeMappings) {
44+
if (e.Value == type)
45+
yield return e.Key;
46+
}
47+
}
48+
}

src/Java.Interop.Tools.Expressions/Java.Interop.Tools.Expressions/ExpressionAssemblyBuilder.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ static MethodDefinition CreateMethodDefinition (AssemblyDefinition declaringAsse
5959
return mmDef;
6060
}
6161

62-
public MethodDefinition CreateRegistrationMethod (IList<ExpressionMethodRegistration> methods)
62+
public void AddRegistrationMethod (TypeDefinition declaringType, IList<ExpressionMethodRegistration> methods)
6363
{
6464
var registrations = new MethodDefinition (
6565
name: "__RegisterNativeMembers",
@@ -71,6 +71,8 @@ public MethodDefinition CreateRegistrationMethod (IList<ExpressionMethodRegistra
7171
},
7272
};
7373

74+
declaringType.Methods.Add (registrations);
75+
7476
var ctor = typeof (JniAddNativeMethodRegistrationAttribute).GetConstructor (Type.EmptyTypes);
7577
var attr = new CustomAttribute (DeclaringAssemblyDefinition.MainModule.ImportReference (ctor));
7678
registrations.CustomAttributes.Add (attr);
@@ -84,7 +86,7 @@ public MethodDefinition CreateRegistrationMethod (IList<ExpressionMethodRegistra
8486
registrations.Body.Variables.Add (array);
8587

8688
var il = registrations.Body.GetILProcessor ();
87-
EmitConsoleWriteLine (il, $"# jonp: called __RegisterNativeMembers w/ {methods.Count} methods to register.");
89+
EmitConsoleWriteLine (il, $"# jonp: called `{declaringType.FullName}.__RegisterNativeMembers()` w/ {methods.Count} methods to register.");
8890
il.Emit (OpCodes.Ldc_I4, methods.Count);
8991
il.Emit (OpCodes.Newarr, DeclaringAssemblyDefinition.MainModule.ImportReference (arrayType.GetElementType ()));
9092
// il.Emit (OpCodes.Stloc_0);
@@ -117,9 +119,6 @@ public MethodDefinition CreateRegistrationMethod (IList<ExpressionMethodRegistra
117119
il.Emit (OpCodes.Ldloc_0);
118120
il.Emit (OpCodes.Call, DeclaringAssemblyDefinition.MainModule.ImportReference (addRegistrations.Method));
119121
il.Emit (OpCodes.Ret);
120-
121-
122-
return registrations;
123122
}
124123

125124
void EmitConsoleWriteLine (ILProcessor il, string message)

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,8 @@ public static void RegisterNatives (JniObjectReference type, JniNativeMethodRegi
265265
int r = _RegisterNatives (type, methods, numMethods);
266266

267267
if (r != 0) {
268+
Console.WriteLine ($"# jonp: _RegisterNatives returned {r}");
269+
JniNativeMethods.ExceptionDescribe (JniEnvironment.CurrentInfo.EnvironmentPointer);
268270
throw new InvalidOperationException (
269271
string.Format ("Could not register native methods for class '{0}'; JNIEnv::RegisterNatives() returned {1}.", GetJniTypeNameFromClass (type), r));
270272
}

src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,9 @@ IEnumerable<Type> CreateGetTypesForSimpleReferenceEnumerator (string jniSimpleRe
372372

373373
protected virtual ReplacementMethodInfo? GetReplacementMethodInfoCore (string jniSimpleReference, string jniMethodName, string jniMethodSignature) => null;
374374

375+
public virtual void RegisterNativeMembers (JniType nativeClass, ReadOnlySpan<char> assmblyQualifiedTypeName, ReadOnlySpan<char> methods) =>
376+
RegisterNativeMembers (nativeClass, Type.GetType (new string (assmblyQualifiedTypeName), throwOnError: true)!, methods);
377+
375378
public virtual void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan<char> methods)
376379
{
377380
TryRegisterNativeMembers (nativeClass, type, methods);
@@ -412,10 +415,21 @@ protected bool TryRegisterNativeMembers (JniType nativeClass, Type type, string?
412415
bool TryLoadJniMarshalMethods (JniType nativeClass, Type type, string? methods)
413416
{
414417
var marshalType = type?.GetNestedType ("__<$>_jni_marshal_methods", BindingFlags.NonPublic);
415-
if (marshalType == null)
418+
if (marshalType == null) {
419+
Console.WriteLine ($"# jonp: could not find type `{(type?.FullName ?? "<no-type>")}.__<$>_jni_marshal_methods`");
416420
return false;
421+
}
417422

418-
var registerMethod = marshalType.GetRuntimeMethod ("__RegisterNativeMembers", registerMethodParameters);
423+
var registerMethod = marshalType.GetMethod (
424+
"__RegisterNativeMembers",
425+
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public,
426+
binder: null,
427+
callConvention: default,
428+
types: registerMethodParameters,
429+
modifiers: null);
430+
if (registerMethod == null) {
431+
Console.WriteLine ($"# jonp: could not find method `{marshalType.FullName}.__<$>_jni_marshal_methods.__RegisterNativeMembers`; will look for methods with `[JniAddNativeMethodRegistration]`…]");
432+
}
419433

420434
return TryRegisterNativeMembers (nativeClass, marshalType, methods, registerMethod);
421435
}
@@ -466,11 +480,14 @@ bool FindAndCallRegisterMethod (Type marshalType, JniNativeMethodRegistrationArg
466480
continue;
467481
}
468482

483+
var declaringTypeName = methodInfo.DeclaringType?.FullName ?? "<no-decl-type>";
484+
469485
if ((methodInfo.Attributes & MethodAttributes.Static) != MethodAttributes.Static) {
470-
throw new InvalidOperationException ($"The method {methodInfo} marked with {nameof (JniAddNativeMethodRegistrationAttribute)} must be static");
486+
throw new InvalidOperationException ($"The method `{declaringTypeName}.{methodInfo}` marked with [{nameof (JniAddNativeMethodRegistrationAttribute)}] must be static!");
471487
}
472488

473489
var register = (Action<JniNativeMethodRegistrationArguments>)methodInfo.CreateDelegate (typeof (Action<JniNativeMethodRegistrationArguments>));
490+
Console.WriteLine ($"# jonp: FindAndCallRegisterMethod: calling `{declaringTypeName}.{methodInfo}`…");
474491
register (arguments);
475492

476493
found = true;

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -197,22 +197,27 @@ static unsafe void RegisterNativeMembers (
197197
var r_nativeClass = new JniObjectReference (n_nativeClass);
198198
var nativeClass = new JniType (ref r_nativeClass, JniObjectReferenceOptions.Copy);
199199

200-
var assemblyQualifiedName = JniEnvironment.Strings.ToString (new JniObjectReference (n_assemblyQualifiedName));
201-
var type = Type.GetType (assemblyQualifiedName!, throwOnError: true)!;
202200
var methodsRef = new JniObjectReference (n_methods);
203-
204201
#if NET
205202

203+
var aqnRef = new JniObjectReference (n_assemblyQualifiedName);
204+
int aqnLength = JniEnvironment.Strings.GetStringLength (aqnRef);
205+
var aqnChars = JniEnvironment.Strings.GetStringChars (aqnRef, null);
206+
var aqn = new ReadOnlySpan<char>(aqnChars, aqnLength);
207+
206208
int methodsLength = JniEnvironment.Strings.GetStringLength (methodsRef);
207209
var methodsChars = JniEnvironment.Strings.GetStringChars (methodsRef, null);
208210
var methods = new ReadOnlySpan<char>(methodsChars, methodsLength);
209211
try {
210-
JniEnvironment.Runtime.TypeManager.RegisterNativeMembers (nativeClass, type, methods);
212+
JniEnvironment.Runtime.TypeManager.RegisterNativeMembers (nativeClass, aqn, methods);
211213
}
212214
finally {
215+
JniEnvironment.Strings.ReleaseStringChars (aqnRef, aqnChars);
213216
JniEnvironment.Strings.ReleaseStringChars (methodsRef, methodsChars);
214217
}
215218
#else // NET
219+
var assemblyQualifiedName = JniEnvironment.Strings.ToString (new JniObjectReference (n_assemblyQualifiedName));
220+
var type = Type.GetType (assemblyQualifiedName!, throwOnError: true)!;
216221
var methods = JniEnvironment.Strings.ToString (methodsRef);
217222
JniEnvironment.Runtime.TypeManager.RegisterNativeMembers (nativeClass, type, methods);
218223
#endif // NET

0 commit comments

Comments
 (0)