Skip to content

Commit 98e0288

Browse files
committed
[Java.Runtime.Environment] Support .NET Core
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
1 parent 89a5a22 commit 98e0288

17 files changed

+612
-83
lines changed

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

+13
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ public static unsafe JniObjectReference FindClass (string classname)
4747
return r;
4848
}
4949

50+
// NativeMethods.java_interop_jnienv_exception_describe (info.EnvironmentPointer);
51+
5052
NativeMethods.java_interop_jnienv_exception_clear (info.EnvironmentPointer);
5153

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

168170
public static void RegisterNatives (JniObjectReference type, JniNativeMethodRegistration [] methods, int numMethods)
169171
{
172+
#if DEBUG && NETCOREAPP
173+
foreach (var m in methods) {
174+
if (m.Marshaler.GetType ().GenericTypeArguments.Length != 0) {
175+
var method = m.Marshaler.Method;
176+
Debug.WriteLine ($"JNIEnv::RegisterNatives() given a generic delegate type. .NET Core doesn't like this.");
177+
Debug.WriteLine ($" Java: {m.Name}{m.Signature}");
178+
Debug.WriteLine ($" Marshaler Type={m.Marshaler.GetType ().FullName} Method={method.DeclaringType.FullName}.{method.Name}");
179+
}
180+
}
181+
#endif // DEBUG
182+
170183
int r = _RegisterNatives (type, methods, numMethods);
171184

172185
if (r != 0) {

src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs

+3
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,12 @@ protected virtual void Dispose (bool disposing)
7474
disposed = true;
7575
}
7676

77+
public abstract bool PeersRequireRelease {get;}
78+
7779
public abstract void WaitForGCBridgeProcessing ();
7880

7981
public abstract void CollectPeers ();
82+
public abstract void ReleasePeers ();
8083

8184
public abstract void AddPeer (IJavaPeerable value);
8285

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

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Collections.ObjectModel;
4+
using System.Diagnostics;
5+
using System.IO;
6+
using System.Linq;
7+
using System.Reflection;
8+
using System.Runtime.InteropServices;
9+
using System.Text;
10+
using System.Threading;
11+
12+
namespace Java.Interop {
13+
14+
class ManagedObjectReferenceManager : JniRuntime.JniObjectReferenceManager {
15+
16+
TextWriter? grefLog;
17+
TextWriter? lrefLog;
18+
19+
int grefCount;
20+
int wgrefCount;
21+
22+
23+
public override int GlobalReferenceCount => grefCount;
24+
public override int WeakGlobalReferenceCount => wgrefCount;
25+
26+
public override bool LogLocalReferenceMessages => lrefLog != null;
27+
public override bool LogGlobalReferenceMessages => grefLog != null;
28+
29+
public ManagedObjectReferenceManager (TextWriter? grefLog, TextWriter? lrefLog)
30+
{
31+
if (grefLog != null && lrefLog != null && object.ReferenceEquals (grefLog, lrefLog)) {
32+
this.grefLog = this.lrefLog = TextWriter.Synchronized (grefLog);
33+
return;
34+
}
35+
36+
var grefPath = Environment.GetEnvironmentVariable ("JAVA_INTEROP_GREF_LOG");
37+
var lrefPath = Environment.GetEnvironmentVariable ("JAVA_INTEROP_LREF_LOG");
38+
39+
bool samePath = !string.IsNullOrEmpty (grefPath) &&
40+
!string.IsNullOrEmpty (lrefPath) &&
41+
grefPath == lrefPath;
42+
43+
if (grefLog != null) {
44+
this.grefLog = TextWriter.Synchronized (grefLog);
45+
}
46+
if (lrefLog != null) {
47+
this.lrefLog = TextWriter.Synchronized (lrefLog);
48+
}
49+
50+
if (this.grefLog == null && !string.IsNullOrEmpty (grefPath)) {
51+
this.grefLog = TextWriter.Synchronized (CreateTextWriter (grefPath));
52+
}
53+
if (this.lrefLog == null && samePath) {
54+
this.lrefLog = this.grefLog;
55+
}
56+
if (this.lrefLog == null && !string.IsNullOrEmpty (lrefPath)) {
57+
this.lrefLog = TextWriter.Synchronized (CreateTextWriter (lrefPath));
58+
}
59+
}
60+
61+
public override void OnSetRuntime (JniRuntime runtime)
62+
{
63+
base.OnSetRuntime (runtime);
64+
}
65+
66+
static TextWriter? CreateTextWriter (string path)
67+
{
68+
return new StreamWriter (path, append: false, encoding: new UTF8Encoding (encoderShouldEmitUTF8Identifier: false));
69+
}
70+
71+
public override void WriteLocalReferenceLine (string format, params object[] args)
72+
{
73+
if (lrefLog == null)
74+
return;
75+
lrefLog.WriteLine (format, args);
76+
lrefLog.Flush ();
77+
}
78+
79+
public override JniObjectReference CreateLocalReference (JniObjectReference reference, ref int localReferenceCount)
80+
{
81+
if (!reference.IsValid)
82+
return reference;
83+
84+
var r = base.CreateLocalReference (reference, ref localReferenceCount);
85+
86+
CreatedReference (lrefLog, "+l+ lrefc", localReferenceCount, reference, r, Runtime);
87+
88+
return r;
89+
}
90+
91+
public override void DeleteLocalReference (ref JniObjectReference reference, ref int localReferenceCount)
92+
{
93+
if (!reference.IsValid)
94+
return;
95+
96+
var r = reference;
97+
98+
base.DeleteLocalReference (ref reference, ref localReferenceCount);
99+
100+
DeletedReference (lrefLog, "-l- lrefc", localReferenceCount, r, Runtime);
101+
}
102+
103+
public override void CreatedLocalReference (JniObjectReference reference, ref int localReferenceCount)
104+
{
105+
if (!reference.IsValid)
106+
return;
107+
base.CreatedLocalReference (reference, ref localReferenceCount);
108+
CreatedReference (lrefLog, "+l+ lrefc", localReferenceCount, reference, Runtime);
109+
}
110+
111+
public override IntPtr ReleaseLocalReference (ref JniObjectReference reference, ref int localReferenceCount)
112+
{
113+
if (!reference.IsValid)
114+
return IntPtr.Zero;
115+
var r = reference;
116+
var p = base.ReleaseLocalReference (ref reference, ref localReferenceCount);
117+
DeletedReference (lrefLog, "-l- lrefc", localReferenceCount, r, Runtime);
118+
return p;
119+
}
120+
121+
public override void WriteGlobalReferenceLine (string format, params object?[]? args)
122+
{
123+
if (grefLog == null)
124+
return;
125+
grefLog.WriteLine (format, args);
126+
grefLog.Flush ();
127+
}
128+
129+
public override JniObjectReference CreateGlobalReference (JniObjectReference reference)
130+
{
131+
if (!reference.IsValid)
132+
return reference;
133+
var n = base.CreateGlobalReference (reference);
134+
int c = Interlocked.Increment (ref grefCount);
135+
CreatedReference (grefLog, "+g+ grefc", c, reference, n, Runtime);
136+
return n;
137+
}
138+
139+
public override void DeleteGlobalReference (ref JniObjectReference reference)
140+
{
141+
if (!reference.IsValid)
142+
return;
143+
int c = Interlocked.Decrement (ref grefCount);
144+
DeletedReference (grefLog, "-g- grefc", c, reference, Runtime);
145+
base.DeleteGlobalReference (ref reference);
146+
}
147+
148+
public override JniObjectReference CreateWeakGlobalReference (JniObjectReference reference)
149+
{
150+
if (!reference.IsValid)
151+
return reference;
152+
var n = base.CreateWeakGlobalReference (reference);
153+
154+
int wc = Interlocked.Increment (ref wgrefCount);
155+
int gc = grefCount;
156+
if (grefLog != null) {
157+
string message = $"+w+ grefc {gc} gwrefc {wc} obj-handle {reference.ToString ()} -> new-handle {n.ToString ()} " +
158+
$"from thread '{Runtime.GetCurrentManagedThreadName ()}'({Environment.CurrentManagedThreadId})" +
159+
Environment.NewLine +
160+
Runtime.GetCurrentManagedThreadStackTrace (skipFrames: 2, fNeedFileInfo: true);
161+
grefLog.WriteLine (message);
162+
grefLog.Flush ();
163+
}
164+
165+
return n;
166+
}
167+
168+
public override void DeleteWeakGlobalReference (ref JniObjectReference reference)
169+
{
170+
if (!reference.IsValid)
171+
return;
172+
173+
int wc = Interlocked.Decrement (ref wgrefCount);
174+
int gc = grefCount;
175+
176+
if (grefLog != null) {
177+
string message = $"-w- grefc {gc} gwrefc {wc} handle {reference.ToString ()} " +
178+
$"from thread '{Runtime.GetCurrentManagedThreadName ()}'({Environment.CurrentManagedThreadId})" +
179+
Environment.NewLine +
180+
Runtime.GetCurrentManagedThreadStackTrace (skipFrames: 2, fNeedFileInfo: true);
181+
grefLog.WriteLine (message);
182+
grefLog.Flush ();
183+
}
184+
185+
base.DeleteWeakGlobalReference (ref reference);
186+
}
187+
188+
protected override void Dispose (bool disposing)
189+
{
190+
}
191+
192+
static void CreatedReference (TextWriter? writer, string kind, int count, JniObjectReference reference, JniRuntime runtime)
193+
{
194+
if (writer == null)
195+
return;
196+
string message = $"{kind} {count} handle {reference.ToString ()} " +
197+
$"from thread '{runtime.GetCurrentManagedThreadName ()}'({Environment.CurrentManagedThreadId})" +
198+
Environment.NewLine +
199+
runtime.GetCurrentManagedThreadStackTrace (skipFrames: 2, fNeedFileInfo: true);
200+
writer.WriteLine (message);
201+
writer.Flush ();
202+
}
203+
204+
static void CreatedReference (TextWriter? writer, string kind, int count, JniObjectReference reference, JniObjectReference newReference, JniRuntime runtime)
205+
{
206+
if (writer == null)
207+
return;
208+
string message = $"{kind} {count} obj-handle {reference.ToString ()} -> new-handle {newReference.ToString ()} " +
209+
$"from thread '{runtime.GetCurrentManagedThreadName ()}'({Environment.CurrentManagedThreadId})" +
210+
Environment.NewLine +
211+
runtime.GetCurrentManagedThreadStackTrace (skipFrames: 2, fNeedFileInfo: true);
212+
writer.WriteLine (message);
213+
writer.Flush ();
214+
}
215+
216+
static void DeletedReference (TextWriter? writer, string kind, int count, JniObjectReference reference, JniRuntime runtime)
217+
{
218+
if (writer == null)
219+
return;
220+
string message = $"{kind} {count} handle {reference.ToString ()} " +
221+
$"from thread '{runtime.GetCurrentManagedThreadName ()}'({Environment.CurrentManagedThreadId})" +
222+
Environment.NewLine +
223+
runtime.GetCurrentManagedThreadStackTrace (skipFrames: 2, fNeedFileInfo: true);
224+
writer.WriteLine (message);
225+
writer.Flush ();
226+
}
227+
}
228+
}

0 commit comments

Comments
 (0)