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 `Java.Interop.Java*Array` types.
This should help reduce marshaling logic & overhead, as arrays don'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.

~~ 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 10, 2021
1 parent 13be0dd commit 06ec632
Show file tree
Hide file tree
Showing 424 changed files with 12,671 additions and 4,334 deletions.
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,17 +30,18 @@ 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 ())
.Where (j => minVersion != null ? j.Version >= minVersion : true)
.Where (j => maxVersion != null ? j.Version <= maxVersion : true)
.Where (j => j.IncludePath.Any ())
.FirstOrDefault ();
Expand All @@ -56,10 +59,12 @@ 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;
}
Expand Down Expand Up @@ -97,39 +102,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
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>
97 changes: 97 additions & 0 deletions src/Java.Base/Java.Base.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<Project>

<UsingTask TaskName="Java.Interop.BootstrapTasks.JdkInfo" AssemblyFile="$(MSBuildThisFileDirectory)..\..\bin\Build$(Configuration)\Java.Interop.BootstrapTasks.dll" />

<Import
Condition=" Exists('$(IntermediateOutputPath)jdk-11.props') "
Project="$(IntermediateOutputPath)jdk-11.props"
/>

<PropertyGroup>
<GeneratorPath>$(ToolOutputFullPath)generator.dll</GeneratorPath>
</PropertyGroup>

<Target Name="_FindJdk11">
<JdkInfo
Condition=" !Exists('$(IntermediateOutputPath)jdk-11.props') "
PropertyNameModifier="11"
JdksRoot="$(JdksRoot)"
MinimumJdkVersion="11.0"
MaximumJdkVersion="11.99.0"
PropertyFile="$(IntermediateOutputPath)jdk-11.props">
<Output TaskParameter="JavaHomePath" PropertyName="Java11SdkDirectory"/>
</JdkInfo>
</Target>

<Target Name="_GetJavaBaseJmodPath"
DependsOnTargets="_FindJdk11">
<PropertyGroup Condition=" '$(Java11SdkDirectory)' != '' ">
<_JavaBaseJmod>$(Java11SdkDirectory)/jmods/java.base.jmod</_JavaBaseJmod>
</PropertyGroup>
</Target>

<Target Name="_GenerateApiDescription"
DependsOnTargets="_GetJavaBaseJmodPath"
BeforeTargets="_GenerateBinding"
Inputs="$(_JavaBaseJmod)"
Outputs="$(IntermediateOutputPath)\mcw\api.xml">
<MakeDir Directories="$(IntermediateOutputPath)mcw" />
<PropertyGroup>
<_ClassParse>"$(ToolOutputFullPath)class-parse.dll"</_ClassParse>
<_Input>"$(_JavaBaseJmod)"</_Input>
<_Output>"-o=$(IntermediateOutputPath)/mcw/api.xml"</_Output>
</PropertyGroup>
<Error
Condition=" !Exists($(_JavaBaseJmod)) "
Text="Could not find a JDK-11 installation directory"
/>
<Exec
Command="$(DotnetToolPath) $(_ClassParse) $(_Input) $(_Output)"
/>
</Target>

<ItemGroup>
<_GenerateBindingInputs Include="$(GeneratorPath)" />
<_GenerateBindingInputs Include="Transforms\**" />
<_GenerateBindingInputs Include="$(IntermediateOutputPath)mcw\api.xml" />
</ItemGroup>

