Skip to content

Commit caca88d

Browse files
committed
[Java.Runtime.Environment] Add Java.Runtime.Environment.
Android preparation! Android doesn't support JNI_CreateJavaVM(), JNI_GetCreatedJavaVMs(), or any other JNI_*() export, so having anything that relies on these JVM features will likely result in pain & suffering when we try to get this all running under Dalvik/ART. Add the Java.Runtime.Environment.dll assembly with the new Java.Interop.JreVM type, and move the JNI_* functionality there. Refactor/simplify Java.Interop.dll/Java.Interop.JavaVM to remove the use of this functionality (e.g. rename JavaVM.GetCreatedJavaVMs() to JavaVM.GetRegisteredJavaVM()). The (unfortunate) result of all this is that Java.Interop.dll is no longer usable by itself; it needs to be used in conjunction with Java.Runtime.Environment.dll in order to create a Java VM.
1 parent 6cb6cab commit caca88d

File tree

12 files changed

+294
-152
lines changed

12 files changed

+294
-152
lines changed

Java.Interop.sln

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,18 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Java.Interop-Tests", "src\J
99
EndProject
1010
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hello", "samples\Hello\Hello.csproj", "{F3ECB73D-9263-4E42-A5B4-3FC0D1D829F9}"
1111
EndProject
12+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Java.Runtime.Environment", "src\Java.Runtime.Environment\Java.Runtime.Environment.csproj", "{5887B410-D448-4257-A46B-EAC03C80BE93}"
13+
EndProject
1214
Global
1315
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1416
Debug|Any CPU = Debug|Any CPU
1517
Release|Any CPU = Release|Any CPU
1618
EndGlobalSection
1719
GlobalSection(ProjectConfigurationPlatforms) = postSolution
20+
{5887B410-D448-4257-A46B-EAC03C80BE93}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21+
{5887B410-D448-4257-A46B-EAC03C80BE93}.Debug|Any CPU.Build.0 = Debug|Any CPU
22+
{5887B410-D448-4257-A46B-EAC03C80BE93}.Release|Any CPU.ActiveCfg = Release|Any CPU
23+
{5887B410-D448-4257-A46B-EAC03C80BE93}.Release|Any CPU.Build.0 = Release|Any CPU
1824
{6410DA0F-5E14-4FC0-9AEE-F4C542C96C7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
1925
{6410DA0F-5E14-4FC0-9AEE-F4C542C96C7A}.Debug|Any CPU.Build.0 = Debug|Any CPU
2026
{6410DA0F-5E14-4FC0-9AEE-F4C542C96C7A}.Release|Any CPU.ActiveCfg = Release|Any CPU

samples/Hello/Hello.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,9 @@
4242
<Project>{94BD81F7-B06F-4295-9636-F8A3B6BDC762}</Project>
4343
<Name>Java.Interop</Name>
4444
</ProjectReference>
45+
<ProjectReference Include="..\..\src\Java.Runtime.Environment\Java.Runtime.Environment.csproj">
46+
<Project>{5887B410-D448-4257-A46B-EAC03C80BE93}</Project>
47+
<Name>Java.Runtime.Environment</Name>
48+
</ProjectReference>
4549
</ItemGroup>
4650
</Project>

samples/Hello/Program.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ public static void Main (string[] args)
1515
} catch (InvalidOperationException e) {
1616
Console.WriteLine (e);
1717
}
18-
foreach (var h in JavaVM.GetCreatedJavaVMs ()) {
19-
Console.WriteLine ("PRE: GetCreatedJavaVMs: {0}", h);
18+
foreach (var h in JreVM.GetCreatedJavaVMHandles ()) {
19+
Console.WriteLine ("PRE: GetCreatedJavaVMHandles: {0}", h);
2020
}
2121
Console.WriteLine ("Part 2!");
22-
using (var vm = new JavaVMBuilder ().CreateJavaVM ()) {
22+
using (var vm = new JreVMBuilder ().CreateJreVM ()) {
2323
Console.WriteLine ("# JniEnvironment.Current={0}", JniEnvironment.Current);
2424
Console.WriteLine ("vm.SafeHandle={0}", vm.SafeHandle);
2525
var t = new JniType ("java/lang/Object");
@@ -43,12 +43,12 @@ public static void Main (string[] args)
4343
t.Start ();
4444
waitForCreation.Wait ();
4545
*/
46-
foreach (var h in JavaVM.GetCreatedJavaVMs ()) {
46+
foreach (var h in JreVM.GetCreatedJavaVMHandles ()) {
4747
Console.WriteLine ("WITHIN: GetCreatedJavaVMs: {0}", h);
4848
}
4949
// exitThread.Signal ();
5050
}
51-
foreach (var h in JavaVM.GetCreatedJavaVMs ()) {
51+
foreach (var h in JreVM.GetCreatedJavaVMHandles ()) {
5252
Console.WriteLine ("POST: GetCreatedJavaVMs: {0}", h);
5353
}
5454
}

src/Java.Interop/Java.Interop/JavaVM.cs

Lines changed: 37 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -39,27 +39,14 @@ struct JavaVMThreadAttachArgs {
3939
public IntPtr group; /* global ref of a ThreadGroup object, or NULL */
4040
}
4141

42-
struct JavaVMOption {
43-
public IntPtr /* const char * */ optionString;
44-
public IntPtr /* void * */ extraInfo;
45-
}
46-
47-
struct JavaVMInitArgs {
48-
public JniVersion version; /* use JNI_VERSION_1_2 or later */
49-
50-
public int nOptions;
51-
public IntPtr /* JavaVMOption[] */ options;
52-
public byte ignoreUnrecognized;
53-
}
54-
5542
public sealed class JavaVMSafeHandle : SafeHandle {
5643

5744
JavaVMSafeHandle ()
5845
: base (IntPtr.Zero, ownsHandle:false)
5946
{
6047
}
6148

62-
internal JavaVMSafeHandle (IntPtr handle)
49+
public JavaVMSafeHandle (IntPtr handle)
6350
: this ()
6451
{
6552
SetHandle (handle);
@@ -90,52 +77,36 @@ public override string ToString ()
9077
}
9178
}
9279

93-
public sealed class JavaVMBuilder {
94-
95-
internal List<string> Options = new List<string> ();
80+
public class JavaVMOptions {
9681

97-
public JniVersion JniVersion {get; set;}
98-
public bool IgnoreUnrecognizedOptions {get; set;}
9982
public bool TrackIDs {get; set;}
83+
public bool DestroyVMOnDispose {get; set;}
10084

101-
public JavaVMBuilder ()
102-
{
103-
JniVersion = JniVersion.v1_2;
104-
}
105-
106-
public JavaVMBuilder AddOption (string option)
107-
{
108-
Options.Add (option);
109-
return this;
110-
}
111-
112-
public JavaVMBuilder AddSystemProperty (string name, string value)
113-
{
114-
if (name == null)
115-
throw new ArgumentNullException ("name");
116-
if (value == null)
117-
throw new ArgumentNullException ("value");
118-
Options.Add (string.Format ("-D{0}={1}", name, value));
119-
return this;
120-
}
85+
public JavaVMSafeHandle VMHandle {get; set;}
86+
public JniEnvironmentSafeHandle EnvironmentHandle {get; set;}
12187

122-
public JavaVM CreateJavaVM ()
88+
public JavaVMOptions ()
12389
{
124-
return new JavaVM (this);
12590
}
12691
}
12792

128-
public partial class JavaVM : IDisposable
93+
public abstract partial class JavaVM : IDisposable
12994
{
130-
const string LibraryName = "/System/Library/Frameworks/JavaVM.framework/JavaVM";
13195

132-
[DllImport (LibraryName)]
133-
static extern int JNI_CreateJavaVM (out JavaVMSafeHandle javavm, out JniEnvironmentSafeHandle jnienv, ref JavaVMInitArgs args);
96+
static ConcurrentDictionary<IntPtr, JavaVM> JavaVMs = new ConcurrentDictionary<IntPtr, JavaVM> ();
13497

135-
[DllImport (LibraryName)]
136-
static extern int JNI_GetCreatedJavaVMs ([Out] IntPtr[] handles, int bufLen, out int nVMs);
98+
public static IEnumerable<JavaVM> GetRegisteredJavaVMs ()
99+
{
100+
return JavaVMs.Values;
101+
}
137102

138-
static ConcurrentDictionary<IntPtr, JavaVM> JavaVMs = new ConcurrentDictionary<IntPtr, JavaVM> ();
103+
public static JavaVM GetRegisteredJavaVM (JavaVMSafeHandle handle)
104+
{
105+
JavaVM vm;
106+
return JavaVMs.TryGetValue (handle.DangerousGetHandle (), out vm)
107+
? vm
108+
: null;
109+
}
139110

140111
static JavaVM current;
141112
public static JavaVM Current {
@@ -144,12 +115,12 @@ public static JavaVM Current {
144115
return current;
145116
JavaVM c = null;
146117
int count = 0;
147-
foreach (var vm in GetCreatedJavaVMs ()) {
118+
foreach (var vm in JavaVMs.Values) {
148119
if (count++ == 0)
149120
c = vm;
150121
}
151122
if (count == 0)
152-
throw new InvalidOperationException ("No JavaVM has been created. Please use JavaVMBuilder.CreateJavaVM().");
123+
throw new InvalidOperationException ("No JavaVM has been created. Please use Java.Interop.JreVMBuilder.CreateJreVM().");
153124
if (count > 1)
154125
throw new NotSupportedException (string.Format ("Found {0} JavaVMs. Don't know which to use. Use JavaVM.SetCurrent().", count));
155126
return current = c;
@@ -160,52 +131,10 @@ public static void SetCurrent (JavaVM newCurrent)
160131
{
161132
if (newCurrent == null)
162133
throw new ArgumentNullException ("newCurrent");
134+
JavaVMs.TryAdd (newCurrent.SafeHandle.DangerousGetHandle (), newCurrent);
163135
current = newCurrent;
164136
}
165137

166-
public static IEnumerable<JavaVM> GetCreatedJavaVMs ()
167-
{
168-
int nVMs;
169-
int r = JNI_GetCreatedJavaVMs (null, 0, out nVMs);
170-
if (r != 0)
171-
throw new NotSupportedException ("JNI_GetCreatedJavaVMs() returned: " + r);
172-
var handles = new IntPtr [nVMs];
173-
r = JNI_GetCreatedJavaVMs (handles, handles.Length, out nVMs);
174-
if (r != 0)
175-
throw new InvalidOperationException ("JNI_GetCreatedJavaVMs() [take 2!] returned: " + r);
176-
foreach (var h in handles) {
177-
JavaVM v;
178-
if (!JavaVMs.TryGetValue (h, out v))
179-
JavaVMs.TryAdd (h, v = new JavaVM (new JavaVMSafeHandle (h)));
180-
yield return v;
181-
}
182-
}
183-
184-
public static JavaVM FromHandle (JavaVMSafeHandle handle)
185-
{
186-
JavaVM vm;
187-
if (JavaVMs.TryGetValue (handle.DangerousGetHandle (), out vm))
188-
return vm;
189-
return new JavaVM (handle, null);
190-
}
191-
192-
void CreateJavaVM (ref JavaVMInitArgs args)
193-
{
194-
JavaVMSafeHandle javavm;
195-
JniEnvironmentSafeHandle jnienv;
196-
int r = JNI_CreateJavaVM (out javavm, out jnienv, ref args);
197-
if (r != 0) {
198-
var message = string.Format ("{1}JNI_CreateJavaVM returned {0}.",
199-
r,
200-
JavaVMs.Count == 0
201-
? ""
202-
: "The JDK supports creating at most one JVM per process, ever; " +
203-
"do you have a JVM running already, or have you already created (and destroyed?) one? ");
204-
throw new NotSupportedException (message);
205-
}
206-
Initialize (javavm, jnienv);
207-
}
208-
209138
ConcurrentDictionary<IntPtr, JniEnvironment> Environments = new ConcurrentDictionary<IntPtr, JniEnvironment> ();
210139

211140
ConcurrentDictionary<SafeHandle, IDisposable> TrackedInstances;
@@ -219,58 +148,26 @@ void CreateJavaVM (ref JavaVMInitArgs args)
219148

220149
public JavaVMSafeHandle SafeHandle {get; private set;}
221150

222-
protected JavaVM ()
223-
: this (new JavaVMBuilder ())
151+
protected JavaVM (JavaVMOptions options)
224152
{
225-
}
226-
227-
internal protected unsafe JavaVM (JavaVMBuilder builder)
228-
{
229-
if (builder == null)
230-
throw new ArgumentNullException ("builder");
231-
232-
var args = new JavaVMInitArgs () {
233-
version = builder.JniVersion,
234-
nOptions = builder.Options.Count,
235-
ignoreUnrecognized = builder.IgnoreUnrecognizedOptions ? (byte) 1 : (byte) 0,
236-
};
237-
var options = new JavaVMOption [builder.Options.Count];
238-
try {
239-
for (int i = 0; i < options.Length; ++i)
240-
options [i].optionString = Marshal.StringToHGlobalAnsi (builder.Options [i]);
241-
fixed (JavaVMOption* popts = options) {
242-
args.options = (IntPtr) popts;
243-
CreateJavaVM (ref args);
244-
}
245-
} finally {
246-
for (int i = 0; i < options.Length; ++i)
247-
Marshal.FreeHGlobal (options [i].optionString);
248-
}
153+
if (options == null)
154+
throw new ArgumentNullException ("options");
155+
if (options.VMHandle == null)
156+
throw new ArgumentException ("options.VMHandle is null", "options");
157+
if (options.VMHandle.IsInvalid)
158+
throw new ArgumentException ("options.VMHandle is not valid.", "options");
249159

250-
TrackIDs = builder.TrackIDs;
251-
DestroyVM = true;
252-
}
160+
TrackIDs = options.TrackIDs;
161+
DestroyVM = options.DestroyVMOnDispose;
253162

254-
public JavaVM (JavaVMSafeHandle safeHandle, JniEnvironmentSafeHandle jnienv = null)
255-
{
256-
Initialize (safeHandle, jnienv);
257-
}
258-
259-
void Initialize (JavaVMSafeHandle safeHandle, JniEnvironmentSafeHandle jnienv)
260-
{
261-
if (safeHandle == null)
262-
throw new ArgumentNullException ("safeHandle");
263-
if (safeHandle.IsInvalid)
264-
throw new ArgumentException ("safeHandle is not valid.", "safeHandle");
163+
SafeHandle = options.VMHandle;
164+
Invoker = SafeHandle.CreateInvoker ();
265165

266166
if (current == null)
267167
current = this;
268168

269-
SafeHandle = safeHandle;
270-
Invoker = safeHandle.CreateInvoker ();
271-
272-
if (jnienv != null) {
273-
var env = new JniEnvironment (jnienv, this);
169+
if (options.EnvironmentHandle != null) {
170+
var env = new JniEnvironment (options.EnvironmentHandle, this);
274171
Environments.TryAdd (env.SafeHandle.DangerousGetHandle (), env);
275172
}
276173

@@ -301,10 +198,10 @@ protected virtual void Dispose (bool disposing)
301198
current = null;
302199

303200
ClearTrackedReferences ();
304-
if (DestroyVM)
305-
DestroyJavaVM ();
306201
JavaVM _;
307202
JavaVMs.TryRemove (SafeHandle.DangerousGetHandle (), out _);
203+
if (DestroyVM)
204+
DestroyJavaVM ();
308205
SafeHandle.Dispose ();
309206
SafeHandle = null;
310207
}

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,11 @@ public static JniEnvironment GetEnvironmentFromHandle (IntPtr handle)
8282
int r = env.Invoker.GetJavaVM (env.SafeHandle, out vm);
8383
if (r < 0)
8484
throw new InvalidOperationException ("JNIEnv::GetJavaVM() returned: " + r);
85-
env.JavaVM = JavaVM.FromHandle (vm);
85+
env.JavaVM = JavaVM.GetRegisteredJavaVM (vm);
86+
if (env.JavaVM == null)
87+
throw new NotSupportedException (
88+
string.Format ("No JavaVM registered with handle 0x{0}.",
89+
vm.DangerousGetHandle ().ToString ("x")));
8690
return env;
8791
}
8892

@@ -91,10 +95,12 @@ public void Dispose ()
9195
if (SafeHandle == null)
9296
return;
9397
Object_class.Dispose ();
98+
Object_toString.Dispose ();
9499
if (Current == this)
95100
current = null;
96101
SafeHandle.Dispose ();
97102
SafeHandle = null;
103+
JavaVM = null;
98104
}
99105

100106
JniType Object_class;

src/Java.Interop/Tests/Java.Interop-Tests.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,5 +81,9 @@
8181
<Project>{94BD81F7-B06F-4295-9636-F8A3B6BDC762}</Project>
8282
<Name>Java.Interop</Name>
8383
</ProjectReference>
84+
<ProjectReference Include="..\..\Java.Runtime.Environment\Java.Runtime.Environment.csproj">
85+
<Project>{5887B410-D448-4257-A46B-EAC03C80BE93}</Project>
86+
<Name>Java.Runtime.Environment</Name>
87+
</ProjectReference>
8488
</ItemGroup>
8589
</Project>

src/Java.Interop/Tests/Java.Interop/JVM.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@
77

88
namespace Java.InteropTests {
99

10-
class JVM : JavaVM {
10+
class JVM : JreVM {
1111

1212
public static readonly new JavaVM Current = new JVM ();
1313

1414
TextWriter grefLog;
1515

1616
JVM ()
17-
: base (new JavaVMBuilder ().AddSystemProperty ("java.class.path", "interop-test.jar"))
17+
: base (new JreVMBuilder ().AddSystemProperty ("java.class.path", "interop-test.jar"))
1818
{
1919
string logGrefs = (Environment.GetEnvironmentVariable ("_JI_LOG") ?? "")
2020
.Split (new []{ ',' }, StringSplitOptions.RemoveEmptyEntries)

src/Java.Interop/Tests/Java.Interop/JavaVMTest.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public void JDK_OnlySupportsOneVM ()
2424
var first = JVM.Current;
2525
#pragma warning restore 0219
2626
try {
27-
var second = new JavaVMBuilder ().CreateJavaVM ();
27+
var second = new JreVMBuilder ().CreateJreVM ();
2828
// If we reach here, we're in a JVM that supports > 1 VM
2929
second.Dispose ();
3030
Assert.Ignore ();
@@ -42,15 +42,15 @@ public void CreateJavaVMWithNullBuilder ()
4242

4343
class JavaVMWithNullBuilder : JavaVM {
4444
public JavaVMWithNullBuilder ()
45-
: base ((JavaVMBuilder) null)
45+
: base ((JavaVMOptions) null)
4646
{
4747
}
4848
}
4949

5050
[Test]
51-
public void FromHandle_ExistingInstance ()
51+
public void GetRegisteredJavaVM_ExistingInstance ()
5252
{
53-
Assert.AreEqual (JavaVM.Current, JavaVM.FromHandle (JavaVM.Current.SafeHandle));
53+
Assert.AreEqual (JavaVM.Current, JavaVM.GetRegisteredJavaVM (JavaVM.Current.SafeHandle));
5454
}
5555

5656
[Test]

0 commit comments

Comments
 (0)