Skip to content

Commit 203b18a

Browse files
committed
[Java.Runtime.Environment] Partial support for .NET Core
Enable C#8 [Nullable Reference Types][0] for `Java.Runtime.Environment.dll`. Add partial support for a "non-bridged backend", so that a `JniRuntime.JniValueManager` exists for .NET Core. This new "managed" backed is used if the Mono runtime is *not* used. To work, `ManagedValueManager` holds *strong* references to `IJavaPeerable` instances. As such, tests which required the use of GC integration are now "optional", conditional on the `!NO_GC_BRIDGE_SUPPORT` define. The `ManagedValueManager.CollectPeers()` method calls `IJavaPeerable.Dispose()` on all currently referenced peers, then stops referencing the managed peers. This causes all GREFs to be dropped, allowing Java peers to be collected, and then allows the .NET GC to collect the `IJavaPeerable` values. Any and all exceptions thrown by `IJavaPeerable.Dispose()` are caught and re-thrown by an `AggregateException`. Update `Java.Interop-Tests.csproj` to define `NO_GC_BRIDGE_SUPPORT` and `NO_MARSHAL_MEMBER_BUILDER_SUPPORT` when building for .NET Core. This excludes all currently "troublesome"/non-passing tests. These changes allow all remaining `Java.Interop-Tests` unit tests to execute under .NET Core: % dotnet test -v diag '--logger:trx;verbosity=detailed' bin/TestDebug-netcoreapp3.1/Java.Interop-Tests.dll Passed! - Failed: 0, Passed: 617, Skipped: 1, Total: 618, Duration: 1 s Other changes: * The attempt to retain useful Java-side exceptions in 89a5a22 proved to be incomplete. Add a comment to invoke [`JNIEnv::ExceptionDescribe()`][1]. We don't always want this to be present, but when we do want it… * While `NO_MARSHAL_MEMBER_BUILDER_SUPPORT` is set -- which means that `Java.Interop.Export`-related tests aren't run -- there are some fixes for `Java.Interop.Export` & related unit tests for .NET Core, to avoid the use of generic delegate types and to avoid a `Type.GetType()` which is no longer needed. [0]: https://docs.microsoft.com/dotnet/csharp/nullable-references [1]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#ExceptionDescribe
1 parent bba1f07 commit 203b18a

File tree

17 files changed

+586
-84
lines changed

17 files changed

+586
-84
lines changed

build-tools/automation/azure-pipelines.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ variables:
2121
DotNetCoreVersion: 3.1.300
2222
HostedMacImage: macOS-10.15
2323
HostedWinVS2019: Hosted Windows 2019 with VS2019
24+
NetCoreTargetFrameworkPathSuffix: -netcoreapp3.1
2425

2526
jobs:
2627
- job: windows_build

build-tools/automation/templates/core-tests.yaml

+8
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,14 @@ steps:
6767
arguments: bin/Test$(Build.Configuration)/Java.Interop-Tests.dll
6868
continueOnError: true
6969

70+
- task: DotNetCoreCLI@2
71+
displayName: 'Tests: Java.Interop'
72+
condition: eq('${{ parameters.runNativeTests }}', 'true')
73+
inputs:
74+
command: test
75+
arguments: bin/Test$(Build.Configuration)$(NetCoreTargetFrameworkPathSuffix)/Java.Interop-Tests.dll
76+
continueOnError: true
77+
7078
- task: DotNetCoreCLI@2
7179
displayName: 'Tests: Java.Interop.Dynamic'
7280
condition: eq('${{ parameters.runNativeTests }}', 'true')

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

+6-1
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,12 @@ static Expression GetRuntime ()
370370
return Expression.Property (null, typeof (JniEnvironment), "Runtime");
371371
}
372372

373-
static MethodInfo FormatterServices_GetUninitializedObject = Type.GetType ("System.Runtime.Serialization.FormatterServices", throwOnError: true)
373+
static MethodInfo FormatterServices_GetUninitializedObject =
374+
#if NETFRAMEWORK || NET_2_0
375+
typeof (System.Runtime.Serialization.FormatterServices)
376+
#else // !(NETFRAMEWORK || NET_2_0)
377+
typeof (System.Runtime.CompilerServices.RuntimeHelpers)
378+
#endif // NETFRAMEWORK || NET_2_0
374379
.GetRuntimeMethod ("GetUninitializedObject", new[]{typeof (Type)});
375380
static MethodInfo IJavaPeerable_SetPeerReference = typeof (IJavaPeerable).GetRuntimeMethod ("SetPeerReference", new[]{typeof (JniObjectReference)});
376381

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

+18
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#nullable enable
22

33
using System;
4+
using System.Diagnostics;
45
using System.Collections.Generic;
56
using System.Text;
67

@@ -47,6 +48,12 @@ public static unsafe JniObjectReference FindClass (string classname)
4748
return r;
4849
}
4950

