Skip to content

Commit 89a5a22

Browse files
authored
[Java.Interop-Tests] Initial dotnet test support (#801)
Context: dotnet/android#5592 With introductory support for `Java.Interop.dll` on .NET Core via commit 2a299eb, what about running the unit tests within `Java.Interop-Tests.dll` on .NET Core? Update `src/java-interop` so that the `java-interop` native library is copied to `$(OutputPath)` of referencing projects. This allows e.g. `libjava-interop.dylib` to be implicitly copied into the appropriate output dir, e.g. `bin/TestDebug-netcoreapp3.1`. Further update `src/java-interop` so that `$(IntermediateOutputPath)` correctly differs between net472 and netcoreapp3.1 builds, and generate `jni.c` into `$(IntermediateOutputPath)`. This ensures that e.g. `bin/Debug/libjava-interop.dylib` reliably uses Mono on net472, instead of occasionally/accidentally being the netstandard version. Update `tests/Java.Interop-Tests` and `tests/TestJVM/TestJVM.csproj` to multitarget between net472 and netcoreapp3.1. Update `Java.Runtime.Environment.csproj` to reference `java-interop.csproj`, which causes all projects which reference `Java.Runtime.Environment.csproj` to get the `java-interop` native library copied to the appropriate `$(OutputPath)`. Update `JreRuntime` to use "dummy" `JniRuntime.JniValueManager` and `JniRuntime.JniObjectReferenceManager` subclasses when *not* running under Mono. This avoids an `ArgumentException` when creating the `Java.InteropTests.JavaVMFixture` instance used by the unit tests. Update `tests/TestJVM` to reference `Xamarin.Android.Tools.AndroidSdk.csproj`, allowing the `TestJVM` type to use `JdkInfo` to find a JVM to use. This allows us to avoid a requirement to set the `JI_JVM_PATH` environment variable in order to create `TestJVM` instances. Update `src/Java.Interop` so that generic delegate types are not marshaled via P/Invoke. .NET Core doesn't support this. Update `JniEnvironment.Types.FindClass()` to call `info.Runtime.GetExceptionForThrowable()` as soon as possible after calling `JNIEnv::ExceptionClear()`, instead of potentially after calling `ClassLoader.loadClass()`. The problem is Java-side stack- frames: if `info.Runtime.GetExceptionForThrowable()` is delayed, then the exception created by `info.Runtime.GetExceptionForThrowable()` only contains the Java message text, but no stack trace, and no "cause" information. This meant that when generic delegate types were still being used, `dotnet test` failures would show: java.lang.NoClassDefFoundError: Could not initialize class com.xamarin.interop.CallVirtualFromConstructorDerive with *no* additional information about *why* it failed. (It failed because P/Invoke was trying to marshal a generic delegate, so the `ManagedPeer` static constructor threw an exception, which was "within" a `static{ManagedPeer.registerNativeMembers(…)}` Java block, and thus the Java type initializer failed, so nothing worked.) Update `**/*.java` to remove Version, Culture, and PublicKeyToken information from the assembly qualified name, for two reasons: 1. The default Version value appears to differ between net472 (0.0.0.0) and netcoreapp3.1 (1.0.0.0). 2. Wrt dotnet/android#5592, certain "security products" believe that the Version number is an *IP address*, and emit a warning about "IP Address disclosure". Since we don't strong-name any of these assemblies (unit tests!) *or* the expected execution environment is a "self-contained app" in which you can't have multiple versions of the same assembly (Xamarin.Android), remove the Version, Culture, and PublicKeyToken information from these Java Callable Wrappers.
1 parent a4a2c13 commit 89a5a22

File tree

25 files changed

+166
-110
lines changed

25 files changed

+166
-110
lines changed

build-tools/scripts/RunNUnitTests.targets

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,6 @@
1919
</ItemGroup>
2020
<Target Name="RunTests"
2121
Outputs="$(_TopDir)\TestResult-%(_TestAssembly.Filename).xml">
22-
<ItemGroup>
23-
<_JavaInteropNativeLibrary Include="$(_TopDir)\bin\$(Configuration)\libjava-interop.*" />
24-
</ItemGroup>
25-
<Copy
26-
SourceFiles="@(_JavaInteropNativeLibrary)"
27-
DestinationFolder="$(_TopDir)\bin\Test$(Configuration)"
28-
SkipUnchangedFiles="True"
29-
/>
3022
<SetEnvironmentVariable Name="ANDROID_SDK_PATH" Value="$(AndroidSdkDirectory)" Condition=" '$(AndroidSdkDirectory)' != '' " />
3123
<SetEnvironmentVariable Name="MONO_TRACE_LISTENER" Value="Console.Out" />
3224
<SetEnvironmentVariable Name="JAVA_INTEROP_GREF_LOG" Value="bin\Test$(Configuration)\g-%(_TestAssembly.Filename).txt" />

src/Java.Interop/Java.Interop/JavaProxyObject.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ sealed class JavaProxyObject : JavaObject, IEquatable<JavaProxyObject>
1717
[JniAddNativeMethodRegistrationAttribute]
1818
static void RegisterNativeMembers (JniNativeMethodRegistrationArguments args)
1919
{
20-
args.Registrations.Add (new JniNativeMethodRegistration ("equals", "(Ljava/lang/Object;)Z", (Func<IntPtr, IntPtr, IntPtr, bool>)Equals));
21-
args.Registrations.Add (new JniNativeMethodRegistration ("hashCode", "()I", (Func<IntPtr, IntPtr, int>)GetHashCode));
22-
args.Registrations.Add (new JniNativeMethodRegistration ("toString", "()Ljava/lang/String;", (Func<IntPtr, IntPtr, IntPtr>)ToString));
20+
args.Registrations.Add (new JniNativeMethodRegistration ("equals", "(Ljava/lang/Object;)Z", (EqualsMarshalMethod)Equals));
21+
args.Registrations.Add (new JniNativeMethodRegistration ("hashCode", "()I", (GetHashCodeMarshalMethod)GetHashCode));
22+
args.Registrations.Add (new JniNativeMethodRegistration ("toString", "()Ljava/lang/String;", (ToStringMarshalMethod)ToString));
2323
}
2424

2525
public override JniPeerMembers JniPeerMembers {
@@ -72,6 +72,7 @@ public override bool Equals (object? obj)
7272
}
7373

7474
// TODO: Keep in sync with the code generated by ExportedMemberBuilder
75+
delegate bool EqualsMarshalMethod (IntPtr jnienv, IntPtr n_self, IntPtr n_value);
7576
static bool Equals (IntPtr jnienv, IntPtr n_self, IntPtr n_value)
7677
{
7778
var envp = new JniTransition (jnienv);
@@ -91,6 +92,7 @@ static bool Equals (IntPtr jnienv, IntPtr n_self, IntPtr n_value)
9192
}
9293

9394
// TODO: Keep in sync with the code generated by ExportedMemberBuilder
95+
delegate int GetHashCodeMarshalMethod (IntPtr jnienv, IntPtr n_self);
9496
static int GetHashCode (IntPtr jnienv, IntPtr n_self)
9597
{
9698
var envp = new JniTransition (jnienv);
@@ -107,6 +109,7 @@ static int GetHashCode (IntPtr jnienv, IntPtr n_self)
107109
}
108110
}
109111

