Skip to content

Commit c93fea0

Browse files
authored
[jcw-gen, jnimarshalmethod-gen] Native method consistency (#1160)
Context: #1153 Context: 4787e01 Context: 77800dd PR #1153 is exploring the use of [.NET Native AOT][0] to produce a native library which is used *within* a `java`-originated process: % java -cp … com/microsoft/hello_from_jni/App # launches NativeAOT-generated native lib, executes C# code… As NativeAOT has no support for `System.Reflection.Emit`, the only way for Java code to invoke managed code -- in a Desktop Java.Base environment! [^0] see 4787e01 -- would be to pre-generate the required marshal methods via `jnimarshalmethod-gen`. This in turn requires updating `jcw-gen` to support the pre-existing `Java.Interop.JavaCallableAttribute`, so that C# code could reasonably declare methods visible to Java, along with the introduction of, and support for, a new `Java.Interop.JavaCallableConstructorAttribute` type. This allows straightforward usage: [JniTypeSignature ("example/ManagedType")] // for a nice Java name! class ManagedType : Java.Lang.Object { int value; [JavaCallableConstructor(SuperConstructorExpression="")] public ManagedType (int value) { this.value = value; } [JavaCallable ("getString")] public Java.Lang.String GetString () { return new Java.Lang.String ($"Hello from C#, via Java.Interop! Value={value}"); } } Run this through `jcw-gen` and `jnimarshalmethod-gen`, run the app, and nothing worked (?!), because not all pieces were in agreement. Java `native` method registration is One Of Those Things™ that involves lots of moving pieces: * `generator` emits bindings for Java types, which includes Java method names, signatures, and (on .NET Android) the "connector method" to use: [Register ("toString", "()Ljava/lang/String;", "GetToStringHandler")] // .NET Android [JniMethodSignature ("toString", "()Ljava/lang/String;")] // Java.Base public override unsafe string? ToString () {…} * `jcw-gen` uses `generator` output, *prefixing* Java method names with `n_` for `native` method declarations, along with a method wrapper [^1] public String toString() {return n_toString();} private native String n_toString(); * `jnimarshalmethod-gen` emits marshal methods for Java.Base, and needs to register the `native` methods declared by `jcw-gen`. `jnimarshalmethod-gen` and `jcw-gen` need to be consistent with each other. * `MarshalMemberbuilder.CreateMarshalToManagedMethodRegistration()` creates a `JniNativeMethodRegistration` instance which contains the name of the Java `native` method to register, and was using a name inconsistent with `jcw-gen`. Turns Out, `jcw-gen`, `jnimarshalmethod-gen`, and `MarshalMemberBuilder` were *not* consistent. The only "real" `jnimarshalmethod-gen` usage (77800dd) is with the `Java.Interop.Export-Tests` unit test assembly, which *did not use* `jcw-gen`; it contained only hand-written Java code. Consequently, *none* of the Java `native` methods declared within it had an `n_` prefix, and since this worked with `jnimarshalmethod-gen`, this means that `jnimarshalmethod-gen` registration logic likewise didn't use `n_` prefixed method names. The result is that in the NativeAOT app, it would attempt to register the `native` Java method `ManagedType.getString()`, while what `jcw-gen` declared was `ManagedType.n_getString()`! Java promptly threw an exception, and the app crashed. Update `Java.Interop.Export-Tests` so that all the methods used with `MarshalMemberBuilder` are declared with `n_` prefixes, and add a `Java.Lang.Object` subclass example to the unit tests: Update `tests/Java.Interop.Tools.JavaCallableWrappers-Tests` to add a test for `.CodeGenerationTarget==JavaInterop1`. Add `$(NoWarn)` to `Java.Interop.Tools.JavaCallableWrappers-Tests.csproj` in order to "work around" warnings-as-errors: …/src/Java.Interop.NamingCustomAttributes/Java.Interop/ExportFieldAttribute.cs(19,63): error CA1019: Remove the property setter from Name or reduce its accessibility because it corresponds to positional argument name …/src/Java.Interop.NamingCustomAttributes/Android.Runtime/RegisterAttribute.cs(53,4): error CA1019: Remove the property setter from Name or reduce its accessibility because it corresponds to positional argument name …/src/Java.Interop.NamingCustomAttributes/Java.Interop/ExportFieldAttribute.cs(12,16): error CA1813: Avoid unsealed attributes … These are "weird"; the warnings/errors appear to come in because `Java.Interop.Tools.JavaCallableWrappers-Tests.csproj` now includes: <Compile Include="..\..\src\Java.Interop\Java.Interop\JniTypeSignatureAttribute.cs" /> which appears to pull in `src/Java.Interop/.editorconfig`, which makes CA1019 and CA1813 errors. (I do not understand what is happening.) Update `jnimarshalmethod-gen` so that the Java `native` methods it registers have an `n_` prefix. Refactor `ExpressionAssemblyBuilder.CreateRegistrationMethod()` to `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… Update `ExpressionAssemblyBuilder` so that the delegate types it creates for marshal method registration all have `[UnmanagedFunctionPointer(CallingConvention.Winapi)]`. (This isn't needed *here*, but is needed in the context of NativeAOT, as NativeAOT will only emit "marshal stubs" for delegate types which have `[UnmanagedFunctionPointer]`.) Unfortunately, adding `[UnmanagedFunctionPointer]` broke things: error JM4006: jnimarshalmethod-gen: Unable to process assembly '…/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) … The problem is that `CallingConvention` was resolved from `System.Private.CoreLib`, and when we removed that assembly reference, the `CallingConvention` couldn't be resolved at all. We could "fix" this by explicitly adding a reference to `System.Runtime.InteropServices.dll`, but how many more such corner cases exist? The current approach is not viable. Remove the code from 77800dd which attempts to remove `System.Private.CoreLib`. So long as `ExpressionAssemblyBuilder` output is *only* used in "completed" apps (not distributed in NuGet packages or some "intermediate" form), referencing `System.Private.CoreLib` is "fine". Update `jnimarshalmethod-gen` assembly location probing: in #1153, it was attempting to resolve the *full assembly name* of `Java.Base`, as `Java.Base, Version=7.0.0.0, Culture=neutral, PublicKeyToken=null`, causing it to attempt to load the file `Java.Base, Version=7.0.0.0, Culture=neutral, PublicKeyToken=null.dll`, which doesn't exist. Use `AssemblyName` to parse the string and extract out the assembly name, so that `Java.Base.dll` is probed for and found. Update `JreTypeManager` to *also* register the marshal methods generated by `Runtime.MarshalMemberBuilder.GetExportedMemberRegistrations()`. With all that, the updated `Java.Interop.Export-Tests` test now work both before and after `jnimarshalmethod-gen` is run: % dotnet test --logger "console;verbosity=detailed" bin/TestDebug-net7.0/Java.Interop.Export-Tests.dll && dotnet bin/Debug-net7.0/jnimarshalmethod-gen.dll bin/TestDebug-net7.0/Java.Interop.Export-Tests.dll && dotnet test --logger "console;verbosity=detailed" bin/TestDebug-net7.0/Java.Interop.Export-Tests.dll … TODO: * `generator --codegen-target=JavaInterop1` should emit JNI method signature information for constructors! This would likely remove the need for `[JavaCallableConstructor(SuperConstructorExpression="")`. * #1159 [0]: https://learn.microsoft.com/dotnet/core/deploying/native-aot [^0]: In a .NET Android environment, marshal methods are part of `generator` output, so things would be more straightforward there, though all the `_JniMarshal_*` types that are declared would also need to have `[UnmanagedFunctionPointer(CallingConvention.Winapi)]`… [^1]: Why not just declare `toString()` as `native`? Why have the separate `n_`-prefixed version? To make the `#if MONODROID_TIMING` block more consistent within `JavaCallableWrapperGenerator.GenerateMethod()`. It's likely "too late" to *easily* change this now.
1 parent f9a16d5 commit c93fea0

File tree

20 files changed

+351
-66
lines changed

20 files changed

+351
-66
lines changed

src/Java.Interop.Export/Java.Interop/JavaCallableAttribute.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#nullable enable
12
using System;
23

34
namespace Java.Interop {
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#nullable enable
2+
using System;
3+
4+
namespace Java.Interop {
5+
6+
[AttributeUsage (AttributeTargets.Constructor, AllowMultiple=false)]
7+
public sealed class JavaCallableConstructorAttribute : Attribute {
8+
9+
public JavaCallableConstructorAttribute ()
10+
{
11+
}
12+
13+
public string? SuperConstructorExpression {get; set;}
14+
public string? Signature {get; set;}
15+
}
16+
}

src/Java.Interop.Export/Java.Interop/MarshalMemberBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public JniNativeMethodRegistration CreateMarshalToManagedMethodRegistration (Jav
6868

6969
string GetJniMethodName (JavaCallableAttribute export, MethodInfo method)
7070
{
71-
return export.Name ?? "n_" + method.Name;
71+
return "n_" + method.Name;
7272
}
7373

7474
public string GetJniMethodSignature (JavaCallableAttribute export, MethodInfo method)

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

Lines changed: 14 additions & 23 deletions
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;
@@ -58,7 +59,7 @@ static MethodDefinition CreateMethodDefinition (AssemblyDefinition declaringAsse
5859
return mmDef;
5960
}
6061

61-
public MethodDefinition CreateRegistrationMethod (IList<ExpressionMethodRegistration> methods)
62+
public void AddRegistrationMethod (TypeDefinition declaringType, IList<ExpressionMethodRegistration> methods)
6263
{
6364
var registrations = new MethodDefinition (
6465
name: "__RegisterNativeMembers",
@@ -70,6 +71,8 @@ public MethodDefinition CreateRegistrationMethod (IList<ExpressionMethodRegistra
7071
},
7172
};
7273

74+
declaringType.Methods.Add (registrations);
75+
7376
var ctor = typeof (JniAddNativeMethodRegistrationAttribute).GetConstructor (Type.EmptyTypes);
7477
var attr = new CustomAttribute (DeclaringAssemblyDefinition.MainModule.ImportReference (ctor));
7578
registrations.CustomAttributes.Add (attr);
@@ -83,7 +86,7 @@ public MethodDefinition CreateRegistrationMethod (IList<ExpressionMethodRegistra
8386
registrations.Body.Variables.Add (array);
8487

8588
var il = registrations.Body.GetILProcessor ();
86-
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.");
8790
il.Emit (OpCodes.Ldc_I4, methods.Count);
8891
il.Emit (OpCodes.Newarr, DeclaringAssemblyDefinition.MainModule.ImportReference (arrayType.GetElementType ()));
8992
// il.Emit (OpCodes.Stloc_0);
@@ -116,9 +119,6 @@ public MethodDefinition CreateRegistrationMethod (IList<ExpressionMethodRegistra
116119
il.Emit (OpCodes.Ldloc_0);
117120
il.Emit (OpCodes.Call, DeclaringAssemblyDefinition.MainModule.ImportReference (addRegistrations.Method));
118121
il.Emit (OpCodes.Ret);
119-
120-
121-
return registrations;
122122
}
123123

124124
void EmitConsoleWriteLine (ILProcessor il, string message)
@@ -187,6 +187,15 @@ public TypeDefinition CreateMarshalMethodDelegateType (string delegateName, ILis
187187
);
188188
delegateDef.BaseType = DeclaringAssemblyDefinition.MainModule.ImportReference (typeof (MulticastDelegate));
189189

190+
var ufpCtor = typeof (UnmanagedFunctionPointerAttribute).GetConstructor (new[]{typeof (CallingConvention)});
191+
var ufpCtorRef = DeclaringAssemblyDefinition.MainModule.ImportReference (ufpCtor);
192+
var ufpAttr = new CustomAttribute (ufpCtorRef);
193+
ufpAttr.ConstructorArguments.Add (
194+
new CustomAttributeArgument (
195+
DeclaringAssemblyDefinition.MainModule.ImportReference (typeof (CallingConvention)),
196+
CallingConvention.Winapi));
197+
delegateDef.CustomAttributes.Add (ufpAttr);
198+
190199
var delegateCtor = new MethodDefinition (
191200
name: ".ctor",
192201
attributes: MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
@@ -237,22 +246,10 @@ public void Write (string path)
237246
};
238247
var newAsm = AssemblyDefinition.ReadAssembly (c, rp);
239248
module = newAsm.MainModule;
240-
var systemRuntimeRef = module.AssemblyReferences.FirstOrDefault (r => r.Name == "System.Runtime");
241-
var privateCorelibRef = module.AssemblyReferences.FirstOrDefault (r => r.Name == "System.Private.CoreLib");
242-
243-
if (systemRuntimeRef == null && privateCorelibRef != null) {
244-
systemRuntimeRef = GetSystemRuntimeReference ();
245-
module.AssemblyReferences.Add (systemRuntimeRef);
246-
}
247249

248250
var selfRef = module.AssemblyReferences.FirstOrDefault (r => r.Name == newAsm.Name.Name);
249251
foreach (var member in module.GetMemberReferences ()) {
250252
Logger (TraceLevel.Verbose, $"# jonp: looking at ref for member: [{member.DeclaringType.Scope?.Name}]{member}");
251-
if (member.DeclaringType.Scope == privateCorelibRef) {
252-
Logger (TraceLevel.Verbose, $"# jonp: Fixing scope ref for member: {member}");
253-
member.DeclaringType.Scope = systemRuntimeRef;
254-
continue;
255-
}
256253
if (member.DeclaringType.Scope == selfRef) {
257254
Logger (TraceLevel.Verbose, $"# jonp: Fixing scope self ref for member: {member}");
258255
member.DeclaringType.Scope = null;
@@ -261,18 +258,12 @@ public void Write (string path)
261258
}
262259
foreach (var type in module.GetTypeReferences ()) {
263260
Logger (TraceLevel.Verbose, $"# jonp: looking at ref for type: [{type.Scope}]{type}");
264-
if (type.Scope == privateCorelibRef) {
265-
Logger (TraceLevel.Verbose, $"# jonp: Fixing scope ref for type: {type}");
266-
type.Scope = systemRuntimeRef;
267-
continue;
268-
}
269261
if (type.Scope == selfRef) {
270262
Logger (TraceLevel.Verbose, $"# jonp: Fixing scope self ref for type: {type}");
271263
type.Scope = null;
272264
continue;
273265
}
274266
}
275-
module.AssemblyReferences.Remove (privateCorelibRef);
276267
if (selfRef != null) {
277268
module.AssemblyReferences.Remove (selfRef);
278269
}

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

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,13 @@ 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 ("Java.Interop.JavaCallableConstructorAttribute")) {
171+
AddMethod (null, minfo);
172+
HasExport = true;
173+
} else if (minfo.AnyCustomAttributes (typeof(ExportFieldAttribute))) {
168174
AddMethod (null, minfo);
169175
HasExport = true;
170176
} else if (minfo.AnyCustomAttributes (typeof (ExportAttribute))) {
@@ -412,6 +418,25 @@ ExportAttribute ToExportAttribute (CustomAttribute attr, IMemberDefinition decla
412418
return new ExportAttribute (name) {ThrownNames = thrown, SuperArgumentsString = superArgs};
413419
}
414420

421+
ExportAttribute ToExportAttributeFromJavaCallableAttribute (CustomAttribute attr, IMemberDefinition declaringMember)
422+
{
423+
var name = attr.ConstructorArguments.Count > 0
424+
? (string) attr.ConstructorArguments [0].Value
425+
: declaringMember.Name;
426+
return new ExportAttribute (name);
427+
}
428+
429+
ExportAttribute ToExportAttributeFromJavaCallableConstructorAttribute (CustomAttribute attr, IMemberDefinition declaringMember)
430+
{
431+
var superArgs = (string) attr.Properties
432+
.FirstOrDefault (p => p.Name == "SuperConstructorExpression")
433+
.Argument
434+
.Value;
435+
return new ExportAttribute (".ctor") {
436+
SuperArgumentsString = superArgs,
437+
};
438+
}
439+
415440
internal static ExportFieldAttribute ToExportFieldAttribute (CustomAttribute attr)
416441
{
417442
return new ExportFieldAttribute ((string) attr.ConstructorArguments [0].Value);
@@ -447,7 +472,11 @@ static IEnumerable<RegisterAttribute> GetMethodRegistrationAttributes (Mono.Ceci
447472

448473
IEnumerable<ExportAttribute> GetExportAttributes (IMemberDefinition p)
449474
{
450-
return GetAttributes<ExportAttribute> (p, a => ToExportAttribute (a, p));
475+
return GetAttributes<ExportAttribute> (p, a => ToExportAttribute (a, p))
476+
.Concat (GetAttributes<ExportAttribute> (p, "Java.Interop.JavaCallableAttribute",
477+
a => ToExportAttributeFromJavaCallableAttribute (a, p)))
478+
.Concat (GetAttributes<ExportAttribute> (p, "Java.Interop.JavaCallableConstructorAttribute",
479+
a => ToExportAttributeFromJavaCallableConstructorAttribute (a, p)));
451480
}
452481

453482
static IEnumerable<ExportFieldAttribute> GetExportFieldAttributes (Mono.Cecil.ICustomAttributeProvider p)
@@ -458,7 +487,13 @@ static IEnumerable<ExportFieldAttribute> GetExportFieldAttributes (Mono.Cecil.IC
458487
static IEnumerable<TAttribute> GetAttributes<TAttribute> (Mono.Cecil.ICustomAttributeProvider p, Func<CustomAttribute, TAttribute?> selector)
459488
where TAttribute : class
460489
{
461-
return p.GetCustomAttributes (typeof (TAttribute))
490+
return GetAttributes (p, typeof (TAttribute).FullName, selector);
491+
}
492+
493+
static IEnumerable<TAttribute> GetAttributes<TAttribute> (Mono.Cecil.ICustomAttributeProvider p, string attributeName, Func<CustomAttribute, TAttribute?> selector)
494+
where TAttribute : class
495+
{
496+
return p.GetCustomAttributes (attributeName)
462497
.Select (selector)
463498
.Where (v => v != null)
464499
.Select (v => v!);

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

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -412,11 +412,17 @@ protected bool TryRegisterNativeMembers (JniType nativeClass, Type type, string?
412412
bool TryLoadJniMarshalMethods (JniType nativeClass, Type type, string? methods)
413413
{
414414
var marshalType = type?.GetNestedType ("__<$>_jni_marshal_methods", BindingFlags.NonPublic);
415-
if (marshalType == null)
415+
if (marshalType == null) {
416416
return false;
417+
}
417418

418-
var registerMethod = marshalType.GetRuntimeMethod ("__RegisterNativeMembers", registerMethodParameters);
419-
419+
var registerMethod = marshalType.GetMethod (
420+
name: "__RegisterNativeMembers",
421+
bindingAttr: BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public,
422+
binder: null,
423+
callConvention: default,
424+
types: registerMethodParameters,
425+
modifiers: null);
420426
return TryRegisterNativeMembers (nativeClass, marshalType, methods, registerMethod);
421427
}
422428

@@ -466,8 +472,10 @@ bool FindAndCallRegisterMethod (Type marshalType, JniNativeMethodRegistrationArg
466472
continue;
467473
}
468474

475+
var declaringTypeName = methodInfo.DeclaringType?.FullName ?? "<no-decl-type>";
476+
469477
if ((methodInfo.Attributes & MethodAttributes.Static) != MethodAttributes.Static) {
470-
throw new InvalidOperationException ($"The method {methodInfo} marked with {nameof (JniAddNativeMethodRegistrationAttribute)} must be static");
478+
throw new InvalidOperationException ($"The method `{declaringTypeName}.{methodInfo}` marked with [{nameof (JniAddNativeMethodRegistrationAttribute)}] must be static!");
471479
}
472480

473481
var register = (Action<JniNativeMethodRegistrationArguments>)methodInfo.CreateDelegate (typeof (Action<JniNativeMethodRegistrationArguments>));

src/Java.Interop/Java.Interop/JniTypeSignatureAttribute.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ public sealed class JniTypeSignatureAttribute : Attribute {
1111

1212
public JniTypeSignatureAttribute (string simpleReference)
1313
{
14+
#if !JCW_ONLY_TYPE_NAMES
1415
JniRuntime.JniTypeManager.AssertSimpleReference (simpleReference, nameof (simpleReference));
16+
#endif // !JCW_ONLY_TYPE_NAMES
1517

1618
SimpleReference = simpleReference;
1719
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ static unsafe void RegisterNativeMembers (
216216

217217
}
218218
catch (Exception e) when (JniEnvironment.Runtime.ExceptionShouldTransitionToJni (e)) {
219-
Debug.WriteLine (e.ToString ());
219+
Debug.WriteLine ($"Exception when trying to register native methods with JNI: {e}");
220220
envp.SetPendingException (e);
221221
}
222222
finally {

src/Java.Runtime.Environment/Java.Interop/JreTypeManager.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ public override void RegisterNativeMembers (JniType nativeClass, Type type, Read
5252
registrations.Add (new JniNativeMethodRegistration (name, signature, marshaler!));
5353
}
5454

55+
foreach (var registration in Runtime.MarshalMemberBuilder.GetExportedMemberRegistrations (type)) {
56+
registrations.Add (registration);
57+
}
58+
5559
nativeClass.RegisterNativeMethods (registrations.ToArray ());
5660
}
5761

tests/Java.Interop.Export-Tests/Java.Interop.Export-Tests.csproj

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,16 @@
2222

2323
<ItemGroup>
2424
<ProjectReference Include="..\..\src\Java.Interop\Java.Interop.csproj" />
25+
<ProjectReference Include="..\..\src\Java.Base\Java.Base.csproj" />
2526
<ProjectReference Include="..\..\src\Java.Interop.Export\Java.Interop.Export.csproj" />
2627
<ProjectReference Include="..\..\src\Java.Runtime.Environment\Java.Runtime.Environment.csproj" />
2728
<ProjectReference Include="..\TestJVM\TestJVM.csproj" />
2829
</ItemGroup>
2930

3031
<ItemGroup>
31-
<JavaExportTestJar Include="$(MSBuildThisFileDirectory)java\com\xamarin\interop\export\ExportType.java" />
32+
<JavaExportTestJar Include="$(MSBuildThisFileDirectory)java\**\*.java" />
3233
</ItemGroup>
3334

34-
<Target Name="BuildExportTestJar" BeforeTargets="Build" Inputs="@(JavaExportTestJar)" Outputs="$(OutputPath)export-test.jar">
35-
<MakeDir Directories="$(IntermediateOutputPath)et-classes" />
36-
<Exec Command="&quot;$(JavaCPath)&quot; -classpath &quot;$(OutputPath)java-interop.jar&quot; $(_JavacSourceOptions) -d &quot;$(IntermediateOutputPath)et-classes&quot; @(JavaExportTestJar->'%(Identity)', ' ')" />
37-
<Exec Command="&quot;$(JarPath)&quot; cf &quot;$(OutputPath)export-test.jar&quot; -C &quot;$(IntermediateOutputPath)et-classes&quot; ." />
38-
</Target>
35+
<Import Project="Java.Interop.Export-Tests.targets" />
3936

4037
</Project>
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<Project>
2+
3+
<Target Name="_CreateJavaCallableWrappers"
4+
Condition=" '$(TargetPath)' != '' "
5+
BeforeTargets="BuildExportTestJar"
6+
Inputs="$(TargetPath)"
7+
Outputs="$(IntermediateOutputPath)java\.stamp">
8+
<RemoveDir Directories="$(IntermediateOutputPath)java" />
9+
<MakeDir Directories="$(IntermediateOutputPath)java" />
10+
<ItemGroup>
11+
<!-- I can't find a good way to trim the trailing `\`, so append with `.` so we can sanely quote for $(_Libpath) -->
12+
<_RefAsmDirs Include="@(ReferencePathWithRefAssemblies->'%(RootDir)%(Directory).'->Distinct())" />
13+
</ItemGroup>
14+
<PropertyGroup>
15+
<_JcwGen>"$(UtilityOutputFullPath)/jcw-gen.dll"</_JcwGen>
16+
<_Target>--codegen-target JavaInterop1</_Target>
17+
<_Output>-o "$(IntermediateOutputPath)/java"</_Output>
18+
<_Libpath>@(_RefAsmDirs->'-L "%(Identity)"', ' ')</_Libpath>
19+
</PropertyGroup>
20+
<Exec Command="$(DotnetToolPath) $(_JcwGen) &quot;$(TargetPath)&quot; $(_Target) $(_Output) $(_Libpath)" />
21+
<Touch Files="$(IntermediateOutputPath)java\.stamp" AlwaysCreate="True" />
22+
</Target>
23+
24+
<Target Name="BuildExportTestJar"
25+
AfterTargets="Build"
26+
Inputs="$(TargetPath);@(JavaExportTestJar)"
27+
Outputs="$(OutputPath)export-test.jar">
28+
<MakeDir Directories="$(IntermediateOutputPath)et-classes" />
29+
<ItemGroup>
30+
<_JcwSource Include="$(IntermediateOutputPath)java\**\*.java" />
31+
</ItemGroup>
32+
<ItemGroup>
33+
<_Source Include="@(_JcwSource->Replace('%5c', '/'))" />
34+
<_Source Include="@(JavaExportTestJar->Replace('%5c', '/'))" />
35+
</ItemGroup>
36+
<WriteLinesToFile
37+
File="$(IntermediateOutputPath)_java_sources.txt"
38+
Lines="@(_Source)"
39+
Overwrite="True"
40+
/>
41+
<ItemGroup>
42+
<_JavacOpt Include="$(_JavacSourceOptions)" />
43+
<_JavacOpt Include="-d &quot;$(IntermediateOutputPath)et-classes&quot; " />
44+
<_JavacOpt Include="-classpath &quot;$(OutputPath)java-interop.jar&quot; " />
45+
<_JavacOpt Include="&quot;@$(IntermediateOutputPath)_java_sources.txt&quot;" />
46+
<_JavacOpt Include="-h &quot;$(IntermediateOutputPath)et-classes&quot; " />
47+
</ItemGroup>
48+
<Exec Command="&quot;$(JavaCPath)&quot; @(_JavacOpt, ' ')" />
49+
<Delete Files="$(IntermediateOutputPath)_java_sources.txt" />
50+
<Exec Command="&quot;$(JarPath)&quot; cf &quot;$(OutputPath)export-test.jar&quot; -C &quot;$(IntermediateOutputPath)et-classes&quot; ." />
51+
</Target>
52+
53+
</Project>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using Java.Interop;
2+
3+
namespace Java.InteropTests;
4+
5+
[JniTypeSignature (TypeSignature)]
6+
class JavaCallableExample : Java.Lang.Object {
7+
8+
internal const string TypeSignature = "net/dot/jni/test/JavaCallableExample";
9+
10+
[JavaCallableConstructor(SuperConstructorExpression="")]
11+
public JavaCallableExample (int a)
12+
{
13+
this.a = a;
14+
}
15+
16+
int a;
17+
18+
[JavaCallable ("getA")]
19+
public int GetA ()
20+
{
21+
return a;
22+
}
23+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using Java.Interop;
2+
3+
using NUnit.Framework;
4+
5+
namespace Java.InteropTests;
6+
7+
[TestFixture]
8+
class JavaCallableExampleTest : JavaVMFixture
9+
{
10+
[Test]
11+
public void JavaCallableWrappers ()
12+
{
13+
using var jce = CreateUseJavaCallableExampleType ();
14+
var m = jce.GetStaticMethod ("test", "()Z");
15+
var z = JniEnvironment.StaticMethods.CallStaticBooleanMethod (jce.PeerReference, m);
16+
Assert.IsTrue (z);
17+
}
18+
19+
static JniType CreateUseJavaCallableExampleType () =>
20+
new JniType ("net/dot/jni/test/UseJavaCallableExample");
21+
}

0 commit comments

Comments
 (0)