Skip to content

Commit 69c90ba

Browse files
committed
"Full(er)" sample
What `Hello-NativeAOTFromJNI` previously did was quite minimal: 1. Use `[UnmanagedCallersOnly]` to provide a `JNI_OnLoad()` method which 2. Initialized the Java.Interop runtime, allowing 3. An `[UnmanagedCallersOnly]`-provided `Java_…` method which is called from Java. All quite low level, all miles away from .NET Android. Expand the sample to: 1. Contain a `Java.Lang.Object` subclass, which contains a `[JavaCallable]` method. 2. Call `jcw-gen` to generate Java Callable Wrappers for (1), containing the `[JavaCallable]` method. 3. Call `jnimarshalmethod-gen` to generate marshal methods for (1), as NativeAOT doesn't support System.Reflection.Emit. 4. Instantiate (1) *from Java*, and invoke the `[JavaCallable]` method. *Now* we're (kinda) getting something that looks like .NET Android. But first, we need to make that *work*: Update `Java.Interop.Tools.JavaCallableWrappers` so that it will emit `native` method declarations for `[JavaCallable]` methods, not just method overrides and `[Export]` methods. Update `Java.Interop.Tools.Expressions` so that the `_JniMarshal_*` delegate types have `[UnmanagedFunctionPointer(CallingConvention.Winapi)]`, as this is what allows NativeAOT to emit appropriate "stubs"; see also da9f188. Update `Java.Interop.Tools.Expressions.ExpressionAssemblyBuilder` to no longer attempt to "remove and fixup" `System.Private.CoreLib`. So long as `ExpressionAssemblyBuilder` output is *only* used in "completed" apps (not distributed in NuGet packages in some "intermediate" form), referencing `System.Private.CoreLib` is "fine". Additionally, trying to remove `System.Private.CoreLib` broke things when adding `[UnmanagedFunctionPointer]`, as `CallingConvention` could not be resolved, resulting in `jnimarshalmethod-gen` erroring out with: error JM4006: jnimarshalmethod-gen: Unable to process assembly '/Volumes/Xamarin-Work/src/xamarin/Java.Interop/samples/Hello-NativeAOTFromJNI/bin/Debug/Hello-NativeAOTFromJNI.dll' Failed to resolve System.Runtime.InteropServices.CallingConvention Mono.Cecil.ResolutionException: Failed to resolve System.Runtime.InteropServices.CallingConvention at Mono.Cecil.Mixin.CheckedResolve(TypeReference self) at Mono.Cecil.SignatureWriter.WriteCustomAttributeEnumValue(TypeReference enum_type, Object value) … (This is because `CallingConvention` is in `System.Runtime.InteropServices.dll`, which isn't referenced.) We could "fix" this by explicitly adding a reference to `System.Runtime.InteropServices.dll`, but this is just one of an unknown number of corner cases. Give up for now. Update `jnimarshalmethod-gen` assembly location probing: it was given the *full assembly name* of `Java.Base`: # jonp: resolving assembly: Java.Base, Version=7.0.0.0, Culture=neutral, PublicKeyToken=null …and failing to find `Java.Base.dll`, because it was looking for `Java.Base, Version=7.0.0.0, Culture=neutral, PublicKeyToken=null.dll`. Oops. Use `AssemblyName` to parse the string and extract out the assembly name., so that `Java.Base.dll` is probed for and found. With all that…it still fails: % (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! 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) `App.main()` has `new example.ManagedType()`, which hits the `ManagedType` static constructor of: public /* partial */ class ManagedType extends java.lang.Object implements com.xamarin.java_interop.GCUserPeerable { /** @hide */ public static final String __md_methods; static { __md_methods = "n_GetString:()Ljava/lang/String;:__export__\n" + ""; com.xamarin.java_interop.ManagedPeer.registerNativeMembers (ManagedType.class, "Example.ManagedType, Hello-NativeAOTFromJNI", __md_methods); } } The `ManagedPeer.registerNativeMembers()` call is what is needed to register the native `ManagedPeer.getString()` method, so that it can be called. This is good. (Though `__md_methods` containing *anything* is not desired, but that's a different problem.) `ManagedPeer.RegisterNativeMembers()` is given the assembly-qualified name `Example.ManagedType, Hello-NativeAOTFromJNI`, and tries to: Type.GetType ("Example.ManagedType, Hello-NativeAOTFromJNI", throwOnError: true); …which then proceeds to throw, because in NativeAOT *there are no assemblies*, and thus `Type.GetType()` *cannot work*. Oops. Thus, the only way to make something remotely like .NET Android infrastructure work is to *require* the use of `Java_…` native method names and `[UnmanagedCallersOnly]` on marshal methods. (In .NET Android parlance, the experimental `$(AndroidEnableMarshalMethods)`=True is required.)
1 parent da9f188 commit 69c90ba