112+
delegate IntPtr ToStringMarshalMethod (IntPtr jnienv, IntPtr n_self);
110113
static IntPtr ToString (IntPtr jnienv, IntPtr n_self)
111114
{
112115
var envp = new JniTransition (jnienv);

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

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -48,29 +48,37 @@ public static unsafe JniObjectReference FindClass (string classname)
4848
}
4949

5050
NativeMethods.java_interop_jnienv_exception_clear (info.EnvironmentPointer);
51-
var e = new JniObjectReference (thrown, JniObjectReferenceType.Local);
52-
LogCreateLocalRef (e);
51+
52+
var findClassThrown = new JniObjectReference (thrown, JniObjectReferenceType.Local);
53+
LogCreateLocalRef (findClassThrown);
54+
var pendingException = info.Runtime.GetExceptionForThrowable (ref findClassThrown, JniObjectReferenceOptions.CopyAndDispose);
5355

5456
if (info.Runtime.ClassLoader_LoadClass != null) {
5557
var java = info.ToJavaName (classname);
5658
var __args = stackalloc JniArgumentValue [1];
5759
__args [0] = new JniArgumentValue (java);
5860

59-
IntPtr ignoreThrown;
60-
c = NativeMethods.java_interop_jnienv_call_object_method_a (info.EnvironmentPointer, out ignoreThrown, info.Runtime.ClassLoader.Handle, info.Runtime.ClassLoader_LoadClass.ID, (IntPtr) __args);
61+
c = NativeMethods.java_interop_jnienv_call_object_method_a (info.EnvironmentPointer, out thrown, info.Runtime.ClassLoader.Handle, info.Runtime.ClassLoader_LoadClass.ID, (IntPtr) __args);
6162
JniObjectReference.Dispose (ref java);
62-
if (ignoreThrown == IntPtr.Zero) {
63-
JniObjectReference.Dispose (ref e);
63+
if (thrown == IntPtr.Zero) {
64+
(pendingException as IJavaPeerable)?.Dispose ();
6465
var r = new JniObjectReference (c, JniObjectReferenceType.Local);
6566
JniEnvironment.LogCreateLocalRef (r);
6667
return r;
6768
}
6869
NativeMethods.java_interop_jnienv_exception_clear (info.EnvironmentPointer);
69-
NativeMethods.java_interop_jnienv_delete_local_ref (info.EnvironmentPointer, ignoreThrown);
7070

71+
if (pendingException != null) {
72+
NativeMethods.java_interop_jnienv_delete_local_ref (info.EnvironmentPointer, thrown);
73+
}
74+
else {
75+
var loadClassThrown = new JniObjectReference (thrown, JniObjectReferenceType.Local);
76+
LogCreateLocalRef (loadClassThrown);
77+
pendingException = info.Runtime.GetExceptionForThrowable (ref loadClassThrown, JniObjectReferenceOptions.CopyAndDispose);
78+
}
7179
}
7280

73-
throw info.Runtime.GetExceptionForThrowable (ref e, JniObjectReferenceOptions.CopyAndDispose)!;
81+
throw pendingException!;
7482
#endif // !FEATURE_JNIENVIRONMENT_JI_PINVOKES
7583
#if FEATURE_JNIOBJECTREFERENCE_SAFEHANDLES
7684
var c = info.Invoker.FindClass (info.EnvironmentPointer, classname);
@@ -80,25 +88,32 @@ public static unsafe JniObjectReference FindClass (string classname)
8088
return new JniObjectReference (c, JniObjectReferenceType.Local);
8189
}
8290
info.Invoker.ExceptionClear (info.EnvironmentPointer);
83-
LogCreateLocalRef (thrown);
91+
var findClassThrown = new JniObjectReference (thrown, JniObjectReferenceType.Local);
92+
LogCreateLocalRef (findClassThrown);
93+
var pendingException = info.Runtime.GetExceptionForThrowable (ref findClassThrown, JniObjectReferenceOptions.CopyAndDispose);
8494

