Skip to content

Commit

Permalink
[Java.Base] Begin binding JDK-11 java.base module
Browse files Browse the repository at this point in the history
Context: dotnet#858

What do *I* want?  To be able to use our wonderful Java binding
infrastructure against *Desktop Java*, not just Android.

At the same time, I don't want "Android-isms" "leaking" into such a
binding.  *Just* Java.Interop, no xamarin-android.

"Take over" the `generator --codegen-target=JavaInterop1` format
so that it *isn't* useful for Xamarin.Android, and is instead usable
for non-Android usage.

This is a work-in-progress, and *far* from complete.  For prototype
purposes, this *only* binds:

  * `java.lang.Object`
  * `java.lang.Throwable`
  * `java.lang.Class`

The `Java.Base` binding is only for .NET 6 and above.  I'm not
interested in .NET Standard support at this point in time.

Update `samples/Hello` so that it (1) works, and (2) instantiates the
`Java.Lang.Object` binding:

	dotnet run --project samples/Hello

~~ Binding changes vs. Xamarin.Android ~~

Java arrays are bound as appropriate `IList<T>`, using the
`Java.Interop.Java*Array` types as an intermediary.  This should help
reduce marshaling logic & overhead, as if the "source" array is a
`Java*Array`, it doesn't need to be "deep copied".  The exception is
C# `params` arrays, which continue to be bound as arrays, and are
marshaled via an appropriate `Java*Array` type.

`java.io.InputStream` isn't bound as `System.IO.Stream`, etc.

"Java.Interop-style" constructors are used (25de1f3), e.g.

	// This
	DeclaringType (ref JniObjectReference reference, JniObjectReferenceOptions options);

	// Not Xamarin.Android-style
	DeclaringType (IntPtr handle, JniHandleOwnership transfer);

"Java.Interop-style" wrapper construction is used, e.g.

	// This
	var wrapper = JniEnvironment.Runtime.ValueManager.GetValue<DeclaringType>(ref h, JniObjectReferenceOptions.CopyAndDispose);

	// Not this
	var wrapper = Java.Lang.Object.GetObject<DeclaringType>(handle);

Marshal methods are currently skipped.  Java-to-managed invocations
are not currently supported.

~~ TODO: Marshal Methods? ~~

Xamarin.Android uses Java Callable Wrappers + `Runtime.register()`
to specify which methods to register, via lots of reflection, etc.

For Desktop, JCW's shouldn't have all the methods to register.
Instead, use the `jnimarshalmethod-gen`-originated strategy of
`[JniAddNativeMethodRegistrationAttribute]` within the binding, and
then have it use `MarshalMethodBuilder` to generate the marshal
methods.  Need to update `MarshalMethodBuilder` to look for overrides
in addition to methods with [`JavaCallable`], which in turn will
require an equivalent to `Android.Runtime.RegisterAttribute(…)`.

Perhaps `JniMethodSignatureAttribute(string name, string sig)`?

In the meantime, `Java.Base` will skip all marshal-method logic
plus runtime method generation.  Leave that for later.

~~ TODO: Other API Changes? ~~

We should "unify" `java.lang.Object` and `System.Object`.  Consider
`java.lang.Class`:

	/* partial */ class Class<T> {
	    public boolean isInstance(java.lang.Object);
	    public java.lang.Object[] getSigners();
	}

If we unify `java.lang.Object` and `System.Object`, we'd have a
binding of:

	partial class Class {
	    public bool IsInstance (object value);
	    public object[] GetSigners();
	}

~~ Open Questions ~~

What's up with `java.lang.Class.getAnnotationsByType()`?