File tree

11 files changed

+155
-26
lines changed

11 files changed

+155
-26
lines changed

samples/Hello-NativeAOTFromJNI/App.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System.Runtime.InteropServices;
2+
3+
using Java.Interop;
4+
5+
namespace Hello_NativeAOTFromJNI;
6+
7+
static class App {
8+
9+
// symbol name from `$(IntermediateOutputPath)/h-classes/com_microsoft_hello_from_jni_NativeAOTInit.h`
10+
[UnmanagedCallersOnly (EntryPoint="Java_com_microsoft_hello_1from_1jni_App_sayHello")]
11+
static IntPtr sayHello (IntPtr jnienv, IntPtr klass)
12+
{
13+
var s = $"Hello from .NET NativeAOT!";
14+
Console.WriteLine (s);
15+
var h = JniEnvironment.Strings.NewString (s);
16+
var r = JniEnvironment.References.NewReturnToJniRef (h);
17+
JniObjectReference.Dispose (ref h);
18+
return r;
19+
}
20+
}

samples/Hello-NativeAOTFromJNI/Hello-NativeAOTFromJNI.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
<ProjectReference Include="..\..\src\Java.Runtime.Environment\Java.Runtime.Environment.csproj"
1818
AdditionalProperties="Standalone=True"
1919
/>
20+
<ProjectReference Include="..\..\src\Java.Base\Java.Base.csproj" />
21+
<ProjectReference Include="..\..\src\Java.Interop.Export\Java.Interop.Export.csproj" />
2022
</ItemGroup>
2123

2224
<ItemGroup>

samples/Hello-NativeAOTFromJNI/Hello-NativeAOTFromJNI.targets

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,62 @@
11
<Project>
22