8595
var java = info.ToJavaName (classname);
8696
var __args = stackalloc JniArgumentValue [1];
8797
__args [0] = new JniArgumentValue (java);
8898

89-
c = info.Invoker.CallObjectMethodA (info.EnvironmentPointer, info.Runtime.ClassLoader.SafeHandle, info.Runtime.ClassLoader_LoadClass.ID, __args);
99+
c = info.Invoker.CallObjectMethodA (info.EnvironmentPointer, info.Runtime.ClassLoader.SafeHandle, info.Runtime.ClassLoader_LoadClass.ID, __args);
90100
JniObjectReference.Dispose (ref java);
91-
var ignoreThrown = info.Invoker.ExceptionOccurred (info.EnvironmentPointer);
101+
thrown = info.Invoker.ExceptionOccurred (info.EnvironmentPointer);
92102
if (ignoreThrown.IsInvalid) {
93-
thrown.Dispose ();
94-
JniEnvironment.LogCreateLocalRef (c);
95-
return new JniObjectReference (c, JniObjectReferenceType.Local);
103+
(pendingException as IJavaPeerable)?.Dispose ();
104+
var r = new JniObjectReference (c, JniObjectReferenceType.Local);
105+
JniEnvironment.LogCreateLocalRef (r);
106+
return r;
96107
}
97108
info.Invoker.ExceptionClear (info.EnvironmentPointer);
98-
LogCreateLocalRef (ignoreThrown);
99-
ignoreThrown.Dispose ();
100-
var e = new JniObjectReference (thrown, JniObjectReferenceType.Local);
101-
throw info.Runtime.GetExceptionForThrowable (ref e, JniObjectReferenceOptions.CopyAndDispose);
109+
if (pendingException != null) {
110+
thrown.Dispose ();
111+
throw pendingException;
112+
}
113+
var loadClassThrown = new JniObjectReference (thrown, JniObjectReferenceType.Local);
114+
LogCreateLocalRef (loadClassThrown);
115+
pendingException = info.Runtime.GetExceptionForThrowable (ref loadClassThrown, JniObjectReferenceOptions.CopyAndDispose);
116+
throw pendingException!;
102117
#endif // !FEATURE_JNIOBJECTREFERENCE_SAFEHANDLES
103118
}
104119

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

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,6 @@ namespace Java.Interop {
1414
[JniTypeSignature (JniTypeName)]
1515
/* static */ sealed class ManagedPeer : JavaObject {
1616

17-
delegate void ConstructDelegate (IntPtr jnienv,
18-
IntPtr klass,
19-
IntPtr n_self,
20-
IntPtr n_assemblyQualifiedName,
21-
IntPtr n_constructorSignature,
22-
IntPtr n_constructorArguments);
23-
delegate void RegisterDelegate (IntPtr jnienv,
24-
IntPtr klass,
25-
IntPtr n_nativeClass,
26-
IntPtr n_assemblyQualifiedName,
27-
IntPtr n_methods);
28-
2917
internal const string JniTypeName = "com/xamarin/java_interop/ManagedPeer";
3018

3119

@@ -37,11 +25,11 @@ static ManagedPeer ()
3725
new JniNativeMethodRegistration (
3826
"construct",
3927
ConstructSignature,
40-
(ConstructDelegate) Construct),
28+
(ConstructMarshalMethod) Construct),
4129
new JniNativeMethodRegistration (
4230
"registerNativeMembers",
4331
RegisterNativeMembersSignature,
44-
(RegisterDelegate) RegisterNativeMembers)
32+
(RegisterMarshalMethod) RegisterNativeMembers)
4533
);
4634
}
4735

