Skip to content

Commit

Permalink
[Java.Runtime.Environment] Partial support for .NET Core (#804)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
jonpryor committed Feb 23, 2021
1 parent bba1f07 commit a666a6f
Show file tree
Hide file tree
Showing 17 changed files with 598 additions and 87 deletions.
1 change: 1 addition & 0 deletions build-tools/automation/azure-pipelines.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ variables:
DotNetCoreVersion: 3.1.300
HostedMacImage: macOS-10.15
HostedWinVS2019: Hosted Windows 2019 with VS2019
NetCoreTargetFrameworkPathSuffix: -netcoreapp3.1

jobs:
- job: windows_build
Expand Down
8 changes: 8 additions & 0 deletions build-tools/automation/templates/core-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,14 @@ steps:
arguments: bin/Test$(Build.Configuration)/Java.Interop-Tests.dll
continueOnError: true

- task: DotNetCoreCLI@2
displayName: 'Tests: Java.Interop'
condition: eq('${{ parameters.runNativeTests }}', 'true')
inputs:
command: test
arguments: bin/Test$(Build.Configuration)$(NetCoreTargetFrameworkPathSuffix)/Java.Interop-Tests.dll
continueOnError: true

- task: DotNetCoreCLI@2
displayName: 'Tests: Java.Interop.Dynamic'
condition: eq('${{ parameters.runNativeTests }}', 'true')
Expand Down
7 changes: 6 additions & 1 deletion src/Java.Interop.Export/Java.Interop/MarshalMemberBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,12 @@ static Expression GetRuntime ()
return Expression.Property (null, typeof (JniEnvironment), "Runtime");
}

static MethodInfo FormatterServices_GetUninitializedObject = Type.GetType ("System.Runtime.Serialization.FormatterServices", throwOnError: true)
static MethodInfo FormatterServices_GetUninitializedObject =
#if NETCOREAPP
typeof (System.Runtime.CompilerServices.RuntimeHelpers)
#else // !NETCOREAPP
typeof (System.Runtime.Serialization.FormatterServices)
#endif // NETCOREAPP
.GetRuntimeMethod ("GetUninitializedObject", new[]{typeof (Type)});
static MethodInfo IJavaPeerable_SetPeerReference = typeof (IJavaPeerable).GetRuntimeMethod ("SetPeerReference", new[]{typeof (JniObjectReference)});

Expand Down
18 changes: 18 additions & 0 deletions src/Java.Interop/Java.Interop/JniEnvironment.Types.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#nullable enable

using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Text;

Expand Down Expand Up @@ -47,6 +48,12 @@ public static unsafe JniObjectReference FindClass (string classname)
return r;
}

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

NativeMethods.java_interop_jnienv_exception_clear (info.EnvironmentPointer);

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

public static void RegisterNatives (JniObjectReference type, JniNativeMethodRegistration [] methods, int numMethods)
{
#if DEBUG && NETCOREAPP
foreach (var m in methods) {
if (m.Marshaler.GetType ().GenericTypeArguments.Length != 0) {
var method = m.Marshaler.Method;
Debug.WriteLine ($"JNIEnv::RegisterNatives() given a generic delegate type. .NET Core doesn't like this.");
Debug.WriteLine ($" Java: {m.Name}{m.Signature}");
Debug.WriteLine ($" Marshaler Type={m.Marshaler.GetType ().FullName} Method={method.DeclaringType.FullName}.{method.Name}");
}
}
#endif // DEBUG && NETCOREAPP

int r = _RegisterNatives (type, methods, numMethods);

if (r != 0) {
Expand Down
72 changes: 14 additions & 58 deletions src/Java.Runtime.Environment/Java.Interop/JreRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ public class JreRuntimeOptions : JniRuntime.CreationOptions {

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

public TextWriter? JniGlobalReferenceLogWriter {get; set;}
public TextWriter? JniLocalReferenceLogWriter {get; set;}

public JreRuntimeOptions ()
{
JniVersion = JniVersion.v1_2;
Expand All @@ -39,16 +42,6 @@ public JreRuntimeOptions ()
Path.GetDirectoryName (typeof (JreRuntimeOptions).Assembly.Location),
"java-interop.jar"),
};

bool onMono = Type.GetType ("Mono.Runtime", throwOnError: false) != null;
if (onMono) {
ValueManager = ValueManager ?? new MonoRuntimeValueManager ();
ObjectReferenceManager = ObjectReferenceManager ?? new MonoRuntimeObjectReferenceManager ();
}
else {
ValueManager = ValueManager ?? new DummyValueManager ();
ObjectReferenceManager = ObjectReferenceManager ?? new DummyObjectReferenceManager ();
}
}

public JreRuntimeOptions AddOption (string option)
Expand Down Expand Up @@ -87,12 +80,22 @@ static unsafe JreRuntimeOptions CreateJreVM (JreRuntimeOptions builder)
if (builder == null)
throw new ArgumentNullException ("builder");

bool onMono = Type.GetType ("Mono.Runtime", throwOnError: false) != null;
if (onMono) {
builder.ValueManager = builder.ValueManager ?? new MonoRuntimeValueManager ();
builder.ObjectReferenceManager = builder.ObjectReferenceManager ?? new MonoRuntimeObjectReferenceManager ();
}
else {
builder.ValueManager = builder.ValueManager ?? new ManagedValueManager ();
builder.ObjectReferenceManager = builder.ObjectReferenceManager ?? new ManagedObjectReferenceManager (builder.JniGlobalReferenceLogWriter, builder.JniLocalReferenceLogWriter);
}

if (builder.InvocationPointer != IntPtr.Zero)
return builder;

if (!string.IsNullOrEmpty (builder.JvmLibraryPath)) {
IntPtr errorPtr = IntPtr.Zero;
int r = NativeMethods.java_interop_jvm_load_with_error_message (builder.JvmLibraryPath, out errorPtr);
int r = NativeMethods.java_interop_jvm_load_with_error_message (builder.JvmLibraryPath!, out errorPtr);
if (r != 0) {
string error = Marshal.PtrToStringAnsi (errorPtr);
NativeMethods.java_interop_free (errorPtr);
Expand Down Expand Up @@ -166,52 +169,5 @@ partial class NativeMethods {
[DllImport (JavaInteropLib, CharSet=CharSet.Ansi, CallingConvention=CallingConvention.Cdecl)]
internal static extern int java_interop_jvm_create (out IntPtr javavm, out IntPtr jnienv, ref JavaVMInitArgs args);
}

class DummyValueManager : JniRuntime.JniValueManager {

public override void WaitForGCBridgeProcessing ()
{
}

public override void CollectPeers ()
{
}

public override void AddPeer (IJavaPeerable reference)
{
}

public override void RemovePeer (IJavaPeerable reference)
{
}

public override void FinalizePeer (IJavaPeerable reference)
{
}

public override List<JniSurfacedPeerInfo> GetSurfacedPeers ()
{
return null;
}

public override IJavaPeerable PeekPeer (global::Java.Interop.JniObjectReference reference)
{
return null;
}

public override void ActivatePeer (IJavaPeerable self, JniObjectReference reference, ConstructorInfo cinfo, object [] argumentValues)
{
}
}

class DummyObjectReferenceManager : JniRuntime.JniObjectReferenceManager {
public override int GlobalReferenceCount {
get {return 0;}
}

public override int WeakGlobalReferenceCount {
get {return 0;}
}
}
}

Loading

0 comments on commit a666a6f

Please sign in to comment.