3+
<PropertyGroup>
4+
<DotnetToolPath>$(DOTNET_HOST_PATH)</DotnetToolPath>
5+
<UtilityOutputFullPath>$(MSBuildThisFileDirectory)..\..\bin\Debug-net7.0</UtilityOutputFullPath>
6+
</PropertyGroup>
7+
8+
<Target Name="_CreateJavaCallableWrappers"
9+
Condition=" '$(TargetPath)' != '' "
10+
BeforeTargets="BuildNativeAOTFromJNIJar"
11+
Inputs="$(TargetPath)"
12+
Outputs="$(IntermediateOutputPath)java\.stamp">
13+
<RemoveDir Directories="$(IntermediateOutputPath)java" />
14+
<MakeDir Directories="$(IntermediateOutputPath)java" />
15+
<ItemGroup>
16+
<!-- I can't find a good way to trim the trailing `\`, so append with `.` so we can sanely quote for $(_Libpath) -->
17+
<_RefAsmDirs Include="@(ReferencePathWithRefAssemblies->'%(RootDir)%(Directory).'->Distinct())" />
18+
</ItemGroup>
19+
<PropertyGroup>
20+
<_JcwGen>"$(UtilityOutputFullPath)/jcw-gen.dll"</_JcwGen>
21+
<_Target>--codegen-target JavaInterop1</_Target>
22+
<_Output>-o "$(IntermediateOutputPath)/java"</_Output>
23+
<_Libpath>@(_RefAsmDirs->'-L "%(Identity)"', ' ')</_Libpath>
24+
</PropertyGroup>
25+
<Exec Command="$(DotnetToolPath) $(_JcwGen) &quot;$(TargetPath)&quot; $(_Target) $(_Output) $(_Libpath)" />
26+
<Touch Files="$(IntermediateOutputPath)java\.stamp" AlwaysCreate="True" />
27+
</Target>
28+
29+
<Target Name="_AddMarshalMethods"
30+
Condition=" '$(TargetPath)' != '' "
31+
Inputs="$(TargetPath)"
32+
Outputs="$(IntermediateOutputPath).added-marshal-methods"
33+
AfterTargets="_CreateJavaCallableWrappers">
34+
<ItemGroup>
35+
<!-- I can't find a good way to trim the trailing `\`, so append with `.` so we can sanely quote for $(_Libpath) -->
36+
<_RefAsmDirs Include="@(ReferencePathWithRefAssemblies->'%(RootDir)%(Directory).'->Distinct())" />
37+
</ItemGroup>
38+
<PropertyGroup>
39+
<_JnimarshalmethodGen>"$(UtilityOutputFullPath)/jnimarshalmethod-gen.dll"</_JnimarshalmethodGen>
40+
<_Verbosity>-v -v --keeptemp</_Verbosity>
41+
<_Libpath>-L "$(TargetDir)" @(_RefAsmDirs->'-L "%(Identity)"', ' ')</_Libpath>
42+
<!-- <_Output>-o "$(IntermediateOutputPath)/jonp"</_Output> -->
43+
</PropertyGroup>
44+
45+
<Exec Command="$(DotnetToolPath) $(_JnimarshalmethodGen) &quot;$(TargetPath)&quot; $(_Verbosity) $(_Libpath)" />
46+
47+
<Touch Files="$(IntermediateOutputPath).added-marshal-methods" AlwaysCreate="True" />
48+
</Target>
49+
350
<Target Name="BuildNativeAOTFromJNIJar"
4-
BeforeTargets="Build"
51+
AfterTargets="Build"
552
Inputs="@(HelloNativeAOTFromJNIJar)"
653
Outputs="$(OutputPath)hello-from-java.jar">
754
<MakeDir Directories="$(IntermediateOutputPath)h-classes" />
855
<ItemGroup>
56+
<_JcwSource Include="$(IntermediateOutputPath)java\**\*.java" />
57+
</ItemGroup>
58+
<ItemGroup>
59+
<_Source Include="@(_JcwSource->Replace('%5c', '/'))" />
960
<_Source Include="@(HelloNativeAOTFromJNIJar->Replace('%5c', '/'))" />
1061
</ItemGroup>
1162
<WriteLinesToFile

samples/Hello-NativeAOTFromJNI/JNIEnvInit.cs renamed to samples/Hello-NativeAOTFromJNI/JavaInteropRuntime.cs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
namespace Hello_NativeAOTFromJNI;
66

7-
static class JNIEnvInit
7+
static class JavaInteropRuntime
88
{
99
static JniRuntime? runtime;
1010

@@ -30,15 +30,9 @@ static void JNI_Onload (IntPtr vm, IntPtr reserved)
3030
runtime?.Dispose ();
3131
}
3232