51+
// If the Java-side exception stack trace is *lost* a'la 89a5a229,
52+
// change `false` to `true` and rebuild+re-run.
53+
#if false
54+
NativeMethods.java_interop_jnienv_exception_describe (info.EnvironmentPointer);
55+
#endif
56+
5057
NativeMethods.java_interop_jnienv_exception_clear (info.EnvironmentPointer);
5158

5259
var findClassThrown = new JniObjectReference (thrown, JniObjectReferenceType.Local);
@@ -167,6 +174,17 @@ public static void RegisterNatives (JniObjectReference type, JniNativeMethodRegi
167174

168175
public static void RegisterNatives (JniObjectReference type, JniNativeMethodRegistration [] methods, int numMethods)
169176
{
177+
#if DEBUG && NETCOREAPP
178+
foreach (var m in methods) {
179+
if (m.Marshaler.GetType ().GenericTypeArguments.Length != 0) {
180+
var method = m.Marshaler.Method;
181+
Debug.WriteLine ($"JNIEnv::RegisterNatives() given a generic delegate type. .NET Core doesn't like this.");
182+
Debug.WriteLine ($" Java: {m.Name}{m.Signature}");
183+
Debug.WriteLine ($" Marshaler Type={m.Marshaler.GetType ().FullName} Method={method.DeclaringType.FullName}.{method.Name}");
184+
}
185+
}
186+
#endif // DEBUG && NETCOREAPP
187+
170188
int r = _RegisterNatives (type, methods, numMethods);
171189

172190
if (r != 0) {

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

+14-58
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ public class JreRuntimeOptions : JniRuntime.CreationOptions {
3131

3232
public Collection<string> ClassPath {get; private set;}
3333

34+
public TextWriter? JniGlobalReferenceLogWriter {get; set;}
35+
public TextWriter? JniLocalReferenceLogWriter {get; set;}
36+
3437
public JreRuntimeOptions ()
3538
{
3639
JniVersion = JniVersion.v1_2;
@@ -39,16 +42,6 @@ public JreRuntimeOptions ()
3942
Path.GetDirectoryName (typeof (JreRuntimeOptions).Assembly.Location),
4043
"java-interop.jar"),
4144
};
42-
43-
bool onMono = Type.GetType ("Mono.Runtime", throwOnError: false) != null;
44-
if (onMono) {
45-
ValueManager = ValueManager ?? new MonoRuntimeValueManager ();
46-
ObjectReferenceManager = ObjectReferenceManager ?? new MonoRuntimeObjectReferenceManager ();
47-
}
48-
else {
49-
ValueManager = ValueManager ?? new DummyValueManager ();
50-
ObjectReferenceManager = ObjectReferenceManager ?? new DummyObjectReferenceManager ();
51-
}
5245
}
5346

5447
public JreRuntimeOptions AddOption (string option)
@@ -87,12 +80,22 @@ static unsafe JreRuntimeOptions CreateJreVM (JreRuntimeOptions builder)
8780
if (builder == null)
8881
throw new ArgumentNullException ("builder");
8982

83+
bool onMono = Type.GetType ("Mono.Runtime", throwOnError: false) != null;
84+
if (onMono) {
85+
builder.ValueManager = builder.ValueManager ?? new MonoRuntimeValueManager ();
86+
builder.ObjectReferenceManager = builder.ObjectReferenceManager ?? new MonoRuntimeObjectReferenceManager ();
87+
}
88+
else {
89+
builder.ValueManager = builder.ValueManager ?? new ManagedValueManager ();
90+
builder.ObjectReferenceManager = builder.ObjectReferenceManager ?? new ManagedObjectReferenceManager (builder.JniGlobalReferenceLogWriter, builder.JniLocalReferenceLogWriter);
91+
}
92+
9093
if (builder.InvocationPointer != IntPtr.Zero)
9194
return builder;
9295

9396
if (!string.IsNullOrEmpty (builder.JvmLibraryPath)) {
9497
IntPtr errorPtr = IntPtr.Zero;
95-
int r = NativeMethods.java_interop_jvm_load_with_error_message (builder.JvmLibraryPath, out errorPtr);
98+
int r = NativeMethods.java_interop_jvm_load_with_error_message (builder.JvmLibraryPath!, out errorPtr);
9699
if (r != 0) {
97100
string error = Marshal.PtrToStringAnsi (errorPtr);
98101
NativeMethods.java_interop_free (errorPtr);
@@ -166,52 +169,5 @@ partial class NativeMethods {
166169
[DllImport (JavaInteropLib, CharSet=CharSet.Ansi, CallingConvention=CallingConvention.Cdecl)]
167170
internal static extern int java_interop_jvm_create (out IntPtr javavm, out IntPtr jnienv, ref JavaVMInitArgs args);
168171
}
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-
}
216172
}
217173

0 commit comments

Comments
 (0)