Skip to content
This repository was archived by the owner on Jul 6, 2026. It is now read-only.

Commit a666a6f

Browse files
authored
[Java.Runtime.Environment] Partial support for .NET Core (#804)
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 a666a6f

17 files changed

Lines changed: 598 additions & 87 deletions

File tree

‎build-tools/automation/azure-pipelines.yaml‎

Lines changed: 1 addition & 0 deletions
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‎

Lines changed: 8 additions & 0 deletions
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‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,12 @@ static Expression GetRuntime ()
370370
returnExpression.Property(null,typeof(JniEnvironment),"Runtime");
371371
}
372372

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

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#nullable enable
22

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

@@ -47,6 +48,12 @@ public static unsafe JniObjectReference FindClass (string classname)
4748
returnr;
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
varfindClassThrown=newJniObjectReference(thrown,JniObjectReferenceType.Local);
@@ -167,6 +174,17 @@ public static void RegisterNatives (JniObjectReference type, JniNativeMethodRegi
167174

168175
publicstaticvoidRegisterNatives(JniObjectReferencetype,JniNativeMethodRegistration[]methods,intnumMethods)
169176
{
177+
#if DEBUG&&NETCOREAPP
178+
foreach(varminmethods){
179+
if(m.Marshaler.GetType().GenericTypeArguments.Length!=0){
180+
varmethod=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
intr=_RegisterNatives(type,methods,numMethods);
171189

172190
if(r!=0){

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

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

3232
publicCollection<string>ClassPath{get;privateset;}
3333

34+
publicTextWriter?JniGlobalReferenceLogWriter{get;set;}
35+
publicTextWriter?JniLocalReferenceLogWriter{get;set;}
36+
3437
publicJreRuntimeOptions()
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-
boolonMono=Type.GetType("Mono.Runtime",throwOnError:false)!=null;
44-
if(onMono){
45-
ValueManager=ValueManager??newMonoRuntimeValueManager();
46-
ObjectReferenceManager=ObjectReferenceManager??newMonoRuntimeObjectReferenceManager();
47-
}
48-
else{
49-
ValueManager=ValueManager??newDummyValueManager();
50-
ObjectReferenceManager=ObjectReferenceManager??newDummyObjectReferenceManager();
51-
}
5245
}
5346

5447
publicJreRuntimeOptionsAddOption(stringoption)
@@ -87,12 +80,22 @@ static unsafe JreRuntimeOptions CreateJreVM (JreRuntimeOptions builder)
8780
if(builder==null)
8881
thrownewArgumentNullException("builder");
8982

83+
boolonMono=Type.GetType("Mono.Runtime",throwOnError:false)!=null;
84+
if(onMono){
85+
builder.ValueManager=builder.ValueManager??newMonoRuntimeValueManager();
86+
builder.ObjectReferenceManager=builder.ObjectReferenceManager??newMonoRuntimeObjectReferenceManager();
87+
}
88+
else{
89+
builder.ValueManager=builder.ValueManager??newManagedValueManager();
90+
builder.ObjectReferenceManager=builder.ObjectReferenceManager??newManagedObjectReferenceManager(builder.JniGlobalReferenceLogWriter,builder.JniLocalReferenceLogWriter);
91+
}
92+
9093
if(builder.InvocationPointer!=IntPtr.Zero)
9194
returnbuilder;
9295

9396
if(!string.IsNullOrEmpty(builder.JvmLibraryPath)){
9497
IntPtrerrorPtr=IntPtr.Zero;
95-
intr=NativeMethods.java_interop_jvm_load_with_error_message(builder.JvmLibraryPath,outerrorPtr);
98+
intr=NativeMethods.java_interop_jvm_load_with_error_message(builder.JvmLibraryPath!,outerrorPtr);
9699
if(r!=0){
97100
stringerror=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
internalstaticexternintjava_interop_jvm_create(outIntPtrjavavm,outIntPtrjnienv,refJavaVMInitArgsargs);
168171
}
169-
170-
classDummyValueManager:JniRuntime.JniValueManager{
171-
172-
publicoverridevoidWaitForGCBridgeProcessing()
173-
{
174-
}
175-
176-
publicoverridevoidCollectPeers()
177-
{
178-
}
179-
180-
publicoverridevoidAddPeer(IJavaPeerablereference)
181-
{
182-
}
183-
184-
publicoverridevoidRemovePeer(IJavaPeerablereference)
185-
{
186-
}
187-
188-
publicoverridevoidFinalizePeer(IJavaPeerablereference)
189-
{
190-
}
191-
192-
publicoverrideList<JniSurfacedPeerInfo>GetSurfacedPeers()
193-
{
194-
returnnull;
195-
}
196-
197-
publicoverrideIJavaPeerablePeekPeer(global::Java.Interop.JniObjectReferencereference)
198-
{
199-
returnnull;
200-
}
201-
202-
publicoverridevoidActivatePeer(IJavaPeerableself,JniObjectReferencereference,ConstructorInfocinfo,object[]argumentValues)
203-
{
204-
}
205-
}
206-
207-
classDummyObjectReferenceManager:JniRuntime.JniObjectReferenceManager{
208-
publicoverrideintGlobalReferenceCount{
209-
get{return0;}
210-
}
211-
212-
publicoverrideintWeakGlobalReferenceCount{
213-
get{return0;}
214-
}
215-
}
216172
}
217173

0 commit comments

Comments
 (0)