@@ -62,6 +50,12 @@ public override JniPeerMembers JniPeerMembers {
6250
const string ConstructSignature = "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)V";
6351

6452
// TODO: Keep in sync with the code generated by ExportedMemberBuilder
53+
delegate void ConstructMarshalMethod (IntPtr jnienv,
54+
IntPtr klass,
55+
IntPtr n_self,
56+
IntPtr n_assemblyQualifiedName,
57+
IntPtr n_constructorSignature,
58+
IntPtr n_constructorArguments);
6559
static void Construct (
6660
IntPtr jnienv,
6761
IntPtr klass,
@@ -183,6 +177,11 @@ static Type[] GetParameterTypes (string? signature)
183177

184178
const string RegisterNativeMembersSignature = "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;)V";
185179

180+
delegate void RegisterMarshalMethod (IntPtr jnienv,
181+
IntPtr klass,
182+
IntPtr n_nativeClass,
183+
IntPtr n_assemblyQualifiedName,
184+
IntPtr n_methods);
186185
static void RegisterNativeMembers (
187186
IntPtr jnienv,
188187
IntPtr klass,

src/Java.Interop/java/com/xamarin/java_interop/internal/JavaProxyObject.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
extends java.lang.Object
99
implements GCUserPeerable
1010
{
11-
static final String assemblyQualifiedName = "Java.Interop.JavaProxyObject, Java.Interop, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null";
11+
static final String assemblyQualifiedName = "Java.Interop.JavaProxyObject, Java.Interop";
1212
static {
1313
com.xamarin.java_interop.ManagedPeer.registerNativeMembers (
1414
JavaProxyObject.class,

src/Java.Interop/java/com/xamarin/java_interop/internal/JavaProxyThrowable.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
extends java.lang.Error
99
implements GCUserPeerable
1010
{
11-
static final String assemblyQualifiedName = "Java.Interop.JavaProxyThrowable, Java.Interop, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null";
11+
static final String assemblyQualifiedName = "Java.Interop.JavaProxyThrowable, Java.Interop";
1212
static {
1313
com.xamarin.java_interop.ManagedPeer.registerNativeMembers (
1414
JavaProxyThrowable.class,

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

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Diagnostics;
55
using System.IO;
66
using System.Linq;
7+
using System.Reflection;
78
using System.Runtime.InteropServices;
89
using System.Threading;
910

@@ -44,6 +45,10 @@ public JreRuntimeOptions ()
4445
ValueManager = ValueManager ?? new MonoRuntimeValueManager ();
4546
ObjectReferenceManager = ObjectReferenceManager ?? new MonoRuntimeObjectReferenceManager ();
4647
}
48+
else {
49+
ValueManager = ValueManager ?? new DummyValueManager ();
50+
ObjectReferenceManager = ObjectReferenceManager ?? new DummyObjectReferenceManager ();
51+
}
4752
}
4853

4954
public JreRuntimeOptions AddOption (string option)
@@ -161,5 +166,52 @@ partial class NativeMethods {
161166
[DllImport (JavaInteropLib, CharSet=CharSet.Ansi, CallingConvention=CallingConvention.Cdecl)]
162167
internal static extern int java_interop_jvm_create (out IntPtr javavm, out IntPtr jnienv, ref JavaVMInitArgs args);
163168
}
169+
170+
class DummyValueManager : JniRuntime.JniValueManager {
171+
172+
public override void WaitForGCBridgeProcessing ()
173+
{
174+
}
175+
176+
public override void CollectPeers ()
177+
{
178+
}
179+
180+
public override void AddPeer (IJavaPeerable reference)
181+
{
182+
}
183+
184+
public override void RemovePeer (IJavaPeerable reference)
185+
{
186+
}
187+
188+
public override void FinalizePeer (IJavaPeerable reference)
189+
{
190+
}
191+
192+
public override List<JniSurfacedPeerInfo> GetSurfacedPeers ()
193+
{
194+
return null;
195+
}
196+
197+
public override IJavaPeerable PeekPeer (global::Java.Interop.JniObjectReference reference)
198+
{
199+
return null;
200+
}
201+
202+
public override void ActivatePeer (IJavaPeerable self, JniObjectReference reference, ConstructorInfo cinfo, object [] argumentValues)
203+
{
204+
}
205+
}
206+
207+
class DummyObjectReferenceManager : JniRuntime.JniObjectReferenceManager {
208+
public override int GlobalReferenceCount {
209+
get {return 0;}
210+
}
211+
212+
public override int WeakGlobalReferenceCount {
213+
get {return 0;}
214+
}
215+
}
164216
}
165217

src/Java.Runtime.Environment/Java.Runtime.Environment.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
<ItemGroup>
1515
<ProjectReference Include="..\Java.Interop\Java.Interop.csproj" />
16+
<ProjectReference Include="..\..\src\java-interop\java-interop.csproj" ReferenceOutputAssembly="false" />
1617
</ItemGroup>
1718

1819
<ItemGroup>

src/java-interop/java-interop.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
</ItemDefinitionGroup>
4040

4141
<ItemGroup>
42-
<ClCompile Include="jni.c" />
42+
<ClCompile Include="$(IntermediateOutputPath)jni.c" />
4343
<ClCompile Include="java-interop.cc" />
4444
<ClCompile Include="java-interop-dlfcn.cc" />
4545
<ClCompile Include="java-interop-jvm.cc" />

src/java-interop/java-interop.targets

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,33 @@
22
<Project>
33
<Target Name="BuildJni_c"
44
Inputs="$(_JNIEnvGenPath)"
5-
Outputs="jni.c">
5+
Outputs="$(IntermediateOutputPath)jni.c">
66
<MakeDir Directories="$(OutputPath)" />
7-
<Exec Command="$(_RunJNIEnvGen) jni.g.cs jni.c" />
7+
<Exec Command="$(_RunJNIEnvGen) $(IntermediateOutputPath)jni.g.cs $(IntermediateOutputPath)jni.c" />
88
</Target>
99

1010
<PropertyGroup>
1111
<_MacLib>$(OutputPath)/lib$(OutputName).dylib</_MacLib>
1212
<_UnixLib>$(OutputPath)/lib$(OutputName).so</_UnixLib>
1313
</PropertyGroup>
1414

15+
<ItemGroup Condition=" '$(OS)' != 'Windows_NT' And Exists ('/Library/Frameworks/') ">
16+
<None Include="$(_MacLib)">
17+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
18+
</None>
19+
</ItemGroup>
20+
21+
<ItemGroup Condition=" '$(OS)' != 'Windows_NT' And !Exists ('/Library/Frameworks/') ">
22+
<None Include="$(_UnixLib)">
23+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
24+
</None>
25+
</ItemGroup>
26+
1527
<ItemDefinitionGroup>
1628
<ClCompile>
1729
<PreprocessorDefinitions>$([MSBuild]::Unescape($(DefineSymbols.Replace(' ', ';'))))</PreprocessorDefinitions>
1830
<AdditionalIncludeDirectories>$([MSBuild]::Unescape($(_MonoIncludePath)));$([MSBuild]::Unescape($(_JdkIncludePath)))</AdditionalIncludeDirectories>
19-
<Obj Condition=" '$(OS)' != 'Windows_NT' ">obj/$(Configuration)/%(Filename).o</Obj>
31+
<Obj Condition=" '$(OS)' != 'Windows_NT' ">$(IntermediateOutputPath)/%(Filename).o</Obj>
2032
</ClCompile>
2133
</ItemDefinitionGroup>
2234

@@ -25,7 +37,7 @@
2537
DependsOnTargets="BuildJni_c"
2638
Inputs="@(ClCompile);@(ClInclude)"
2739
Outputs="%(ClCompile.Obj)">
28-
<MakeDir Directories="obj\$(Configuration)" />
40+
<MakeDir Directories="$(IntermediateOutputPath)" />
2941
<ItemGroup>
3042
<_Cl Include="@(ClCompile)">
3143
<Compiler Condition=" '%(Extension)' == '.c' ">gcc -std=c99 -fPIC</Compiler>
@@ -86,7 +98,6 @@
8698

8799
<Target Name="Clean">
88100
<RemoveDir Directories="obj" />
89-
<Delete Files="jni.c" />
90101
<Delete
91102
Files="$(_MacLib);$(_UnixLib)"
92103
Condition=" '$(OS)' != 'Windows_NT' "

0 commit comments

Comments
 (0)