During an iteration of this PR, I got:

	public unsafe Java.Interop.JavaObjectArray<Java.Lang.Object>? GetAnnotationsByType (Java.Lang.Class? annotationClass)
	{
	    const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/annotation/Annotation;";

From `__id` we see that the Java return type is `Annotation[]`, yet
we bind it as an `Object` array?  Why?  How do we fix that to instead
bind it as `JavaObjectArray<Java.Lang.Annotations.Annotation>`?

Currently, it's differently *worse*; I don't know why, but `__id`
is now:

	const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/Object;";

i.e. the return type is an `Object` array instead of an `Annotation`
array, which is wrong, as per `javap`:

	% javap -s java.lang.Class
	…
	  public <A extends java.lang.annotation.Annotation> A getAnnotation(java.lang.Class<A>);
	    descriptor: (Ljava/lang/Class;)Ljava/lang/annotation/Annotation;

Fixing unit tests...
  • Loading branch information
jonpryor committed Nov 17, 2021
1 parent 0293360 commit fd966e3
Show file tree
Hide file tree
Showing 443 changed files with 13,708 additions and 4,399 deletions.
4 changes: 4 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
Project="$(_OutputPath)JdkInfo.props"
Condition="Exists('$(_OutputPath)JdkInfo.props')"
/>
<Import
Project="$(_OutputPath)JdkInfo-11.props"
Condition="Exists('$(_OutputPath)JdkInfo-11.props')"
/>
<Import
Project="$(_OutputPath)MonoInfo.props"
Condition="Exists('$(_OutputPath)MonoInfo.props')"
Expand Down
7 changes: 7 additions & 0 deletions Java.Interop.sln
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Java.Interop.Tools.JavaType
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Java.Interop.Tools.JavaTypeSystem-Tests", "tests\Java.Interop.Tools.JavaTypeSystem-Tests\Java.Interop.Tools.JavaTypeSystem-Tests.csproj", "{11942DE9-AEC2-4B95-87AB-CA707C37643D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Java.Base", "src\Java.Base\Java.Base.csproj", "{30DCECA5-16FD-4FD0-883C-E5E83B11565D}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Java.Interop.NamingCustomAttributes\Java.Interop.NamingCustomAttributes.projitems*{58b564a1-570d-4da2-b02d-25bddb1a9f4f}*SharedItemsImports = 5
Expand Down Expand Up @@ -296,6 +298,10 @@ Global
{11942DE9-AEC2-4B95-87AB-CA707C37643D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{11942DE9-AEC2-4B95-87AB-CA707C37643D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{11942DE9-AEC2-4B95-87AB-CA707C37643D}.Release|Any CPU.Build.0 = Release|Any CPU
{30DCECA5-16FD-4FD0-883C-E5E83B11565D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{30DCECA5-16FD-4FD0-883C-E5E83B11565D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{30DCECA5-16FD-4FD0-883C-E5E83B11565D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{30DCECA5-16FD-4FD0-883C-E5E83B11565D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -346,6 +352,7 @@ Global
{BF5A4019-F2FF-45AC-949D-EF7E8C94196B} = {271C9F30-F679-4793-942B-0D9527CB3E2F}
{B173F53B-986C-4E0D-881C-063BBB116E1D} = {0998E45F-8BCE-4791-A944-962CD54E2D80}
{11942DE9-AEC2-4B95-87AB-CA707C37643D} = {271C9F30-F679-4793-942B-0D9527CB3E2F}
{30DCECA5-16FD-4FD0-883C-E5E83B11565D} = {0998E45F-8BCE-4791-A944-962CD54E2D80}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {29204E0C-382A-49A0-A814-AD7FBF9774A5}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ public class JdkInfo : Task
{
public string JdksRoot { get; set; }

public string PropertyNameModifier { get; set; } = "";
public string MinimumJdkVersion { get; set; }
public string MaximumJdkVersion { get; set; }

public string DotnetToolPath { get; set; }
Expand All @@ -28,19 +30,30 @@ public class JdkInfo : Task
[Required]
public ITaskItem PropertyFile { get; set; }

[Required]
public ITaskItem MakeFragmentFile { get; set; }

[Output]
public string JavaHomePath { get; set; }

public override bool Execute ()
{
var minVersion = GetVersion (MinimumJdkVersion);
var maxVersion = GetVersion (MaximumJdkVersion);

XATInfo jdk = XATInfo.GetKnownSystemJdkInfos (CreateLogger ())
var explicitJdks = GetJdkRoots ();
var defaultJdks = XATInfo.GetKnownSystemJdkInfos (CreateLogger ())
.Where (j => minVersion != null ? j.Version >= minVersion : true)
.Where (j => maxVersion != null ? j.Version <= maxVersion : true)
.Where (j => j.IncludePath.Any ())
.Where (j => j.IncludePath.Any ());

foreach (var x in explicitJdks) {
Log.LogWarning ($"# jonp: Explicit JDK path: {x.HomePath}");
}
foreach (var x in defaultJdks) {
Log.LogWarning ($"# jonp: Default JDK path: {x.HomePath}");
}

var jdk = explicitJdks.Concat (defaultJdks)
.FirstOrDefault ();

if (jdk == null) {
Expand All @@ -56,14 +69,31 @@ public override bool Execute ()
JavaHomePath = jdk.HomePath;

Directory.CreateDirectory (Path.GetDirectoryName (PropertyFile.ItemSpec));
Directory.CreateDirectory (Path.GetDirectoryName (MakeFragmentFile.ItemSpec));

WritePropertyFile (jdk.JavaPath, jdk.JarPath, jdk.JavacPath, jdk.JdkJvmPath, rtJarPath, jdk.IncludePath);
WriteMakeFragmentFile (jdk.JavaPath, jdk.JarPath, jdk.JavacPath, jdk.JdkJvmPath, rtJarPath, jdk.IncludePath);

if (MakeFragmentFile != null) {
Directory.CreateDirectory (Path.GetDirectoryName (MakeFragmentFile.ItemSpec));
WriteMakeFragmentFile (jdk.JavaPath, jdk.JarPath, jdk.JavacPath, jdk.JdkJvmPath, rtJarPath, jdk.IncludePath);
}

return !Log.HasLoggedErrors;
}

XATInfo[] GetJdkRoots ()
{
XATInfo jdk = null;
try {
if (!string.IsNullOrEmpty (JdksRoot))
jdk = new XATInfo (JdksRoot);
} catch (Exception e) {
Log.LogWarning ($"Could not get information about JdksRoot path `{JdksRoot}`: {e.Message}");
Log.LogMessage (MessageImportance.Low, e.ToString ());
}
return jdk == null
? Array.Empty<XATInfo>()
: new[] { jdk };
}

Version GetVersion (string value)
{
if (string.IsNullOrEmpty (value))
Expand Down Expand Up @@ -97,39 +127,35 @@ Action<TraceLevel, string> CreateLogger ()

void WritePropertyFile (string javaPath, string jarPath, string javacPath, string jdkJvmPath, string rtJarPath, IEnumerable<string> includes)
{
var dotnet = string.IsNullOrEmpty (DotnetToolPath) ? "dotnet" : DotnetToolPath;
var msbuild = XNamespace.Get ("http://schemas.microsoft.com/developer/msbuild/2003");
var jdkJvmP = $"JdkJvm{PropertyNameModifier}Path";
var project = new XElement (msbuild + "Project",
new XElement (msbuild + "Choose",
new XElement (msbuild + "When", new XAttribute ("Condition", " '$(JdkJvmPath)' == '' "),
new XElement (msbuild + "When", new XAttribute ("Condition", $" '$({jdkJvmP})' == '' "),
new XElement (msbuild + "PropertyGroup",
new XElement (msbuild + "JdkJvmPath", jdkJvmPath)),
new XElement (msbuild + jdkJvmP, jdkJvmPath)),
new XElement (msbuild + "ItemGroup",
includes.Select (i => new XElement (msbuild + "JdkIncludePath", new XAttribute ("Include", i)))))),
includes.Select (i => new XElement (msbuild + $"Jdk{PropertyNameModifier}IncludePath", new XAttribute ("Include", i)))))),
new XElement (msbuild + "PropertyGroup",
new XElement (msbuild + "JavaSdkDirectory", new XAttribute ("Condition", " '$(JavaSdkDirectory)' == '' "),
JavaHomePath),
new XElement (msbuild + "JavaPath", new XAttribute ("Condition", " '$(JavaPath)' == '' "),
javaPath),
new XElement (msbuild + "JavaCPath", new XAttribute ("Condition", " '$(JavaCPath)' == '' "),
javacPath),
new XElement (msbuild + "JarPath", new XAttribute ("Condition", " '$(JarPath)' == '' "),
jarPath),
new XElement (msbuild + "DotnetToolPath", new XAttribute ("Condition", " '$(DotnetToolPath)' == '' "),
dotnet),
CreateJreRtJarPath (msbuild, rtJarPath)));
CreateProperty (msbuild, $"Java{PropertyNameModifier}SdkDirectory", JavaHomePath),
CreateProperty (msbuild, $"Java{PropertyNameModifier}Path", javaPath),
CreateProperty (msbuild, $"JavaC{PropertyNameModifier}Path", javacPath),
CreateProperty (msbuild, $"Jar{PropertyNameModifier}Path", jarPath),
CreateProperty (msbuild, $"Dotnet{PropertyNameModifier}ToolPath", DotnetToolPath),
CreateProperty (msbuild, $"Jre{PropertyNameModifier}RtJarPath", rtJarPath)));
project.Save (PropertyFile.ItemSpec);
}

static XElement CreateJreRtJarPath (XNamespace msbuild, string rtJarPath)
XElement CreateProperty (XNamespace msbuild, string propertyName, string propertyValue)
{
if (rtJarPath == null)
if (string.IsNullOrEmpty (propertyValue)) {
return null;
return new XElement (msbuild + "JreRtJarPath",
new XAttribute ("Condition", " '$(JreRtJarPath)' == '' "),
rtJarPath);
}
}

return new XElement (msbuild + propertyName,
new XAttribute ("Condition", $" '$({propertyName})' == '' "),
propertyValue);
}
void WriteMakeFragmentFile (string javaPath, string jarPath, string javacPath, string jdkJvmPath, string rtJarPath, IEnumerable<string> includes)
{
using (var o = new StreamWriter (MakeFragmentFile.ItemSpec)) {
Expand Down
11 changes: 11 additions & 0 deletions build-tools/scripts/Prepare.targets
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,16 @@
PropertyFile="$(_TopDir)\bin\Build$(Configuration)\JdkInfo.props">
<Output TaskParameter="JavaHomePath" PropertyName="_JavaSdkDirectory" />
</JdkInfo>
<PropertyGroup>
<JdksRoot Condition=" '$(JdksRoot)' == '' ">$(JAVA_HOME_11_X64)</JdksRoot>
</PropertyGroup>
<JdkInfo
JdksRoot="$(JdksRoot)"
PropertyNameModifier="11"
MinimumJdkVersion="11.0"
MaximumJdkVersion="11.99.0"
PropertyFile="$(_TopDir)\bin\Build$(Configuration)\JdkInfo-11.props">
<Output TaskParameter="JavaHomePath" PropertyName="Java11SdkDirectory"/>
</JdkInfo>
</Target>
</Project>
8 changes: 8 additions & 0 deletions build-tools/scripts/jdk.targets
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,13 @@
PropertyFile="$(MSBuildThisFileDirectory)..\..\bin\Build$(Configuration)\JdkInfo.props">
<Output TaskParameter="JavaHomePath" PropertyName="_JavaHome"/>
</JdkInfo>
<JdkInfo
JdksRoot="$(JdksRoot)"
PropertyNameModifier="11"
MinimumJdkVersion="11.0"
MaximumJdkVersion="11.99.0"
PropertyFile="$(MSBuildThisFileDirectory)..\..\bin\Build$(Configuration)\JdkInfo-11.props">
<Output TaskParameter="JavaHomePath" PropertyName="Java11SdkDirectory"/>
</JdkInfo>
</Target>
</Project>
10 changes: 4 additions & 6 deletions samples/Hello/Hello.csproj
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net472</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<OutputType>Exe</OutputType>
<OutputPath>..\..\bin\Test$(Configuration)</OutputPath>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<PropertyGroup>
<OutputPath>$(TestOutputFullPath)</OutputPath>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
Expand All @@ -18,6 +14,8 @@
<ItemGroup>
<ProjectReference Include="..\..\src\Java.Interop\Java.Interop.csproj" />
<ProjectReference Include="..\..\src\Java.Runtime.Environment\Java.Runtime.Environment.csproj" />
<ProjectReference Include="..\..\src\Java.Base\Java.Base.csproj" />
<ProjectReference Include="..\..\tests\TestJVM\TestJVM.csproj" />
</ItemGroup>

</Project>
56 changes: 49 additions & 7 deletions samples/Hello/Program.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,65 @@
using System;
using System.Threading;

using Mono.Options;

using Java.Interop;

namespace Hello
{
class MainClass
class App
{
public static unsafe void Main (string[] args)
public static void Main (string[] args)
{
Console.WriteLine ("Hello World!");
try {
var ignore = JniRuntime.CurrentRuntime;
} catch (InvalidOperationException e) {
Console.WriteLine (e);
string? jvmPath = global::Java.InteropTests.TestJVM.GetJvmLibraryPath ();
bool createMultipleVMs = false;
bool showHelp = false;
var options = new OptionSet () {
"Using the JVM from C#!",
"",
"Options:",
{ "jvm=",
$"{{PATH}} to JVM to use. Default is:\n {jvmPath}",
v => jvmPath = v },
{ "m",
"Create multiple Java VMs. This will likely creash.",
v => createMultipleVMs = v != null },
{ "h|help",
"Show this message and exit.",
v => showHelp = v != null },
};
options.Parse (args);
if (showHelp) {
options.WriteOptionDescriptions (Console.Out);
return;
}
Console.WriteLine ("Hello World!");
var builder = new JreRuntimeOptions () {
JniAddNativeMethodRegistrationAttributePresent = true,
JvmLibraryPath = jvmPath,
};
builder.AddOption ("-Xcheck:jni");
var jvm = builder.CreateJreVM ();
Console.WriteLine ($"JniRuntime.CurrentRuntime == jvm? {ReferenceEquals (JniRuntime.CurrentRuntime, jvm)}");
foreach (var h in JniRuntime.GetAvailableInvocationPointers ()) {
Console.WriteLine ("PRE: GetCreatedJavaVMHandles: {0}", h);
}

CreateJLO ();

if (createMultipleVMs) {
CreateAnotherJVM ();
}
}

static void CreateJLO ()
{
var jlo = new Java.Lang.Object ();
Console.WriteLine ($"binding? {jlo.ToString ()}");
}

static unsafe void CreateAnotherJVM ()
{
Console.WriteLine ("Part 2!");
using (var vm = new JreRuntimeOptions ().CreateJreVM ()) {
Console.WriteLine ("# JniEnvironment.EnvironmentPointer={0}", JniEnvironment.EnvironmentPointer);
Expand Down
17 changes: 17 additions & 0 deletions src/Java.Base/Java.Base.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Java.Interop\Java.Interop.csproj" />
<ProjectReference Include="..\..\tools\class-parse.csproj" ReferenceOutputAssembly="False" />
<ProjectReference Include="..\..\tools\generator.csproj" ReferenceOutputAssembly="False" />
</ItemGroup>

<Import Project="Java.Base.targets" />

</Project>
Loading

0 comments on commit fd966e3

Please sign in to comment.