Skip to content

Commit

Permalink
[Java.Runtime.Environment] Support .NET Core
Browse files Browse the repository at this point in the history
Fixes: dotnet#426

Enable C#8 [Nullable Reference Types][0] for
`Java.Runtime.Environment.dll`.

Add 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".

To make this work, update `JniRuntime.JniValueManager` to have the
following new abstract members:

	partial class JniRuntime {
	    partial class JniValueManager {
	        public abstract bool PeersRequireRelease {get;}
	        public abstract void ReleasePeers ();
	    }
	}

`PeersRequireRelease` shall be `true` when there is no GC bridge.
When this is true, `JniValueManager.CollectPeers()` is a no-op.
If an `IJavaPeerable` must have disposal logic performed, then
`.Dispose()` must be called on that instance.  There is no
finalization integration.

The new `JniValueManager.ReleasePeers()` method:

 1. Releases all GREFs for all held peers.

    This allows Java to collect the Java peers.

 2. Stops referencing all `IJavaPeerable` values.

    This allows the .NET GC to collect the `IJavaPeerable` values.

There is no notification to the `IJavaPeerable` instances that this
has happened.

Update `Java.Interop-Tests.csproj` to define
`NO_MARSHAL_MEMBER_BUILDER_SUPPORT` when building for .NET Core.

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

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 19, 2021
1 parent bba1f07 commit f77edab
Show file tree
Hide file tree
Showing 20 changed files with 616 additions and 85 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
2 changes: 1 addition & 1 deletion build-tools/automation/templates/core-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ steps:
condition: eq('${{ parameters.runNativeTests }}', 'true')
inputs:
command: test
arguments: bin/Test$(Build.Configuration)/Java.Interop-Tests.dll
arguments: bin/Test$(Build.Configuration)$(NetCoreTargetFrameworkPathSuffix)/Java.Interop-Tests.dll
continueOnError: true

- task: DotNetCoreCLI@2
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 NETFRAMEWORK || NET_2_0
typeof (System.Runtime.Serialization.FormatterServices)
#else // !(NETFRAMEWORK || NET_2_0)
typeof (System.Runtime.CompilerServices.RuntimeHelpers)
#endif // NETFRAMEWORK || NET_2_0
.GetRuntimeMethod ("GetUninitializedObject", new[]{typeof (Type)});
static MethodInfo IJavaPeerable_SetPeerReference = typeof (IJavaPeerable).GetRuntimeMethod ("SetPeerReference", new[]{typeof (JniObjectReference)});

Expand Down
14 changes: 14 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,8 @@ public static unsafe JniObjectReference FindClass (string classname)
return r;
}

// NativeMethods.java_interop_jnienv_exception_describe (info.EnvironmentPointer);

NativeMethods.java_interop_jnienv_exception_clear (info.EnvironmentPointer);

var findClassThrown = new JniObjectReference (thrown, JniObjectReferenceType.Local);
Expand Down Expand Up @@ -167,6 +170,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

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

if (r != 0) {
Expand Down
3 changes: 3 additions & 0 deletions src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,12 @@ protected virtual void Dispose (bool disposing)
disposed = true;
}

public abstract bool PeersRequireRelease {get;}

public abstract void WaitForGCBridgeProcessing ();

public abstract void CollectPeers ();
public abstract void ReleasePeers ();

public abstract void AddPeer (IJavaPeerable value);

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 f77edab

Please sign in to comment.