<Target Name="_GenerateBinding"
BeforeTargets="CoreCompile"
Inputs="@(_GenerateBindingInputs)"
Outputs="$(IntermediateOutputPath)mcw\Java.Base.projitems">
<MakeDir Directories="$(IntermediateOutputPath)mcw" />
<PropertyGroup>
<Generator>"$(GeneratorPath)"</Generator>
<_GenFlags>--public</_GenFlags>
<_Out>-o "$(IntermediateOutputPath)mcw"</_Out>
<_Codegen>--codegen-target=JavaInterop1</_Codegen>
<_Fixup>--fixup=Transforms/Metadata.xml</_Fixup>
<_Enums1>--preserve-enums --enumflags=Transforms/enumflags --enumfields=Transforms/map.csv --enummethods=Transforms/methodmap.csv</_Enums1>
<_Enums2>--enummetadata=$(IntermediateOutputPath)mcw/enummetadata</_Enums2>
<_Assembly>"--assembly=Java.Base"</_Assembly>
<_TypeMap>--type-map-report=$(IntermediateOutputPath)mcw/type-mapping.txt</_TypeMap>
<_Api>$(IntermediateOutputPath)mcw/api.xml</_Api>
<_Dirs>--enumdir=$(IntermediateOutputPath)mcw</_Dirs>
<_FullIntermediateOutputPath>$([System.IO.Path]::GetFullPath('$(IntermediateOutputPath)'))</_FullIntermediateOutputPath>
<_LangFeatures>--lang-features=nullable-reference-types,default-interface-methods,nested-interface-types,interface-constants</_LangFeatures>
</PropertyGroup>
<Exec
Command="$(DotnetToolPath) $(Generator) $(_GenFlags) $(_ApiLevel) $(_Out) $(_Codegen) $(_Fixup) $(_Enums1) $(_Enums2) $(_Versions) $(_Annotations) $(_Assembly) $(_TypeMap) $(_LangFeatures) $(_Dirs) $(_Api) $(_WithJavadocXml)"
IgnoreStandardErrorWarningFormat="True"
/>
<ItemGroup>
<Compile Include="$(_FullIntermediateOutputPath)\mcw\**\*.cs" KeepDuplicates="False" />
</ItemGroup>
<XmlPeek
Namespaces="&lt;Namespace Prefix='msbuild' Uri='http://schemas.microsoft.com/developer/msbuild/2003' /&gt;"
XmlInputPath="$(IntermediateOutputPath)mcw\Java.Base.projitems"
Query="/msbuild:Project/msbuild:PropertyGroup/msbuild:DefineConstants/text()" >
<Output TaskParameter="Result" PropertyName="_GeneratedDefineConstants" />
</XmlPeek>
<PropertyGroup>
<DefineConstants>$(DefineConstants);$([System.String]::Copy('$(_GeneratedDefineConstants)').Replace ('%24(DefineConstants);', ''))</DefineConstants>
</PropertyGroup>
</Target>

</Project>
14 changes: 14 additions & 0 deletions src/Java.Base/Java.Lang/Object.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using Java.Interop;

namespace Java.Lang {

public partial class Object : JavaObject {

public Object (ref JniObjectReference reference, JniObjectReferenceOptions options)
: base (ref reference, options)
{
}
}
}
14 changes: 14 additions & 0 deletions src/Java.Base/Java.Lang/Throwable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using Java.Interop;

namespace Java.Lang {

public partial class Throwable : JavaException {

public Throwable (ref JniObjectReference reference, JniObjectReferenceOptions options)
: base (ref reference, options)
{
}
}
}
10 changes: 10 additions & 0 deletions src/Java.Base/Transforms/Metadata.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<metadata>
<!-- For now, just bind java.lang.* -->
<remove-node path="//api/package[not(starts-with(@name, 'java.lang'))]" />

<!-- For now, just bind Class, Object, Throwable -->
<remove-node path="//api/package/interface" />
<remove-node path="//api/package/class[not(@name = 'Class' or @name = 'Object' or @name = 'Throwable')]" />

<attr path="/api/package[@name='java.lang']/class[@name='Object']/method[@name='finalize']" name="managedName">JavaFinalize</attr>
</metadata>
2 changes: 2 additions & 0 deletions src/Java.Base/Transforms/enumflags
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Java.Net.IDNFlags
Java.Util.FormatFlags
Loading

0 comments on commit 06ec632

Please sign in to comment.