33-
// symbol name from `$(IntermediateOutputPath)/h-classes/com_microsoft_hello_from_jni_NativeAOTInit.h`
34-
[UnmanagedCallersOnly (EntryPoint="Java_com_microsoft_hello_1from_1jni_NativeAOTInit_sayHello")]
35-
static IntPtr sayHello (IntPtr jnienv, IntPtr klass)
33+
[UnmanagedCallersOnly (EntryPoint="Java_com_microsoft_java_1interop_JavaInteropRuntime_init")]
34+
static void init ()
3635
{
37-
var s = $"Hello from .NET NativeAOT!";
38-
Console.WriteLine (s);
39-
var h = JniEnvironment.Strings.NewString (s);
40-
var r = JniEnvironment.References.NewReturnToJniRef (h);
41-
JniObjectReference.Dispose (ref h);
42-
return r;
36+
Console.Error.WriteLine ($"C# init()");
4337
}
4438
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
namespace Example;
2+
3+
using Java.Interop;
4+
5+
[JniTypeSignature ("example/ManagedType")]
6+
class ManagedType : Java.Lang.Object {
7+
8+
public ManagedType ()
9+
{
10+
}
11+
12+
[JavaCallable ("getString")]
13+
public Java.Lang.String GetString ()
14+
{
15+
return new Java.Lang.String ("Hello from C#, via Java.Interop!");
16+
}
17+
}
Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
package com.microsoft.hello_from_jni;
22

3+
import com.microsoft.java_interop.JavaInteropRuntime;
4+
import example.ManagedType;
5+
36
class App {
47

58
public static void main(String[] args) {
69
System.out.println("Hello from Java!");
7-
String s = NativeAOTInit.sayHello();
10+
JavaInteropRuntime.init();
11+
String s = sayHello();
812
System.out.println("String returned to Java: " + s);
13+
ManagedType mt = new ManagedType();
14+
System.out.println("mt.getString()=" + mt.getString());
915
}
16+
17+
static native String sayHello();
1018
}

samples/Hello-NativeAOTFromJNI/java/com/microsoft/hello_from_jni/NativeAOTInit.java

Lines changed: 0 additions & 9 deletions
This file was deleted.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.microsoft.java_interop;
2+
3+
public class JavaInteropRuntime {
4+
static {
5+
System.loadLibrary("Hello-NativeAOTFromJNI");
6+
}
7+
8+
private JavaInteropRuntime() {
9+
}
10+
11+
public static native void init();
12+
}

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.Diagnostics;
44
using System.Linq.Expressions;
5+
using System.Runtime.InteropServices;
56
using System.Text;
67

78
using Java.Interop;
@@ -187,6 +188,15 @@ public TypeDefinition CreateMarshalMethodDelegateType (string delegateName, ILis
187188
);
188189
delegateDef.BaseType = DeclaringAssemblyDefinition.MainModule.ImportReference (typeof (MulticastDelegate));
189190

191+
var ufpCtor = typeof (UnmanagedFunctionPointerAttribute).GetConstructor (new[]{typeof (CallingConvention)});
192+
var ufpCtorRef = DeclaringAssemblyDefinition.MainModule.ImportReference (ufpCtor);
193+
var ufpAttr = new CustomAttribute (ufpCtorRef);
194+
ufpAttr.ConstructorArguments.Add (
195+
new CustomAttributeArgument (
196+
DeclaringAssemblyDefinition.MainModule.ImportReference (typeof (CallingConvention)),
197+
CallingConvention.Winapi));
198+
delegateDef.CustomAttributes.Add (ufpAttr);
199+
190200
var delegateCtor = new MethodDefinition (
191201
name: ".ctor",
192202
attributes: MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
@@ -218,7 +228,7 @@ public void Write (string path)
218228
var module = DeclaringAssemblyDefinition.MainModule;
219229

220230
var c = new MemoryStream ();
221-
DeclaringAssemblyDefinition.Write (c);
231+
DeclaringAssemblyDefinition.Write (path);
222232
c.Position = 0;
223233

224234
if (KeepTemporaryFiles) {
@@ -227,6 +237,10 @@ public void Write (string path)
227237
c.Position = 0;
228238
}
229239

240+
241+
#if false
242+
// `Failed to resolve System.Runtime.InteropServices.CallingConvention`
243+
// because `System.Runtime.InteropServices` is not referenced.
230244
Logger (TraceLevel.Verbose, $"# jonp: ---");
231245

232246
var rp = new ReaderParameters {
@@ -277,6 +291,7 @@ public void Write (string path)
277291
module.AssemblyReferences.Remove (selfRef);
278292
}
279293
newAsm.Write (path);
294+
#endif // false
280295
}
281296

282297
static AssemblyNameReference GetSystemRuntimeReference ()

src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/JavaCallableWrapperGenerator.cs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,10 @@ void AddNestedTypes (TypeDefinition type)
164164
var baseRegisteredMethod = GetBaseRegisteredMethod (minfo);
165165
if (baseRegisteredMethod != null)
166166
AddMethod (baseRegisteredMethod, minfo);
167-
else if (minfo.AnyCustomAttributes (typeof(ExportFieldAttribute))) {
167+
else if (minfo.AnyCustomAttributes ("Java.Interop.JavaCallableAttribute")) {
168+
AddMethod (null, minfo);
169+
HasExport = true;
170+
} else if (minfo.AnyCustomAttributes (typeof(ExportFieldAttribute))) {
168171
AddMethod (null, minfo);
169172
HasExport = true;
170173
} else if (minfo.AnyCustomAttributes (typeof (ExportAttribute))) {
@@ -412,6 +415,12 @@ ExportAttribute ToExportAttribute (CustomAttribute attr, IMemberDefinition decla
412415
return new ExportAttribute (name) {ThrownNames = thrown, SuperArgumentsString = superArgs};
413416
}
414417

418+
ExportAttribute ToExportAttributeFromJavaCallableAttribute (CustomAttribute attr, IMemberDefinition declaringMember)
419+
{
420+
var name = attr.ConstructorArguments.Count > 0 ? (string) attr.ConstructorArguments [0].Value : declaringMember.Name;
421+
return new ExportAttribute (name);
422+
}
423+
415424
internal static ExportFieldAttribute ToExportFieldAttribute (CustomAttribute attr)
416425
{
417426
return new ExportFieldAttribute ((string) attr.ConstructorArguments [0].Value);
@@ -447,7 +456,8 @@ static IEnumerable<RegisterAttribute> GetMethodRegistrationAttributes (Mono.Ceci
447456

448457
IEnumerable<ExportAttribute> GetExportAttributes (IMemberDefinition p)
449458
{
450-
return GetAttributes<ExportAttribute> (p, a => ToExportAttribute (a, p));
459+
return GetAttributes<ExportAttribute> (p, a => ToExportAttribute (a, p))
460+
.Concat (GetAttributes<ExportAttribute> (p, "Java.Interop.JavaCallableAttribute", a => ToExportAttributeFromJavaCallableAttribute (a, p)));
451461
}
452462

453463
static IEnumerable<ExportFieldAttribute> GetExportFieldAttributes (Mono.Cecil.ICustomAttributeProvider p)
@@ -458,7 +468,13 @@ static IEnumerable<ExportFieldAttribute> GetExportFieldAttributes (Mono.Cecil.IC
458468
static IEnumerable<TAttribute> GetAttributes<TAttribute> (Mono.Cecil.ICustomAttributeProvider p, Func<CustomAttribute, TAttribute?> selector)
459469
where TAttribute : class
460470
{
461-
return p.GetCustomAttributes (typeof (TAttribute))
471+
return GetAttributes (p, typeof (TAttribute).FullName, selector);
472+
}
473+
474+
static IEnumerable<TAttribute> GetAttributes<TAttribute> (Mono.Cecil.ICustomAttributeProvider p, string attributeName, Func<CustomAttribute, TAttribute?> selector)
475+
where TAttribute : class
476+
{
477+
return p.GetCustomAttributes (attributeName)
462478
.Select (selector)
463479
.Where (v => v != null)
464480
.Select (v => v!);

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy