diff --git a/Java.Interop.sln b/Java.Interop.sln
index 2abb51d62..d662692ca 100644
--- a/Java.Interop.sln
+++ b/Java.Interop.sln
@@ -109,6 +109,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Java.Base", "src\Java.Base\
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Java.Base-Tests", "tests\Java.Base-Tests\Java.Base-Tests.csproj", "{CB05E11B-B96F-4179-A4E9-5D6BDE29A8FC}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Java.Interop.Tools.Expressions", "src\Java.Interop.Tools.Expressions\Java.Interop.Tools.Expressions.csproj", "{1A0262FE-3CDB-4AF2-AAD8-65C59524FE8A}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Java.Interop.Tools.Expressions-Tests", "tests\Java.Interop.Tools.Expressions-Tests\Java.Interop.Tools.Expressions-Tests.csproj", "{211BAA88-66B1-41B2-88B2-530DBD8DF702}"
+EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Java.Interop.NamingCustomAttributes\Java.Interop.NamingCustomAttributes.projitems*{58b564a1-570d-4da2-b02d-25bddb1a9f4f}*SharedItemsImports = 5
@@ -308,6 +312,14 @@ Global
{CB05E11B-B96F-4179-A4E9-5D6BDE29A8FC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CB05E11B-B96F-4179-A4E9-5D6BDE29A8FC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CB05E11B-B96F-4179-A4E9-5D6BDE29A8FC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1A0262FE-3CDB-4AF2-AAD8-65C59524FE8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1A0262FE-3CDB-4AF2-AAD8-65C59524FE8A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1A0262FE-3CDB-4AF2-AAD8-65C59524FE8A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1A0262FE-3CDB-4AF2-AAD8-65C59524FE8A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {211BAA88-66B1-41B2-88B2-530DBD8DF702}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {211BAA88-66B1-41B2-88B2-530DBD8DF702}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {211BAA88-66B1-41B2-88B2-530DBD8DF702}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {211BAA88-66B1-41B2-88B2-530DBD8DF702}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -360,6 +372,8 @@ Global
{11942DE9-AEC2-4B95-87AB-CA707C37643D} = {271C9F30-F679-4793-942B-0D9527CB3E2F}
{30DCECA5-16FD-4FD0-883C-E5E83B11565D} = {0998E45F-8BCE-4791-A944-962CD54E2D80}
{CB05E11B-B96F-4179-A4E9-5D6BDE29A8FC} = {271C9F30-F679-4793-942B-0D9527CB3E2F}
+ {1A0262FE-3CDB-4AF2-AAD8-65C59524FE8A} = {0998E45F-8BCE-4791-A944-962CD54E2D80}
+ {211BAA88-66B1-41B2-88B2-530DBD8DF702} = {271C9F30-F679-4793-942B-0D9527CB3E2F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {29204E0C-382A-49A0-A814-AD7FBF9774A5}
diff --git a/Makefile b/Makefile
index 2bbf73873..8f31778fd 100644
--- a/Makefile
+++ b/Makefile
@@ -18,6 +18,8 @@ PREPARE_EXTERNAL_FILES = \
DEPENDENCIES = \
bin/Test$(CONFIGURATION)/libNativeTiming$(NATIVE_EXT)
+NET_SUFFIX = -net7.0
+
TESTS = \
bin/Test$(CONFIGURATION)/Java.Interop-Tests.dll \
bin/Test$(CONFIGURATION)/Java.Interop.Dynamic-Tests.dll \
@@ -33,7 +35,7 @@ TESTS = \
bin/Test$(CONFIGURATION)/Xamarin.SourceWriter-Tests.dll
NET_TESTS = \
- bin/Test$(CONFIGURATION)-net7.0/Java.Base-Tests.dll
+ bin/Test$(CONFIGURATION)$(NET_SUFFIX)/Java.Base-Tests.dll
PTESTS = \
bin/Test$(CONFIGURATION)/Java.Interop-PerformanceTests.dll
@@ -43,6 +45,10 @@ ATESTS = \
all: $(DEPENDENCIES) $(TESTS)
+bin/ilverify:
+ -mkdir bin
+ dotnet tool install --tool-path bin dotnet-ilverify
+
run-all-tests:
r=0; \
$(MAKE) run-tests || r=1 ; \
@@ -127,7 +133,7 @@ run-tests: $(TESTS) bin/Test$(CONFIGURATION)/$(JAVA_INTEROP_LIB)
$(foreach t,$(TESTS), $(call RUN_TEST,$(t),1)) \
exit $$r;
-run-net-tests: $(NET_TESTS) bin/Test$(CONFIGURATION)-net7.0/$(JAVA_INTEROP_LIB)
+run-net-tests: $(NET_TESTS) bin/Test$(CONFIGURATION)$(NET_SUFFIX)/$(JAVA_INTEROP_LIB)
r=0; \
$(foreach t,$(NET_TESTS), dotnet test $(t) || r=1) \
exit $$r;
@@ -150,15 +156,28 @@ $(JRE_DLL_CONFIG): src/Java.Runtime.Environment/Java.Runtime.Environment.csproj
define run-jnimarshalmethod-gen
MONO_TRACE_LISTENER=Console.Out \
- $(RUNTIME) bin/$(CONFIGURATION)/jnimarshalmethod-gen.exe -v --jvm "$(JI_JVM_PATH)" -L "$(JI_MONO_LIB_PATH)mono/4.5" -L "$(JI_MONO_LIB_PATH)mono/4.5/Facades" $(2) $(1)
+ dotnet bin/$(CONFIGURATION)$(NET_SUFFIX)/jnimarshalmethod-gen.dll $(2) $(1)
endef
-run-test-jnimarshal: bin/Test$(CONFIGURATION)/Java.Interop.Export-Tests.dll bin/Test$(CONFIGURATION)/$(JAVA_INTEROP_LIB) $(JRE_DLL_CONFIG)
+# want: /usr/local/share/dotnet/shared/Microsoft.NETCore.App/7.0.0
+# have: Microsoft.NETCore.App 7.0.0 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
+# use: shell pipeline!
+SYSTEM_NET_ASSEMBLIES_PATH := $(shell dotnet --list-runtimes | grep ^Microsoft.NETCore.App | tail -1 | sed -E 's,^Microsoft.NETCore.App ([^ ]+) \[([^]]+)\]$$,\2/\1,g' )
+
+run-test-jnimarshal: bin/Test$(CONFIGURATION)$(NET_SUFFIX)/Java.Interop.Export-Tests.dll bin/Test$(CONFIGURATION)$(NET_SUFFIX)/$(JAVA_INTEROP_LIB) bin/ilverify
mkdir -p test-jni-output
- $(call run-jnimarshalmethod-gen,"$<",-f -o test-jni-output --keeptemp)
- (test -f test-jni-output/$(notdir $<) && test -f test-jni-output/Java.Interop.Export-Tests-JniMarshalMethods.dll) || { echo "jnimarshalmethod-gen did not create the expected assemblies in the test-jni-output directory"; exit 1; }
+ # Do we run w/o error?
+ $(call run-jnimarshalmethod-gen,"$<", -v -v -o test-jni-output --keeptemp)
+ (test -f test-jni-output/$(notdir $<) ) || { echo "jnimarshalmethod-gen did not create the expected assemblies in the test-jni-output directory"; exit 1; }
+ # Is output valid?
+ ikdasm test-jni-output/Java.Interop.Export-Tests.dll || { echo "output can not be processed by ikdasm"; exit 1; }
+ bin/ilverify test-jni-output/Java.Interop.Export-Tests.dll \
+ --tokens --system-module System.Private.CoreLib -r '$(dir $<)/*.dll' \
+ -r '$(SYSTEM_NET_ASSEMBLIES_PATH)/*.dll' || { echo "ilverify found issues"; exit 1; }
+ # replace "original" assembly
$(call run-jnimarshalmethod-gen,"$<")
- $(call RUN_TEST,$<)
+ # make sure tests still pass
+ dotnet test $<
bin/Test$(CONFIGURATION)/generator.exe: bin/$(CONFIGURATION)/generator.exe
cp $<* `dirname "$@"`
diff --git a/build-tools/automation/templates/core-tests.yaml b/build-tools/automation/templates/core-tests.yaml
index 5f56f1797..3ec4ee8f6 100644
--- a/build-tools/automation/templates/core-tests.yaml
+++ b/build-tools/automation/templates/core-tests.yaml
@@ -105,13 +105,31 @@ steps:
- task: DotNetCoreCLI@2
displayName: 'Tests: Java.Interop.Export'
- condition: eq('${{ parameters.runNativeTests }}', 'true')
+ condition: or(eq('${{ parameters.runNativeDotnetTests }}', 'true'), eq('${{ parameters.runNativeTests }}', 'true'))
inputs:
command: test
testRunTitle: Java.Interop.Export (${{ parameters.platformName }})
arguments: bin/Test$(Build.Configuration)$(NetCoreTargetFrameworkPathSuffix)/Java.Interop.Export-Tests.dll
continueOnError: true
+- task: DotNetCoreCLI@2
+ displayName: 'jnimarshalmethod-gen Java.Interop.Export-Tests.dll'
+ condition: or(eq('${{ parameters.runNativeDotnetTests }}', 'true'), eq('${{ parameters.runNativeTests }}', 'true'))
+ inputs:
+ command: custom
+ custom: bin/$(Build.Configuration)$(NetCoreTargetFrameworkPathSuffix)/jnimarshalmethod-gen.dll
+ arguments: bin/Test$(Build.Configuration)$(NetCoreTargetFrameworkPathSuffix)/Java.Interop.Export-Tests.dll -v -v --keeptemp -o bin/Test$(Build.Configuration)$(NetCoreTargetFrameworkPathSuffix)
+ continueOnError: true
+
+- task: DotNetCoreCLI@2
+ displayName: 'Tests: Java.Interop.Export w/ jnimarshalmethod-gen!'
+ condition: or(eq('${{ parameters.runNativeDotnetTests }}', 'true'), eq('${{ parameters.runNativeTests }}', 'true'))
+ inputs:
+ command: test
+ testRunTitle: Java.Interop.Export (jnimarshalmethod-gen + ${{ parameters.platformName }})
+ arguments: bin/Test$(Build.Configuration)$(NetCoreTargetFrameworkPathSuffix)/Java.Interop.Export-Tests.dll
+ continueOnError: true
+
- task: DotNetCoreCLI@2
displayName: 'Tests: Java.Interop-Performance-net472'
condition: eq('${{ parameters.runNativeTests }}', 'true')
diff --git a/src/Java.Base-ref.cs b/src/Java.Base-ref.cs
index 973dc50b7..7f42f5710 100644
--- a/src/Java.Base-ref.cs
+++ b/src/Java.Base-ref.cs
@@ -6408,7 +6408,7 @@ public partial class AccessibleObject : Java.Lang.Object, Java.Interop.IJavaPeer
{
protected AccessibleObject() { }
protected AccessibleObject(ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions options) { }
- public virtual bool Accessible { get { throw null; } set { } }
+ public virtual bool Accessible { [System.ObsoleteAttribute("deprecated")] get { throw null; } set { } }
[System.ComponentModel.EditorBrowsableAttribute(1)]
[System.Diagnostics.DebuggerBrowsableAttribute(0)]
public override Java.Interop.JniPeerMembers JniPeerMembers { get { throw null; } }
diff --git a/src/Java.Interop.Export/Java.Interop.Export.csproj b/src/Java.Interop.Export/Java.Interop.Export.csproj
index 4e797ba0b..e9896daef 100644
--- a/src/Java.Interop.Export/Java.Interop.Export.csproj
+++ b/src/Java.Interop.Export/Java.Interop.Export.csproj
@@ -2,7 +2,7 @@
netstandard2.0;$(DotNetTargetFramework)
- 8.0
+ 9.0
{B501D075-6183-4E1D-92C9-F7B5002475B1}
enable
true
@@ -23,4 +23,4 @@
-
\ No newline at end of file
+
diff --git a/src/Java.Interop.Export/Java.Interop/MarshalMemberBuilder.cs b/src/Java.Interop.Export/Java.Interop/MarshalMemberBuilder.cs
index 4cb7d6724..bd5cf7602 100644
--- a/src/Java.Interop.Export/Java.Interop/MarshalMemberBuilder.cs
+++ b/src/Java.Interop.Export/Java.Interop/MarshalMemberBuilder.cs
@@ -84,20 +84,6 @@ public string GetJniMethodSignature (JavaCallableAttribute export, MethodInfo me
return export.Signature = GetJniMethodSignature (method);
}
- string GetTypeSignature (ParameterInfo p)
- {
- var info = Runtime.TypeManager.GetTypeSignature (p.ParameterType);
- if (info.IsValid)
- return info.QualifiedReference;
-
- var marshaler = GetParameterMarshaler (p);
- info = Runtime.TypeManager.GetTypeSignature (marshaler.MarshalType);
- if (info.IsValid)
- return info.QualifiedReference;
-
- throw new NotSupportedException ("Don't know how to determine JNI signature for parameter type: " + p.ParameterType.FullName + ".");
- }
-
Delegate CreateJniMethodMarshaler (MethodInfo method, JavaCallableAttribute? export, Type? type)
{
var e = CreateMarshalToManagedExpression (method, export, type);
@@ -242,6 +228,7 @@ public LambdaExpression CreateMarshalToManagedExpression (MethodInfo method, Jav
: Expression.Lambda (marshalerType, body, bodyParams);
}
+ // Keep in sync with ExpressionAssemblyBuilder.GetMarshalMethodDelegateType()
static Type? GetMarshalerType (Type? returnType, List funcTypeParams, Type? declaringType)
{
// Too many parameters; does a `_JniMarshal_*` type exist in the type's declaring assembly?
@@ -277,6 +264,7 @@ public LambdaExpression CreateMarshalToManagedExpression (MethodInfo method, Jav
static AssemblyBuilder? assemblyBuilder;
static ModuleBuilder? moduleBuilder;
static Type[]? DelegateCtorSignature;
+ static Dictionary? marshalDelegateTypes;
static Type? CreateMarshalDelegateType (string name, Type? returnType, List funcTypeParams)
{
@@ -290,6 +278,10 @@ public LambdaExpression CreateMarshalToManagedExpression (MethodInfo method, Jav
typeof (object),
typeof (IntPtr)
};
+ marshalDelegateTypes = new ();
+ }
+ if (marshalDelegateTypes!.TryGetValue (name, out var type)) {
+ return type;
}
funcTypeParams.Insert (0, typeof (IntPtr));
funcTypeParams.Insert (0, typeof (IntPtr));
@@ -307,7 +299,9 @@ public LambdaExpression CreateMarshalToManagedExpression (MethodInfo method, Jav
.SetImplementationFlags (ImplAttributes);
typeBuilder.DefineMethod ("Invoke", InvokeAttributes, returnType, funcTypeParams.ToArray ())
.SetImplementationFlags (ImplAttributes);
- return typeBuilder.CreateTypeInfo ();
+ var marshalDelType = typeBuilder.CreateTypeInfo ();
+ marshalDelegateTypes.Add (name, marshalDelType);
+ return marshalDelType;
}
}
#endif // NET
diff --git a/src/Java.Interop.Tools.Expressions/Java.Interop.Tools.Expressions.csproj b/src/Java.Interop.Tools.Expressions/Java.Interop.Tools.Expressions.csproj
new file mode 100644
index 000000000..b8b67337b
--- /dev/null
+++ b/src/Java.Interop.Tools.Expressions/Java.Interop.Tools.Expressions.csproj
@@ -0,0 +1,27 @@
+
+
+
+ $(DotNetTargetFramework)
+ enable
+ enable
+
+
+
+
+
+ $(UtilityOutputFullPath)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Java.Interop.Tools.Expressions/Java.Interop.Tools.Expressions/CecilCompilerExpressionVisitor.cs b/src/Java.Interop.Tools.Expressions/Java.Interop.Tools.Expressions/CecilCompilerExpressionVisitor.cs
new file mode 100644
index 000000000..ac5158657
--- /dev/null
+++ b/src/Java.Interop.Tools.Expressions/Java.Interop.Tools.Expressions/CecilCompilerExpressionVisitor.cs
@@ -0,0 +1,827 @@
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Linq.Expressions;
+
+using Mono.Cecil;
+using Mono.Cecil.Cil;
+
+namespace Java.Interop.Tools.Expressions;
+
+class CecilCompilerExpressionVisitor : ExpressionVisitor
+{
+ public CecilCompilerExpressionVisitor (AssemblyDefinition declaringAssembly, MethodBody body, VariableDefinitions variables, Action logger)
+ {
+ this.assemblyDef = declaringAssembly;
+ this.body = body;
+ this.variables = variables;
+ il = body.GetILProcessor ();
+ Logger = logger;
+ }
+
+ AssemblyDefinition assemblyDef;
+ MethodBody body;
+ ILProcessor il;
+ VariableDefinitions variables;
+ Dictionary> returnFixups = new ();
+ Action Logger;
+
+ ///
+ /// Dispatches the expression to one of the more specialized visit methods in this class.
+ ///
+ /// The expression to visit.
+ /// The modified expression, if it or any subexpression was modified;
+ /// otherwise, returns the original expression.
+ [return: NotNullIfNotNull("node")]
+ public override Expression? Visit (
+ Expression? node)
+ {
+ Logger (TraceLevel.Verbose, $"# jonp: CecilCompilerExpressionVisitor.Visit [{node?.NodeType.ToString () ?? ""}]: {node}");
+ return base.Visit (node);
+ }
+
+ ///
+ /// Visits the children of the .
+ ///
+ /// The expression to visit.
+ /// The modified expression, if it or any subexpression was modified;
+ /// otherwise, returns the original expression.
+ protected override Expression VisitBinary (
+ BinaryExpression node)
+ {
+ Logger (TraceLevel.Verbose, $"# jonp: CecilCompilerExpressionVisitor.VisitBinary: {node} [{node.NodeType}]");
+ switch (node.NodeType) {
+ case ExpressionType.Assign:
+ var target = node.Left as ParameterExpression;
+ if (target == null) {
+ Logger (TraceLevel.Verbose, $"# jonp: don't know where to assign `{node.Left}`!");
+ return base.VisitBinary (node);
+ }
+ Logger (TraceLevel.Verbose, $"# jonp: target={target}; target.Type={target.Type}; requires-&? {InstanceInvokeRequiresAddress (target.Type)}");
+ if (InstanceInvokeRequiresAddress (target.Type) && node.Right is NewExpression n) {
+ variables [target].LoadAddress (il);
+ Visit (node.Right);
+ } else {
+ Visit (node.Right);
+ variables [target].Store (il);
+ }
+ break;
+ case ExpressionType.Equal:
+ Visit (node.Left);
+ Visit (node.Right);
+ il.Emit (OpCodes.Ceq);
+ break;
+ default:
+ Logger (TraceLevel.Verbose, $"# jonp: don't know how to emit binary expr {node.NodeType}!");
+ base.VisitBinary (node);
+ break;
+ }
+ return node;
+ }
+
+ static bool InstanceInvokeRequiresAddress (Type type) => type.IsValueType && !type.IsPrimitive;
+
+ ///
+ /// Visits the children of the .
+ ///
+ /// The expression to visit.
+ /// The modified expression, if it or any subexpression was modified;
+ /// otherwise, returns the original expression.
+ protected override Expression VisitBlock (
+ BlockExpression node)
+ {
+ // Base method also visits parameter nodes after body; we don't want that.
+ // https://cs.github.com/dotnet/runtime/blob/9df6ea21007319967975dc9985413bb6518287da/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/ExpressionVisitor.cs#L214
+ // return base.VisitBlock (node);
+ Logger (TraceLevel.Verbose, $"# jonp: CecilCompilerExpressionVisitor.VisitBlock: {node}");
+ foreach (var e in node.Expressions) {
+ Visit (e);
+ }
+ return node;
+ }
+
+ ///
+ /// Visits the children of the .
+ ///
+ /// The expression to visit.
+ /// The modified expression, if it or any subexpression was modified;
+ /// otherwise, returns the original expression.
+ protected override Expression VisitConditional (
+ ConditionalExpression node)
+ {
+ Logger (TraceLevel.Verbose, $"# jonp: CecilCompilerExpressionVisitor.VisitConditional: {node}");
+ Visit (node.Test);
+ var startFalse = il.Create (OpCodes.Nop);
+ var endBranch = il.Create (OpCodes.Nop);
+ il.Emit (OpCodes.Brfalse, startFalse);
+ Visit (node.IfTrue);
+ il.Emit (OpCodes.Br, endBranch);
+ il.Append (startFalse);
+ Visit (node.IfFalse);
+ il.Append (endBranch);
+ return node;
+ // return base.VisitConditional (node);
+ }
+
+ ///
+ /// Visits the .
+ ///
+ /// The expression to visit.
+ /// The modified expression, if it or any subexpression was modified;
+ /// otherwise, returns the original expression.
+ protected override Expression VisitConstant (
+ ConstantExpression node)
+ {
+ Logger (TraceLevel.Verbose, $"# jonp: CecilCompilerExpressionVisitor.VisitConstant: {node}");
+ switch (Type.GetTypeCode (node.Type)) {
+ case TypeCode.String:
+ il.Emit (OpCodes.Ldstr, (string?) node.Value);
+ break;
+ case TypeCode.Boolean:
+ if ((bool) node.Value!) {
+ il.Emit (OpCodes.Ldc_I4_1);
+ } else {
+ il.Emit (OpCodes.Ldc_I4_0);
+ }
+ break;
+ case TypeCode.Char:
+ il.Emit (OpCodes.Ldc_I4, (char) node.Value!);
+ break;
+ case TypeCode.SByte:
+ il.Emit (OpCodes.Ldc_I4_S, (sbyte) node.Value!);
+ break;
+ case TypeCode.Byte:
+ il.Emit (OpCodes.Ldc_I4, (byte) node.Value!);
+ break;
+ case TypeCode.Int16:
+ il.Emit (OpCodes.Ldc_I4, (short) node.Value!);
+ break;
+ case TypeCode.Int32:
+ il.Emit (OpCodes.Ldc_I4, (int) node.Value!);
+ break;
+ case TypeCode.Int64:
+ il.Emit (OpCodes.Ldc_I8, (long) node.Value!);
+ break;
+ case TypeCode.Single:
+ il.Emit (OpCodes.Ldc_R4, (float) node.Value!);
+ break;
+ case TypeCode.Double:
+ il.Emit (OpCodes.Ldc_R8, (double) node.Value!);
+ break;
+ case TypeCode.UInt16:
+ il.Emit (OpCodes.Ldc_I4, (short) node.Value!);
+ break;
+ case TypeCode.UInt32:
+ il.Emit (OpCodes.Ldc_I4, (int) node.Value!);
+ break;
+ case TypeCode.UInt64:
+ il.Emit (OpCodes.Ldc_I8, (int) node.Value!);
+ break;
+ case TypeCode.Object:
+ if (node.Type == typeof (Type)) {
+ Logger (TraceLevel.Verbose, $"# jonp: TODO load type {node.Value}");
+ break;
+ } else if (node.Value == null) {
+ Logger (TraceLevel.Verbose, $"# jonp: TODO ldnull {node.Value}");
+ il.Emit (OpCodes.Ldnull);
+ break;
+ }
+ goto default;
+ default:
+ Logger (TraceLevel.Verbose, $"# jonp: don't know how to deal with constant with value `{node}` NodeType `{node.NodeType}` Type `{node.Type}` typecode {Type.GetTypeCode (node.Type)}");
+ break;
+ // throw new NotSupportedException ();
+ }
+ return node;
+ }
+
+ ///
+ /// Visits the .
+ ///
+ /// The expression to visit.
+ /// The modified expression, if it or any subexpression was modified;
+ /// otherwise, returns the original expression.
+ protected override Expression VisitDebugInfo (
+ DebugInfoExpression node)
+ {
+ Logger (TraceLevel.Verbose, $"# jonp: CecilCompilerExpressionVisitor.VisitDebugInfo: {node}");
+ return base.VisitDebugInfo (node);
+ }
+
+ ///
+ /// Visits the .
+ ///
+ /// The expression to visit.
+ /// The modified expression, if it or any subexpression was modified;
+ /// otherwise, returns the original expression.
+ protected override Expression VisitDefault (
+ DefaultExpression node)
+ {
+ Logger (TraceLevel.Verbose, $"# jonp: CecilCompilerExpressionVisitor.VisitDefault: {node}");
+ return base.VisitDefault (node);
+ }
+
+ ///
+ /// Visits the children of the extension expression.
+ ///
+ /// The expression to visit.
+ /// The modified expression, if it or any subexpression was modified;
+ /// otherwise, returns the original expression.
+ ///
+ /// This can be overridden to visit or rewrite specific extension nodes.
+ /// If it is not overridden, this method will call ,
+ /// which gives the node a chance to walk its children. By default,
+ /// will try to reduce the node.
+ ///
+ protected override Expression VisitExtension (
+ Expression node)
+ {
+ Logger (TraceLevel.Verbose, $"# jonp: CecilCompilerExpressionVisitor.VisitExtension: {node}");
+ return base.VisitExtension (node);
+ }
+
+ ///
+ /// Visits the children of the .
+ ///
+ /// The expression to visit.
+ /// The modified expression, if it or any subexpression was modified;
+ /// otherwise, returns the original expression.
+ protected override Expression VisitGoto (
+ GotoExpression node)
+ {
+ Logger (TraceLevel.Verbose, $"# jonp: CecilCompilerExpressionVisitor.VisitGoto: {node}");
+ if (node.Kind != GotoExpressionKind.Return || node.Type == typeof (void)) {
+ return base.VisitGoto (node);
+ }
+ Visit (node.Value);
+ variables.ReturnValue?.Store (il);
+ il.Emit (OpCodes.Ret);
+ List fixups = GetFixupsForLabelTarget (node.Target);
+ fixups.Add (il.Body.Instructions.Last ());
+ Logger (TraceLevel.Verbose, $"# jonp: adding fixup for goto `{node}` at index {il.Body.Instructions.Count-1}");
+ return node;
+ }
+
+ ///
+ /// Visits the children of the .
+ ///
+ /// The expression to visit.
+ /// The modified expression, if it or any subexpression was modified;
+ /// otherwise, returns the original expression.
+ protected override Expression VisitInvocation (
+ InvocationExpression node)
+ {
+ Logger (TraceLevel.Verbose, $"# jonp: CecilCompilerExpressionVisitor.VisitInvocation: {node}");
+ return base.VisitInvocation (node);
+ }
+
+ ///
+ /// Visits the .
+ ///
+ /// The expression to visit.
+ /// The modified expression, if it or any subexpression was modified;
+ /// otherwise, returns the original expression.
+ [return: NotNullIfNotNull("node")]
+ protected override LabelTarget? VisitLabelTarget (
+ LabelTarget? node)
+ {
+ Logger (TraceLevel.Verbose, $"# jonp: CecilCompilerExpressionVisitor.VisitLabelTarget: {node}");
+ if (node != null) {
+ il.Emit (OpCodes.Nop);
+ GetFixupsForLabelTarget (node).Add (il.Body.Instructions.Last ());
+ }
+ return base.VisitLabelTarget (node);
+ }
+
+ List GetFixupsForLabelTarget (LabelTarget target)
+ {
+ List? fixups;
+ if (!returnFixups.TryGetValue (target, out fixups)) {
+ returnFixups.Add (target, fixups = new ());
+ }
+ return fixups;
+ }
+
+ ///
+ /// Visits the children of the .
+ ///
+ /// The expression to visit.
+ /// The modified expression, if it or any subexpression was modified;
+ /// otherwise, returns the original expression.
+ protected override Expression VisitLabel (
+ LabelExpression node)
+ {
+ Logger (TraceLevel.Verbose, $"# jonp: CecilCompilerExpressionVisitor.VisitLabel: {node}");
+ var target = il.Body.Instructions.Last ();
+ if (returnFixups.TryGetValue (node.Target, out var fixups)) {
+ foreach (var replace in fixups) {
+ Logger (TraceLevel.Verbose, $"# jonp: VisitLabel: replacing instruction `{replace}` w/ `leave {target}");
+ Debug.Assert (replace.OpCode == OpCodes.Ret || replace.OpCode == OpCodes.Nop);
+ replace.OpCode = OpCodes.Leave;
+ replace.Operand = target;
+ }
+ }
+ return base.VisitLabel (node);
+ }
+
+ ///
+ /// Visits the children of the .
+ ///
+ /// The type of the delegate.
+ /// The expression to visit.
+ /// The modified expression, if it or any subexpression was modified;
+ /// otherwise, returns the original expression.
+ protected override Expression VisitLambda(Expression node)
+ {
+ Logger (TraceLevel.Verbose, $"# jonp: CecilCompilerExpressionVisitor.VisitLambda: {node}");
+ return Visit (node.Body);
+ // Base method also visits parameter nodes after body; we don't want that.
+ // return base.VisitLambda (node);
+ }
+
+ ///
+ /// Visits the children of the .
+ ///
+ /// The expression to visit.
+ /// The modified expression, if it or any subexpression was modified;
+ /// otherwise, returns the original expression.
+ protected override Expression VisitLoop (
+ LoopExpression node)
+ {
+ Logger (TraceLevel.Verbose, $"# jonp: CecilCompilerExpressionVisitor.VisitLoop: {node}");
+ return base.VisitLoop (node);
+ }
+
+ ///
+ /// Visits the children of the .
+ ///
+ /// The expression to visit.
+ /// The modified expression, if it or any subexpression was modified;
+ /// otherwise, returns the original expression.
+ protected override Expression VisitMember (
+ MemberExpression node)
+ {
+ Logger (TraceLevel.Verbose, $"# jonp: CecilCompilerExpressionVisitor.VisitMember: {node}");
+ base.VisitMember (node);
+ switch (node.Member.MemberType) {
+ case System.Reflection.MemberTypes.Field:
+ var field = (System.Reflection.FieldInfo) node.Member;
+ il.Emit (
+ field.IsStatic ? OpCodes.Ldsfld : OpCodes.Ldfld,
+ assemblyDef.MainModule.ImportReference (field));
+ break;
+ case System.Reflection.MemberTypes.Property:
+ var property = (System.Reflection.PropertyInfo) node.Member;
+ var getter = property.GetGetMethod ();
+ il.Emit (GetCallOpCode (getter!), assemblyDef.MainModule.ImportReference (getter));
+ break;
+ default:
+ throw new NotSupportedException ($"How do I visit `{node.Member.MemberType}`? {node}");
+ }
+ return node;
+ }
+
+ ///
+ /// Visits the children of the .
+ ///
+ /// The expression to visit.
+ /// The modified expression, if it or any subexpression was modified;
+ /// otherwise, returns the original expression.
+ protected override Expression VisitIndex (
+ IndexExpression node)
+ {
+ Logger (TraceLevel.Verbose, $"# jonp: CecilCompilerExpressionVisitor.VisitIndex: {node}");
+ return base.VisitIndex (node);
+ }
+
+ ///
+ /// Visits the children of the .
+ ///
+ /// The expression to visit.
+ /// The modified expression, if it or any subexpression was modified;
+ /// otherwise, returns the original expression.
+ protected override Expression VisitMethodCall (
+ MethodCallExpression node)
+ {
+ Logger (TraceLevel.Verbose, $"# jonp: CecilCompilerExpressionVisitor.VisitMethodCall: {node}; node.Object={node.Object}");
+ // We need to special-case `node.Object` handling
+ // https://github.com/dotnet/runtime/blob/edd23fcb1b350cb1a53fa409200da55e9c33e99e/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/ExpressionVisitor.cs#L403-L413
+
+ if (node.Object is ParameterExpression target) {
+ if (InstanceInvokeRequiresAddress (target.Type)) {
+ variables [target].LoadAddress (il);
+ } else {
+ variables [target].Load (il);
+ }
+ } else {
+ Visit (node.Object);
+ }
+ foreach (var a in node.Arguments) {
+ Visit (a);
+ }
+ il.Emit (GetCallOpCode (node.Method), assemblyDef.MainModule.ImportReference (node.Method));
+
+ return node;
+ }
+
+ OpCode GetCallOpCode (global::System.Reflection.MethodBase method)
+ {
+ if (method.IsStatic || (method.DeclaringType?.IsValueType ?? false))
+ return OpCodes.Call;
+ return OpCodes.Callvirt;
+ }
+
+ void EmitConsoleWriteLine (ILProcessor il, string message)
+ {
+ Action cwl = Console.WriteLine;
+ il.Emit (OpCodes.Ldstr, message);
+ il.Emit (OpCodes.Call, assemblyDef.MainModule.ImportReference (cwl.Method));
+ }
+
+ ///
+ /// Visits the children of the .
+ ///
+ /// The expression to visit.
+ /// The modified expression, if it or any subexpression was modified;
+ /// otherwise, returns the original expression.
+ protected override Expression VisitNewArray (
+ NewArrayExpression node)
+ {
+ Logger (TraceLevel.Verbose, $"# jonp: CecilCompilerExpressionVisitor.VisitNewArray: {node}");
+ return base.VisitNewArray (node);
+ }
+
+ ///
+ /// Visits the children of the .
+ ///
+ /// The expression to visit.
+ /// The modified expression, if it or any subexpression was modified;
+ /// otherwise, returns the original expression.
+ protected override Expression VisitNew (
+ NewExpression node)
+ {
+ Logger (TraceLevel.Verbose, $"# jonp: CecilCompilerExpressionVisitor.VisitNew: {node}");
+ Logger (TraceLevel.Verbose, $"# jonp: CecilCompilerExpressionVisitor.VisitNew: ctor={node.Constructor} {node.Constructor != null}");
+ base.VisitNew (node);
+ if (node.Constructor == null && node.Type.IsValueType) {
+ il.Emit (OpCodes.Initobj, assemblyDef.MainModule.ImportReference (node.Type));
+ } else {
+ il.Emit (OpCodes.Call, assemblyDef.MainModule.ImportReference (node.Constructor));
+ }
+ return node;
+ }
+
+ ///
+ /// Visits the .
+ ///
+ /// The expression to visit.
+ /// The modified expression, if it or any subexpression was modified;
+ /// otherwise, returns the original expression.
+ protected override Expression VisitParameter (
+ ParameterExpression node)
+ {
+ Logger (TraceLevel.Verbose, $"# jonp: CecilCompilerExpressionVisitor.VisitParameter: {(node.Type.IsByRef ? "&" : "")}{node}");
+
+ if (node.Type.IsByRef) {
+ variables [node].LoadAddress (il);
+ } else {
+ variables [node].Load (il);
+ }
+
+ return node;
+ }
+
+ ///
+ /// Visits the children of the .
+ ///
+ /// The expression to visit.
+ /// The modified expression, if it or any subexpression was modified;
+ /// otherwise, returns the original expression.
+ protected override Expression VisitRuntimeVariables (
+ RuntimeVariablesExpression node)
+ {
+ Logger (TraceLevel.Verbose, $"# jonp: CecilCompilerExpressionVisitor.VisitRuntimeVariables: {node}");
+ return base.VisitRuntimeVariables (node);
+ }
+
+ ///
+ /// Visits the children of the .
+ ///
+ /// The expression to visit.
+ /// The modified expression, if it or any subexpression was modified;
+ /// otherwise, returns the original expression.
+ protected override SwitchCase VisitSwitchCase (
+ SwitchCase node)
+ {
+ Logger (TraceLevel.Verbose, $"# jonp: CecilCompilerExpressionVisitor.VisitSwitchCase: {node}");
+ return base.VisitSwitchCase (node);
+ }
+
+ ///
+ /// Visits the children of the .
+ ///
+ /// The expression to visit.
+ /// The modified expression, if it or any subexpression was modified;
+ /// otherwise, returns the original expression.
+ protected override Expression VisitSwitch (
+ SwitchExpression node)
+ {
+ Logger (TraceLevel.Verbose, $"# jonp: CecilCompilerExpressionVisitor.VisitSwitch: {node}");
+ return base.VisitSwitch (node);
+ }
+
+ ///
+ /// Visits the children of the .
+ ///
+ /// The expression to visit.
+ /// The modified expression, if it or any subexpression was modified;
+ /// otherwise, returns the original expression.
+ protected override CatchBlock VisitCatchBlock (
+ CatchBlock node)
+ {
+ // On entry, IL stream should assume that there is an Exception type on the evaluation stack.
+
+ Logger (TraceLevel.Verbose, $"# jonp: CecilCompilerExpressionVisitor.VisitCatchBlock: {node}");
+
+ var startCatchBlock = il.Body.Instructions.Count;
+ var handlerDef = new ExceptionHandler (ExceptionHandlerType.Catch) {
+ TryStart = TryStart,
+ };
+ body.ExceptionHandlers.Add (handlerDef);
+
+ if (node.Filter != null) {
+ EmitCatchFilter (node);
+ handlerDef.HandlerType = ExceptionHandlerType.Filter;
+ handlerDef.FilterStart = il.Body.Instructions [startCatchBlock];
+ startCatchBlock = il.Body.Instructions.Count;
+ } else if (node.Test != null) {
+ handlerDef.CatchType = assemblyDef.MainModule.ImportReference (node.Test);
+ }
+
+ if (node.Variable != null) {
+ il.Emit (OpCodes.Castclass, assemblyDef.MainModule.ImportReference (node.Variable.Type));
+ variables [node.Variable!].Store (il);
+ } else {
+ il.Emit (OpCodes.Pop);
+ }
+
+ Visit (node.Body);
+ EmitLeave ();
+
+ handlerDef.HandlerStart = il.Body.Instructions [startCatchBlock];
+
+ return node;
+ }
+
+ void EmitCatchFilter (CatchBlock node)
+ {
+ Instruction? fixupStartFilter = null;
+ Instruction? fixupEndFilter = null;
+
+ if (node.Test != null) {
+ il.Emit (OpCodes.Isinst, assemblyDef.MainModule.ImportReference (node.Test));
+ il.Emit (OpCodes.Dup);
+ il.Emit (OpCodes.Brtrue_S, il.Body.Instructions.Last ());
+ fixupStartFilter = il.Body.Instructions.Last ();
+ il.Emit (OpCodes.Pop);
+ il.Emit (OpCodes.Ldc_I4_0);
+ il.Emit (OpCodes.Br_S, il.Body.Instructions.Last ());
+ fixupEndFilter = il.Body.Instructions.Last ();
+ }
+
+ if (node.Variable != null) {
+ variables [node.Variable!].Store (il);
+ } else {
+ il.Emit (OpCodes.Pop);
+ }
+
+ if (fixupStartFilter != null) {
+ fixupStartFilter.Operand = il.Body.Instructions.Last ();
+ }
+
+ Visit (node.Filter);
+
+ // node.Filter is assumed to leave a "boolean" on the eval stack; convert to an int
+ il.Emit (OpCodes.Ldc_I4_0);
+ il.Emit (OpCodes.Cgt_Un);
+
+ il.Emit (OpCodes.Endfilter);
+
+ if (fixupEndFilter != null) {
+ fixupEndFilter.Operand = il.Body.Instructions.Last ();
+ }
+ }
+
+ Instruction? TryStart;
+ List? FixupLeaveOffsets;
+
+
+ ///
+ /// Visits the children of the .
+ ///
+ /// The expression to visit.
+ /// The modified expression, if it or any subexpression was modified;
+ /// otherwise, returns the original expression.
+ protected override Expression VisitTry (
+ TryExpression node)
+ {
+ Logger (TraceLevel.Verbose, $"# jonp: CecilCompilerExpressionVisitor.VisitTry: {node}");
+
+ var prevTryStart = TryStart;
+ var pFixupLeaveOffsets = FixupLeaveOffsets;
+ try {
+ var startTryBlock = il.Body.Instructions.Count;
+ FixupLeaveOffsets = new ();
+
+ Visit (node.Body);
+ EmitLeave ();
+ TryStart = il.Body.Instructions [startTryBlock];
+
+ Visit (node.Handlers, VisitCatchBlock);
+
+ if (node.Finally != null) {
+ var startFinallyBlock = il.Body.Instructions.Count;
+ Visit (node.Finally);
+ il.Emit (OpCodes.Endfinally);
+
+ var finallyDef = new ExceptionHandler (ExceptionHandlerType.Finally) {
+ TryStart = TryStart,
+ HandlerStart = il.Body.Instructions [startFinallyBlock],
+ };
+ body.ExceptionHandlers.Add (finallyDef);
+ }
+
+ // Visit (node.Fault);
+
+ // ECMA 335 Partition X § 19 Exception Handling
+ // HandlerBlock ::= `handler` Label to Label
+ // Handler range is from first label ***prior to*** second (emphasis @jonpryor)
+ // Therefore we need to append `NOP` to the IL stream so that the fixupTarget is
+ // one-past-the-end, as nothing afterward has yet been emitted.
+
+ il.Emit (OpCodes.Nop);
+ var fixupTarget = il.Body.Instructions.Last ();
+
+ for (int i = 0; i < (body.ExceptionHandlers.Count-1); ++i) {
+ var c = body.ExceptionHandlers [i];
+ var n = body.ExceptionHandlers [i+1];
+ c.TryEnd = c.FilterStart ?? c.HandlerStart;
+ c.HandlerEnd = n.FilterStart ?? n.HandlerStart;
+ }
+ if (body.ExceptionHandlers.Count > 0) {
+ var f = body.ExceptionHandlers [body.ExceptionHandlers.Count-1];
+ f.TryEnd = f.HandlerStart;
+ f.HandlerEnd = fixupTarget;
+ }
+ foreach (var fixup in FixupLeaveOffsets) {
+ fixup.Operand = fixupTarget;
+ }
+ }
+ finally {
+ TryStart = prevTryStart;
+ FixupLeaveOffsets = pFixupLeaveOffsets;
+ }
+
+ return node;
+ }
+
+ void EmitLeave ()
+ {
+ // keep in sync w/ VisitGoto()
+ // Prevent multiple `leave OFFSET`s in the output
+ if (il.Body.Instructions.Last ().OpCode.Code != Code.Ret) {
+ il.Emit (OpCodes.Leave, il.Body.Instructions.Last ());
+ FixupLeaveOffsets!.Add (il.Body.Instructions.Last ());
+ }
+ }
+
+ ///
+ /// Visits the children of the .
+ ///
+ /// The expression to visit.
+ /// The modified expression, if it or any subexpression was modified;
+ /// otherwise, returns the original expression.
+ protected override Expression VisitTypeBinary (
+ TypeBinaryExpression node)
+ {
+ Logger (TraceLevel.Verbose, $"# jonp: CecilCompilerExpressionVisitor.VisitTypeBinary: {node}");
+ return base.VisitTypeBinary (node);
+ }
+
+ ///
+ /// Visits the children of the .
+ ///
+ /// The expression to visit.
+ /// The modified expression, if it or any subexpression was modified;
+ /// otherwise, returns the original expression.
+ protected override Expression VisitUnary (
+ UnaryExpression node)
+ {
+ Logger (TraceLevel.Verbose, $"# jonp: CecilCompilerExpressionVisitor.VisitUnary: {node}");
+ return base.VisitUnary (node);
+ }
+
+ ///
+ /// Visits the children of the .
+ ///
+ /// The expression to visit.
+ /// The modified expression, if it or any subexpression was modified;
+ /// otherwise, returns the original expression.
+ protected override Expression VisitMemberInit (
+ MemberInitExpression node)
+ {
+ Logger (TraceLevel.Verbose, $"# jonp: CecilCompilerExpressionVisitor.VisitMemberInit: {node}");
+ return base.VisitMemberInit (node);
+ }
+
+ ///
+ /// Visits the children of the .
+ ///
+ /// The expression to visit.
+ /// The modified expression, if it or any subexpression was modified;
+ /// otherwise, returns the original expression.
+ protected override Expression VisitListInit (
+ ListInitExpression node)
+ {
+ Logger (TraceLevel.Verbose, $"# jonp: CecilCompilerExpressionVisitor.VisitListInit: {node}");
+ return base.VisitListInit (node);
+ }
+
+ ///
+ /// Visits the children of the .
+ ///
+ /// The expression to visit.
+ /// The modified expression, if it or any subexpression was modified;
+ /// otherwise, returns the original expression.
+ protected override ElementInit VisitElementInit (
+ ElementInit node)
+ {
+ Logger (TraceLevel.Verbose, $"# jonp: CecilCompilerExpressionVisitor.VisitElementInit: {node}");
+ return base.VisitElementInit (node);
+ }
+
+ ///
+ /// Visits the children of the .
+ ///
+ /// The expression to visit.
+ /// The modified expression, if it or any subexpression was modified;
+ /// otherwise, returns the original expression.
+ protected override MemberBinding VisitMemberBinding (
+ MemberBinding node)
+ {
+ Logger (TraceLevel.Verbose, $"# jonp: CecilCompilerExpressionVisitor.VisitMemberBinding: {node}");
+ return base.VisitMemberBinding (node);
+ }
+
+ ///
+ /// Visits the children of the .
+ ///
+ /// The expression to visit.
+ /// The modified expression, if it or any subexpression was modified;
+ /// otherwise, returns the original expression.
+ protected override MemberAssignment VisitMemberAssignment (
+ MemberAssignment node)
+ {
+ Logger (TraceLevel.Verbose, $"# jonp: CecilCompilerExpressionVisitor.VisitMemberAssignment: {node}");
+ return base.VisitMemberAssignment (node);
+ }
+
+ ///
+ /// Visits the children of the .
+ ///
+ /// The expression to visit.
+ /// The modified expression, if it or any subexpression was modified;
+ /// otherwise, returns the original expression.
+ protected override MemberMemberBinding VisitMemberMemberBinding (
+ MemberMemberBinding node)
+ {
+ Logger (TraceLevel.Verbose, $"# jonp: CecilCompilerExpressionVisitor.VisitMemberMemberBinding: {node}");
+ return base.VisitMemberMemberBinding (node);
+ }
+
+ ///
+ /// Visits the children of the .
+ ///
+ /// The expression to visit.
+ /// The modified expression, if it or any subexpression was modified;
+ /// otherwise, returns the original expression.
+ protected override MemberListBinding VisitMemberListBinding (
+ MemberListBinding node)
+ {
+ Logger (TraceLevel.Verbose, $"# jonp: CecilCompilerExpressionVisitor.VisitMemberListBinding: {node}");
+ return base.VisitMemberListBinding (node);
+ }
+
+ ///
+ /// Visits the children of the .
+ ///
+ /// The expression to visit.
+ /// The modified expression, if it or any subexpression was modified;
+ /// otherwise, returns the original expression.
+ protected override Expression VisitDynamic (
+ DynamicExpression node)
+ {
+ Logger (TraceLevel.Verbose, $"# jonp: CecilCompilerExpressionVisitor.VisitDynamic: {node}");
+ return base.VisitDynamic (node);
+ }
+}
diff --git a/src/Java.Interop.Tools.Expressions/Java.Interop.Tools.Expressions/ExpressionAssemblyBuilder.cs b/src/Java.Interop.Tools.Expressions/Java.Interop.Tools.Expressions/ExpressionAssemblyBuilder.cs
new file mode 100644
index 000000000..b138c5e57
--- /dev/null
+++ b/src/Java.Interop.Tools.Expressions/Java.Interop.Tools.Expressions/ExpressionAssemblyBuilder.cs
@@ -0,0 +1,452 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq.Expressions;
+using System.Text;
+
+using Java.Interop;
+using Java.Interop.Tools.Diagnostics;
+
+using Mono.Cecil;
+using Mono.Cecil.Cil;
+using static System.Formats.Asn1.AsnWriter;
+
+namespace Java.Interop.Tools.Expressions;
+
+public class ExpressionAssemblyBuilder {
+
+ public ExpressionAssemblyBuilder (AssemblyDefinition declaringAssemblyDefinition, Action? logger = null)
+ {
+ DeclaringAssemblyDefinition = declaringAssemblyDefinition;
+ Logger = logger ?? Diagnostic.CreateConsoleLogger ();
+ }
+
+ public AssemblyDefinition DeclaringAssemblyDefinition {get;}
+ public Action Logger {get;}
+ public bool KeepTemporaryFiles {get; set;}
+
+ public MethodDefinition Compile (LambdaExpression expression)
+ {
+ var mmDef = CreateMethodDefinition (DeclaringAssemblyDefinition, expression);
+ var decls = new VariableDefinitions (DeclaringAssemblyDefinition, mmDef, expression, Logger);
+ var mmBody = mmDef.Body;
+ var il = mmBody.GetILProcessor ();
+ var v = new CecilCompilerExpressionVisitor (DeclaringAssemblyDefinition, mmBody, decls, Logger);
+ v.Visit (expression);
+
+ if (expression.ReturnType != null && expression.ReturnType != typeof (void) && decls.ReturnValue == null) {
+ Logger (TraceLevel.Error, $"# jonp: validation error: expression has a return type but we didn't find a return value! expression={expression}");
+ }
+
+ decls.ReturnValue?.Load (il);
+ il.Emit (OpCodes.Ret);
+
+ return mmDef;
+ }
+
+ static MethodDefinition CreateMethodDefinition (AssemblyDefinition declaringAssembly, LambdaExpression expression)
+ {
+ var mmDef = new MethodDefinition (
+ name: "@CHANGE-ME@",
+ attributes: Mono.Cecil.MethodAttributes.Static | Mono.Cecil.MethodAttributes.Private | Mono.Cecil.MethodAttributes.HideBySig,
+ returnType: declaringAssembly.MainModule.ImportReference (expression.ReturnType)
+ ) {
+ Body = {
+ InitLocals = true,
+ },
+ };
+ return mmDef;
+ }
+
+ public MethodDefinition CreateRegistrationMethod (IList methods)
+ {
+ var registrations = new MethodDefinition (
+ name: "__RegisterNativeMembers",
+ attributes: MethodAttributes.Static | MethodAttributes.Private | MethodAttributes.HideBySig,
+ returnType: DeclaringAssemblyDefinition.MainModule.TypeSystem.Void
+ ) {
+ Body = {
+ InitLocals = true,
+ },
+ };
+
+ var ctor = typeof (JniAddNativeMethodRegistrationAttribute).GetConstructor (Type.EmptyTypes);
+ var attr = new CustomAttribute (DeclaringAssemblyDefinition.MainModule.ImportReference (ctor));
+ registrations.CustomAttributes.Add (attr);
+
+ var args = new ParameterDefinition ("args", default, DeclaringAssemblyDefinition.MainModule.ImportReference (typeof (JniNativeMethodRegistrationArguments)));
+ registrations.Parameters.Add (args);
+
+ var arrayType = DeclaringAssemblyDefinition.MainModule.ImportReference (typeof (JniNativeMethodRegistration []));
+
+ var array = new VariableDefinition (arrayType);
+ registrations.Body.Variables.Add (array);
+
+ var il = registrations.Body.GetILProcessor ();
+ EmitConsoleWriteLine (il, $"# jonp: called __RegisterNativeMembers w/ {methods.Count} methods to register.");
+ il.Emit (OpCodes.Ldc_I4, methods.Count);
+ il.Emit (OpCodes.Newarr, DeclaringAssemblyDefinition.MainModule.ImportReference (arrayType.GetElementType ()));
+ // il.Emit (OpCodes.Stloc_0);
+
+ var JniNativeMethodRegistration_ctor = typeof (JniNativeMethodRegistration).GetConstructor (new [] { typeof (string), typeof (string), typeof (Delegate) });
+ var jnmr_ctor = DeclaringAssemblyDefinition.MainModule.ImportReference (JniNativeMethodRegistration_ctor);
+
+ for (int i = 0; i < methods.Count; i++) {
+ var delegateCtor = GetMarshalMethodDelegateCtor (methods [i].MarshalMethodDefinition);
+
+ // il.Emit (OpCodes.Ldloc_0); // args
+ il.Emit (OpCodes.Dup); // args
+ il.Emit (OpCodes.Ldc_I4, i); // index of `args` to set
+
+ // new JniNativeMethodRegistration (JniName, JniSignature, new _JniMarshal_PP… (MarshalMethodDefinition))
+ il.Emit (OpCodes.Ldstr, methods [i].JniName);
+ il.Emit (OpCodes.Ldstr, methods [i].JniSignature);
+ il.Emit (OpCodes.Ldnull);
+ il.Emit (OpCodes.Ldftn, methods [i].MarshalMethodDefinition);
+ il.Emit (OpCodes.Newobj, delegateCtor);
+ il.Emit (OpCodes.Newobj, jnmr_ctor);
+
+ il.Emit (OpCodes.Stelem_Any, arrayType.GetElementType ()); // args [i] = new JniNativeMethodRegistration (…)
+ }
+
+ il.Emit (OpCodes.Stloc_0);
+
+ Action> addRegistrations = new JniNativeMethodRegistrationArguments ().AddRegistrations;
+ il.Emit (OpCodes.Ldarga_S, args);
+ il.Emit (OpCodes.Ldloc_0);
+ il.Emit (OpCodes.Call, DeclaringAssemblyDefinition.MainModule.ImportReference (addRegistrations.Method));
+ il.Emit (OpCodes.Ret);
+
+
+ return registrations;
+ }
+
+ void EmitConsoleWriteLine (ILProcessor il, string message)
+ {
+ Action cwl = Console.WriteLine;
+ il.Emit (OpCodes.Ldstr, message);
+ il.Emit (OpCodes.Call, DeclaringAssemblyDefinition.MainModule.ImportReference (cwl.Method));
+ }
+
+ // Keep in sync w/ MarshalMemberBuilder.GetMarshalerType()
+ MethodReference GetMarshalMethodDelegateCtor (MethodDefinition method)
+ {
+ // Too many parameters; does a `_JniMarshal_*` type exist in the type's declaring assembly?
+ var delegateName = GetMarshalMethodDelegateName (method.Parameters, method.ReturnType);
+
+ var delegateDef = DeclaringAssemblyDefinition.MainModule.GetType (delegateName.ToString ());
+ if (delegateDef == null) {
+ delegateDef = CreateMarshalMethodDelegateType (delegateName, method.Parameters, method.ReturnType);
+ DeclaringAssemblyDefinition.MainModule.Types.Add (delegateDef);
+ }
+ return delegateDef.Methods.First (m => m.Name == ".ctor");
+ }
+
+ string GetMarshalMethodDelegateName (IList parameters, TypeReference returnType)
+ {
+ // Too many parameters; does a `_JniMarshal_*` type exist in the type's declaring assembly?
+ var delegateName = new StringBuilder ();
+ delegateName.Append ("_JniMarshal_PP");
+
+ for (int i = 2; i < parameters.Count; i++) {
+ delegateName.Append (GetJniMarshalDelegateParameterIdentifier (parameters [i].ParameterType));
+ }
+ delegateName.Append ("_");
+ delegateName.Append (GetJniMarshalDelegateParameterIdentifier (returnType));
+
+ return delegateName.ToString ();
+ }
+
+ char GetJniMarshalDelegateParameterIdentifier (TypeReference type)
+ {
+ switch (type?.FullName) {
+ case "System.Boolean": return 'Z';
+ case "System.Byte": return 'B';
+ case "System.SByte": return 'B';
+ case "System.Char": return 'C';
+ case "System.Int16": return 'S';
+ case "System.UInt16": return 's';
+ case "System.Int32": return 'I';
+ case "System.UInt32": return 'i';
+ case "System.Int64": return 'J';
+ case "System.UInt64": return 'j';
+ case "System.Single": return 'F';
+ case "System.Double": return 'D';
+ case null:
+ case "System.Void": return 'V';
+ default: return 'L';
+ }
+ }
+
+ public TypeDefinition CreateMarshalMethodDelegateType (string delegateName, IList parameters, TypeReference returnType)
+ {
+ var delegateDef = new TypeDefinition (
+ @namespace: "",
+ name: delegateName,
+ attributes: TypeAttributes.Class | TypeAttributes.NotPublic | TypeAttributes.Sealed | TypeAttributes.AnsiClass | TypeAttributes.AutoClass
+ );
+ delegateDef.BaseType = DeclaringAssemblyDefinition.MainModule.ImportReference (typeof (MulticastDelegate));
+
+ var delegateCtor = new MethodDefinition (
+ name: ".ctor",
+ attributes: MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
+ returnType: DeclaringAssemblyDefinition.MainModule.TypeSystem.Void
+ );
+ delegateCtor.ImplAttributes = MethodImplAttributes.Runtime | MethodImplAttributes.Managed;
+ delegateCtor.Parameters.Add (new ParameterDefinition ("object", default, DeclaringAssemblyDefinition.MainModule.TypeSystem.Object));
+ delegateCtor.Parameters.Add (new ParameterDefinition ("method", default, DeclaringAssemblyDefinition.MainModule.TypeSystem.IntPtr));
+ delegateDef.Methods.Add (delegateCtor);
+
+ var invoke = new MethodDefinition (
+ name: "Invoke",
+ attributes: MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual,
+ returnType: returnType
+ );
+ invoke.ImplAttributes = MethodImplAttributes.Runtime | MethodImplAttributes.Managed;
+ foreach (var p in parameters) {
+ invoke.Parameters.Add (new ParameterDefinition (p.Name, p.Attributes, p.ParameterType));
+ }
+ delegateDef.Methods.Add (invoke);
+
+ return delegateDef;
+ }
+
+
+ public void Write (string path)
+ {
+ Logger (TraceLevel.Verbose, $"# jonp: ExpressionAssemblyBuilder.Write to path={path}");
+ var module = DeclaringAssemblyDefinition.MainModule;
+
+ var c = new MemoryStream ();
+ DeclaringAssemblyDefinition.Write (c);
+ c.Position = 0;
+
+ if (KeepTemporaryFiles) {
+ using var intermediate = File.Create (path + ".cecil");
+ c.WriteTo (intermediate);
+ c.Position = 0;
+ }
+
+ Logger (TraceLevel.Verbose, $"# jonp: ---");
+
+ var rp = new ReaderParameters {
+ InMemory = true,
+ ReadSymbols = false,
+ ReadWrite = false,
+ ReadingMode = ReadingMode.Immediate,
+ };
+ var newAsm = AssemblyDefinition.ReadAssembly (c, rp);
+ module = newAsm.MainModule;
+ var systemRuntimeRef = module.AssemblyReferences.FirstOrDefault (r => r.Name == "System.Runtime");
+ var privateCorelibRef = module.AssemblyReferences.FirstOrDefault (r => r.Name == "System.Private.CoreLib");
+
+ if (systemRuntimeRef == null && privateCorelibRef != null) {
+ systemRuntimeRef = GetSystemRuntimeReference ();
+ module.AssemblyReferences.Add (systemRuntimeRef);
+ }
+
+ var selfRef = module.AssemblyReferences.FirstOrDefault (r => r.Name == newAsm.Name.Name);
+ foreach (var member in module.GetMemberReferences ()) {
+ Logger (TraceLevel.Verbose, $"# jonp: looking at ref for member: [{member.DeclaringType.Scope?.Name}]{member}");
+ if (member.DeclaringType.Scope == privateCorelibRef) {
+ Logger (TraceLevel.Verbose, $"# jonp: Fixing scope ref for member: {member}");
+ member.DeclaringType.Scope = systemRuntimeRef;
+ continue;
+ }
+ if (member.DeclaringType.Scope == selfRef) {
+ Logger (TraceLevel.Verbose, $"# jonp: Fixing scope self ref for member: {member}");
+ member.DeclaringType.Scope = null;
+ continue;
+ }
+ }
+ foreach (var type in module.GetTypeReferences ()) {
+ Logger (TraceLevel.Verbose, $"# jonp: looking at ref for type: [{type.Scope}]{type}");
+ if (type.Scope == privateCorelibRef) {
+ Logger (TraceLevel.Verbose, $"# jonp: Fixing scope ref for type: {type}");
+ type.Scope = systemRuntimeRef;
+ continue;
+ }
+ if (type.Scope == selfRef) {
+ Logger (TraceLevel.Verbose, $"# jonp: Fixing scope self ref for type: {type}");
+ type.Scope = null;
+ continue;
+ }
+ }
+ module.AssemblyReferences.Remove (privateCorelibRef);
+ if (selfRef != null) {
+ module.AssemblyReferences.Remove (selfRef);
+ }
+ newAsm.Write (path);
+ }
+
+ static AssemblyNameReference GetSystemRuntimeReference ()
+ {
+ var privateCorelibDir = Path.GetDirectoryName (typeof (object).Assembly.Location) ??
+ throw new NotSupportedException ("Cannot find directory of `System.Private.CoreLib.dll`!");
+ var systemRuntimePath = Path.Combine (privateCorelibDir, "System.Runtime.dll");
+ if (!File.Exists (systemRuntimePath)) {
+ throw new NotSupportedException ($"Could not find `System.Runtime.dll`; looked at `{systemRuntimePath}`.");
+ }
+ var rp = new ReaderParameters {
+ InMemory = false,
+ ReadSymbols = false,
+ ReadWrite = false,
+ ReadingMode = ReadingMode.Deferred,
+ };
+ using var systemRuntime = AssemblyDefinition.ReadAssembly (systemRuntimePath, rp);
+ var nameDef = systemRuntime.Name;
+ return new AssemblyNameReference (nameDef.Name, nameDef.Version) {
+ HashAlgorithm = nameDef.HashAlgorithm,
+ PublicKeyToken = nameDef.PublicKeyToken,
+ };
+ }
+}
+
+sealed class VariableInfo {
+ public VariableInfo (Action load, Action loadAddress, Action store)
+ {
+ Load = load;
+ LoadAddress = loadAddress;
+ Store = store;
+ }
+
+ public readonly Action Load;
+ public readonly Action LoadAddress;
+ public readonly Action Store;
+}
+
+sealed class VariableDefinitions {
+
+ Dictionary variables = new ();
+ Action Logger;
+
+ public VariableDefinitions (AssemblyDefinition declaringAssembly, MethodDefinition declaringMethod, LambdaExpression expression, Action logger)
+ {
+ Logger = logger;
+ for (int i = 0; i < expression.Parameters.Count; ++i) {
+ var c = expression.Parameters [i];
+ var d = new ParameterDefinition (c.Name, default, declaringAssembly.MainModule.ImportReference (c.Type));
+ declaringMethod.Parameters.Add (d);
+
+ VariableInfo v;
+
+ switch (i) {
+ case 0:
+ v = new VariableInfo (il => il.Emit (OpCodes.Ldarg_0), il => il.Emit (OpCodes.Ldarga, 0), il => il.Emit (OpCodes.Starg, 0));
+ break;
+ case 1:
+ v = new VariableInfo (il => il.Emit (OpCodes.Ldarg_1), il => il.Emit (OpCodes.Ldarga, 1), il => il.Emit (OpCodes.Starg, 1));
+ break;
+ case 2:
+ v = new VariableInfo (il => il.Emit (OpCodes.Ldarg_2), il => il.Emit (OpCodes.Ldarga, 2), il => il.Emit (OpCodes.Starg, 2));
+ break;
+ case 3:
+ v = new VariableInfo (il => il.Emit (OpCodes.Ldarg_3), il => il.Emit (OpCodes.Ldarga, 3), il => il.Emit (OpCodes.Starg, 3));
+ break;
+ default:
+ int x = i;
+ v = new VariableInfo (il => il.Emit (OpCodes.Ldarg, x), il => il.Emit (OpCodes.Ldarga, x), il => il.Emit (OpCodes.Starg, x));
+ break;
+ }
+ variables [c] = v;
+ }
+ FillVariables (declaringAssembly, declaringMethod, expression);
+ }
+
+ public VariableInfo? ReturnValue {get; private set;}
+
+ public VariableInfo this [ParameterExpression e] {
+ get => variables [e];
+ }
+
+ void FillVariables (
+ AssemblyDefinition declaringAssembly,
+ MethodDefinition declaringMethod,
+ Expression e)
+ {
+ var variableVisitor = new VariableExpressionVisitor (variables.Keys, Logger);
+ variableVisitor.Visit (e);
+
+ Console.WriteLine ($"# jonp: filling {variableVisitor.Variables.Count} variables");
+ for (int i = 0; i < variableVisitor.Variables.Count; ++i) {
+ var c = variableVisitor.Variables [i];
+ var d = new VariableDefinition (declaringAssembly.MainModule.ImportReference (c.Type));
+ declaringMethod.Body.Variables.Add (d);
+
+ VariableInfo v;
+
+ switch (i) {
+ case 0:
+ v = new VariableInfo (il => il.Emit (OpCodes.Ldloc_0), il => il.Emit (OpCodes.Ldloca, 0), il => il.Emit (OpCodes.Stloc_0));
+ break;
+ case 1:
+ v = new VariableInfo (il => il.Emit (OpCodes.Ldloc_1), il => il.Emit (OpCodes.Ldloca, 1), il => il.Emit (OpCodes.Stloc_1));
+ break;
+ case 2:
+ v = new VariableInfo (il => il.Emit (OpCodes.Ldloc_2), il => il.Emit (OpCodes.Ldloca, 2), il => il.Emit (OpCodes.Stloc_2));
+ break;
+ case 3:
+ v = new VariableInfo (il => il.Emit (OpCodes.Ldloc_3), il => il.Emit (OpCodes.Ldloca, 3), il => il.Emit (OpCodes.Stloc_3));
+ break;
+ default:
+ var x = i;
+ v = new VariableInfo (il => il.Emit (OpCodes.Ldloc, x), il => il.Emit (OpCodes.Ldloca, x), il => il.Emit (OpCodes.Stloc, x));
+ break;
+ }
+ variables [c] = v;
+ if (c == variableVisitor.ReturnValue) {
+ ReturnValue = v;
+ }
+ Console.WriteLine ($"# jonp: FillVariables: local var {c.Name} is index {i}");
+ }
+ }
+}
+
+class VariableExpressionVisitor : ExpressionVisitor {
+
+ public VariableExpressionVisitor (ICollection arguments, Action logger)
+ {
+ Arguments = arguments;
+ Logger = logger;
+ }
+
+ ICollection Arguments;
+ Action Logger;
+
+ public List Variables = new ();
+ public ParameterExpression? ReturnValue;
+
+ protected override Expression VisitGoto (
+ GotoExpression node)
+ {
+ Logger (TraceLevel.Verbose, $"# jonp: VariableExpressionVisitor.Goto: {node}; node.Kind={node.Kind}; node.Type={node.Type}");
+ if (node.Kind != GotoExpressionKind.Return) {
+ return base.VisitGoto (node);
+ }
+ if (ReturnValue != null) {
+ return base.VisitGoto (node);
+ }
+ Logger (TraceLevel.Verbose, $"# jonp: VariableExpressionVisitor.Goto: node.Target={node.Target} node.Value={node.Value}");
+ if (node.Value is ParameterExpression rv) {
+ ReturnValue = rv;
+ return base.VisitGoto (node);
+ }
+ if (node.Type == typeof (void)) {
+ return base.VisitGoto (node);
+ }
+ var p = Expression.Parameter (node.Type, "__goto.Return.Temporary");
+ Variables.Add (p);
+ ReturnValue = p;
+ Logger (TraceLevel.Verbose, $"# jonp: VariableExpressionVisitor.Goto: setting ReturnValue={p}");
+ return base.VisitGoto (node);
+ }
+
+ protected override Expression VisitParameter (
+ ParameterExpression node)
+ {
+ if (!Arguments.Contains (node) && !Variables.Contains (node)) {
+ Variables.Add (node);
+ }
+ return node;
+ }
+}
diff --git a/src/Java.Interop.Tools.Expressions/Java.Interop.Tools.Expressions/ExpressionMethodRegistration.cs b/src/Java.Interop.Tools.Expressions/Java.Interop.Tools.Expressions/ExpressionMethodRegistration.cs
new file mode 100644
index 000000000..91ae63fe2
--- /dev/null
+++ b/src/Java.Interop.Tools.Expressions/Java.Interop.Tools.Expressions/ExpressionMethodRegistration.cs
@@ -0,0 +1,9 @@
+using System;
+
+using Mono.Cecil;
+
+namespace Java.Interop.Tools.Expressions;
+
+public record ExpressionMethodRegistration (string JniName, string JniSignature, MethodDefinition MarshalMethodDefinition)
+{
+}
diff --git a/tests/Java.Interop.Export-Tests/Java.Interop/MarshalMemberBuilderTest.cs b/tests/Java.Interop.Export-Tests/Java.Interop/MarshalMemberBuilderTest.cs
index 3d117c508..f28d82268 100644
--- a/tests/Java.Interop.Export-Tests/Java.Interop/MarshalMemberBuilderTest.cs
+++ b/tests/Java.Interop.Export-Tests/Java.Interop/MarshalMemberBuilderTest.cs
@@ -12,7 +12,6 @@
namespace Java.InteropTests
{
-#if !NET
[TestFixture]
class MarshalMemberBuilderTest : JavaVMFixture
{
@@ -27,11 +26,17 @@ public void AddExportMethods ()
Assert.AreEqual ("action", methods [0].Name);
Assert.AreEqual ("()V", methods [0].Signature);
- Assert.IsTrue (methods [0].Marshaler is Action);
- Assert.AreEqual ("staticAction", methods [1].Name);
- Assert.AreEqual ("()V", methods [1].Signature);
+ Assert.AreEqual ("staticAction", methods [1].Name);
+ Assert.AreEqual ("()V", methods [1].Signature);
+
+#if NET
+ Assert.AreEqual ("_JniMarshal_PP_V", methods [0].Marshaler.GetType ().FullName);
+ Assert.AreEqual ("_JniMarshal_PP_V", methods [1].Marshaler.GetType ().FullName);
+#else
+ Assert.IsTrue (methods [0].Marshaler is Action);
Assert.IsTrue (methods [1].Marshaler is Action);
+#endif // NET
var m = t.GetStaticMethod ("testStaticMethods", "()V");
JniEnvironment.StaticMethods.CallStaticVoidMethod (t.PeerReference, m);
@@ -201,6 +206,12 @@ static void CheckExpression (LambdaExpression expression, string memberName, Typ
{
Console.WriteLine ("## member: {0}", memberName);
Console.WriteLine (expression.ToCSharpCode ());
+ Assert.AreEqual (expectedBody, expression.ToCSharpCode ());
+#if NET
+ // TODO: Use src/Java.Interop.Tools.Expressions to compile `expression`
+ // and use the "IL decompiler" in tests/Java.Interop.Tools.Expressions-Tests
+ // to verify the expected IL
+#else
var da = AppDomain.CurrentDomain.DefineDynamicAssembly(
new AssemblyName("dyn"), // call it whatever you want
System.Reflection.Emit.AssemblyBuilderAccess.Save,
@@ -216,10 +227,10 @@ static void CheckExpression (LambdaExpression expression, string memberName, Typ
expression.CompileToMethod (mb);
dt.CreateType();
Assert.AreEqual (expressionType, expression.Type);
- Assert.AreEqual (expectedBody, expression.ToCSharpCode ());
#if !__ANDROID__
da.Save (_name);
#endif // !__ANDROID__
+#endif // !NET
}
[Test]
@@ -556,5 +567,4 @@ public void CreateConstructActivationPeerExpression ()
}}");
}
}
-#endif // !NET
}
diff --git a/tests/Java.Interop.Tools.Expressions-Tests/Java.Interop.Tools.Expressions-Tests.csproj b/tests/Java.Interop.Tools.Expressions-Tests/Java.Interop.Tools.Expressions-Tests.csproj
new file mode 100644
index 000000000..dfc8b2a90
--- /dev/null
+++ b/tests/Java.Interop.Tools.Expressions-Tests/Java.Interop.Tools.Expressions-Tests.csproj
@@ -0,0 +1,36 @@
+
+
+
+ $(DotNetTargetFramework)
+ Java.Interop.Tools.ExpressionsTests
+ enable
+ enable
+ false
+
+
+
+
+
+ $(TestOutputFullPath)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/Java.Interop.Tools.Expressions-Tests/Java.Interop.Tools.ExpressionsTests/ExpressionAssemblyBuilderTests.cs b/tests/Java.Interop.Tools.Expressions-Tests/Java.Interop.Tools.ExpressionsTests/ExpressionAssemblyBuilderTests.cs
new file mode 100644
index 000000000..54e1f9998
--- /dev/null
+++ b/tests/Java.Interop.Tools.Expressions-Tests/Java.Interop.Tools.ExpressionsTests/ExpressionAssemblyBuilderTests.cs
@@ -0,0 +1,444 @@
+namespace Java.Interop.Tools.ExpressionsTests;
+
+using System.IO;
+using System.Linq.Expressions;
+using System.Text;
+
+using Java.Interop.Tools.Diagnostics;
+using Java.Interop.Tools.Expressions;
+
+using Mono.Cecil;
+using Mono.Cecil.Cil;
+
+using Mono.Linq.Expressions;
+
+[TestFixture]
+public class ExpressionAssemblyBuilderTests
+{
+ static readonly string AssemblyModuleBaseName;
+
+ static ExpressionAssemblyBuilderTests ()
+ {
+ AssemblyModuleBaseName = typeof (ExpressionAssemblyBuilderTests).Assembly.GetName ().Name +
+ "-" +
+ nameof (ExpressionAssemblyBuilderTests);
+ }
+
+ ExpressionAssemblyBuilder? ExpressionAssemblyBuilder;
+ AssemblyDefinition? AssemblyDefinition;
+ TypeDefinition? TypeDefinition;
+
+ [OneTimeSetUp]
+ public void InitializeTestEnvironment ()
+ {
+ var moduleParams = new ModuleParameters {
+ Kind = ModuleKind.Dll,
+ };
+ AssemblyDefinition = AssemblyDefinition.CreateAssembly (
+ assemblyName: new AssemblyNameDefinition (AssemblyModuleBaseName, new Version (0, 0, 0, 0)),
+ moduleName: AssemblyModuleBaseName + ".dll",
+ parameters: moduleParams
+ );
+ TypeDefinition = new TypeDefinition (
+ @namespace: "Example",
+ name: "Output",
+ attributes: TypeAttributes.Public | TypeAttributes.Sealed
+ );
+ TypeDefinition.BaseType = AssemblyDefinition.MainModule.ImportReference (typeof (object));
+ AssemblyDefinition.MainModule.Types.Add (TypeDefinition);
+
+ ExpressionAssemblyBuilder = new ExpressionAssemblyBuilder (AssemblyDefinition) {
+ KeepTemporaryFiles = true,
+ };
+ }
+
+ [OneTimeTearDown]
+ public void TearDownTestEnvironment ()
+ {
+ var path = Path.GetDirectoryName (typeof (ExpressionAssemblyBuilderTests).Assembly.Location)
+ ?? throw new InvalidOperationException ("`typeof (ExpressionAssemblyBuilderTests).Assembly.Location` is null?!");
+ ExpressionAssemblyBuilder!.Write (Path.Combine (path, AssemblyModuleBaseName + ".dll"));
+ }
+
+ void AddMethod (MethodDefinition method, [System.Runtime.CompilerServices.CallerMemberName] string methodName = "")
+ {
+ method.Name = methodName;
+ method.IsPublic = true;
+ TypeDefinition!.Methods.Add (method);
+ }
+
+ [Test]
+ public void CreateMarshalMethodDelegateType ()
+ {
+ var t = ExpressionAssemblyBuilder!.CreateMarshalMethodDelegateType (
+ "_Jonp_Demo",
+ new [] {
+ new ParameterDefinition ("jnienv", default, AssemblyDefinition!.MainModule.TypeSystem.IntPtr),
+ new ParameterDefinition ("klass", default, AssemblyDefinition!.MainModule.TypeSystem.IntPtr),
+ new ParameterDefinition ("value", default, AssemblyDefinition!.MainModule.TypeSystem.Int32),
+ },
+ AssemblyDefinition.MainModule.TypeSystem.IntPtr
+ );
+ AssemblyDefinition.MainModule.Types.Add (t);
+ }
+
+ [Test]
+ public void Compile_MethodCall ()
+ {
+ Expression e = () => Console.WriteLine ("constant");
+ var m = ExpressionAssemblyBuilder!.Compile (e);
+
+ AddMethod (m);
+
+ var expected = new[]{
+ "Instruction_0000: ldstr \"constant\"",
+ "Instruction_0001: call System.Void System.Console::WriteLine(System.String)",
+ "Instruction_0002: ret",
+ };
+ var actual = m.Body.Instructions;
+ Assert.AreEqual (expected.Length, actual.Count);
+ for (int i = 0; i < expected.Length; ++i) {
+ Assert.AreEqual (expected [i], GetDescription (actual, i));
+ }
+ }
+
+ [Test]
+ public void Compile_Condition_1 ()
+ {
+ Expression> e = (a, b) => a == b;
+ var m = ExpressionAssemblyBuilder!.Compile (e);
+
+ AddMethod (m);
+
+ var expected = new[]{
+ "Instruction_0000: ldarg.0",
+ "Instruction_0001: ldarg.1",
+ "Instruction_0002: ceq",
+ "Instruction_0003: ret",
+ };
+ var actual = m.Body.Instructions;
+ Assert.AreEqual (expected.Length, actual.Count);
+ for (int i = 0; i < expected.Length; ++i) {
+ Assert.AreEqual (expected [i], GetDescription (actual, i));
+ }
+ }
+
+ [Test]
+ public void Compile_Condition_2 ()
+ {
+ Expression> e = (a, b) => a == b ? 1 : 2;
+ var m = ExpressionAssemblyBuilder!.Compile (e);
+
+ AddMethod (m);
+
+ // Alas, branch targets d
+ var expected = new[]{
+ "Instruction_0000: ldarg.0",
+ "Instruction_0001: ldarg.1",
+ "Instruction_0002: ceq",
+ "Instruction_0003: brfalse Instruction_0006",
+ "Instruction_0004: ldc.i4 1",
+ "Instruction_0005: br Instruction_0008",
+ "Instruction_0006: nop",
+ "Instruction_0007: ldc.i4 2",
+ "Instruction_0008: nop",
+ "Instruction_0009: ret",
+ };
+ var actual = m.Body.Instructions;
+ Assert.AreEqual (expected.Length, actual.Count);
+ for (int i = 0; i < expected.Length; ++i) {
+ Assert.AreEqual (expected [i], GetDescription (actual, i));
+ }
+ }
+
+ [Test]
+ public void Compile_TryCatchFinally ()
+ {
+ var exit = Expression.Label (typeof (int), "__exit");
+ var tryBlock = Expression.Block (typeof (int),
+ E(() => Console.WriteLine ("try")).Body,
+ Expression.Return (target: exit, value: Expression.Constant (1), type: typeof (int))
+ );
+ var finallyBlock = E(() => Console.WriteLine ("finally")).Body;
+ var catchLog0 = E>(e => Console.WriteLine ("filtered"));
+ var catchFilt0 = Expression.Equal (
+ Expression.Constant (null, typeof (Exception)),
+ Expression.Property (catchLog0.Parameters [0], "InnerException"));
+ var catchBlock0 = Expression.Block (typeof (int),
+ catchLog0.Body,
+ Expression.Return (target: exit, value: Expression.Constant (3), type: typeof (int))
+ );
+ var catchLog1 = E>(e => Console.WriteLine (e.ToString ()));
+ var catchBlock1 = Expression.Block (typeof (int),
+ catchLog1.Body,
+ Expression.Return (target: exit, value: Expression.Constant (4), type: typeof (int))
+ );
+ var block = new List {
+ Expression.TryCatchFinally (
+ body: tryBlock,
+ @finally: finallyBlock,
+ handlers: new[]{
+ Expression.Catch (catchLog0.Parameters[0], catchBlock0, catchFilt0),
+ Expression.Catch (catchLog1.Parameters[0], catchBlock1),
+ }
+ ),
+ Expression.Label (exit, Expression.Default (typeof (int))),
+ };
+ var e = Expression.Lambda (
+ delegateType: typeof (Func),
+ body: Expression.Block (variables: Array.Empty(), expressions: block),
+ name: nameof (Compile_TryCatchFinally),
+ tailCall: false,
+ parameters: Array.Empty()
+ );
+
+ Assert.AreEqual (1, ((Func) e.Compile ())());
+
+ var expectedCsharp = @"int Compile_TryCatchFinally()
+{
+ try
+ {
+ Console.WriteLine(""try"");
+ return 1;
+ }
+ catch (Exception e) if (null == e.InnerException)
+ {
+ Console.WriteLine(""filtered"");
+ return 3;
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e.ToString());
+ return 4;
+ }
+ finally
+ {
+ Console.WriteLine(""finally"");
+ }
+}";
+ Console.WriteLine ($"# jonp: expression tree as C#:");
+ Console.WriteLine (e.ToCSharpCode ());
+ Assert.AreEqual (expectedCsharp, e.ToCSharpCode ());
+
+ var m = ExpressionAssemblyBuilder!.Compile (e);
+
+ AddMethod (m);
+ DumpInstructions (m);
+
+ // Alas, branch targets d
+ var expected = new[]{
+ // .try
+ "Instruction_0000: ldstr \"try\"",
+ "Instruction_0001: call System.Void System.Console::WriteLine(System.String)",
+ "Instruction_0002: ldc.i4 1",
+ "Instruction_0003: stloc.0",
+ "Instruction_0004: leave Instruction_0025",
+ // }
+ // filter {
+ "Instruction_0005: isinst System.Exception",
+ "Instruction_0006: dup",
+ "Instruction_0007: brtrue.s Instruction_000b",
+ "Instruction_0008: pop",
+ "Instruction_0009: ldc.i4.0",
+ "Instruction_000a: br.s Instruction_0012",
+ "Instruction_000b: stloc.1",
+ "Instruction_000c: ldnull",
+ "Instruction_000d: ldloc.1",
+ "Instruction_000e: callvirt System.Exception System.Exception::get_InnerException()",
+ "Instruction_000f: ceq",
+ "Instruction_0010: ldc.i4.0",
+ "Instruction_0011: cgt.un",
+ "Instruction_0012: endfilter",
+ // }
+ // { // handler
+ "Instruction_0013: castclass System.Exception",
+ "Instruction_0014: stloc.1",
+ "Instruction_0015: ldstr \"filtered\"",
+ "Instruction_0016: call System.Void System.Console::WriteLine(System.String)",
+ "Instruction_0017: ldc.i4 3",
+ "Instruction_0018: stloc.0",
+ "Instruction_0019: leave Instruction_0025",
+ // }
+ // catch class System.Exception {
+ "Instruction_001a: castclass System.Exception",
+ "Instruction_001b: stloc.2",
+ "Instruction_001c: ldloc.2",
+ "Instruction_001d: callvirt System.String System.Object::ToString()",
+ "Instruction_001e: call System.Void System.Console::WriteLine(System.String)",
+ "Instruction_001f: ldc.i4 4",
+ "Instruction_0020: stloc.0",
+ "Instruction_0021: leave Instruction_0025",
+ // }
+ // finally {
+ "Instruction_0022: ldstr \"finally\"",
+ "Instruction_0023: call System.Void System.Console::WriteLine(System.String)",
+ "Instruction_0024: endfinally",
+ // }
+ "Instruction_0025: nop",
+ "Instruction_0026: nop",
+ "Instruction_0027: ldloc.0",
+ "Instruction_0028: ret",
+ };
+ var actual = m.Body.Instructions;
+ Assert.AreEqual (expected.Length, actual.Count);
+ for (int i = 0; i < expected.Length; ++i) {
+ Assert.AreEqual (expected [i], GetDescription (actual, i));
+ }
+ }
+
+ static Expression E(Expression e)
+ where TDelegate : Delegate
+ {
+ return e;
+ }
+
+
+ static void DumpInstructions (MethodDefinition method)
+ {
+ var body = method.Body;
+ var instructions = body.Instructions;
+ if (body.HasExceptionHandlers) {
+ foreach (var h in method.Body.ExceptionHandlers) {
+ Console.Error.WriteLine ($"// Handler: {h.HandlerType}");
+ Console.Error.WriteLine( $"// \t" +
+ $" CatchType=`{h.CatchType}`");
+ Console.Error.WriteLine ($"// \t" +
+ $" TryStart=`{GetDescription (body.Instructions, body.Instructions.IndexOf (h.TryStart))}` TryEnd=`{GetDescription (body.Instructions, body.Instructions.IndexOf (h.TryEnd))}`");
+ Console.Error.WriteLine ($"// \t" +
+ $" FilterStart=`{GetDescription (body.Instructions, body.Instructions.IndexOf (h.FilterStart))}`");
+ Console.Error.WriteLine ($"// \t" +
+ $" HandlerStart=`{GetDescription (body.Instructions, body.Instructions.IndexOf (h.HandlerStart))}` HandlerEnd=`{GetDescription (body.Instructions, body.Instructions.IndexOf (h.HandlerEnd))}`");
+ Console.Error.WriteLine($"");
+ }
+ }
+ int indent = 0;
+ for (int i = 0; i < instructions.Count; ++i) {
+ var instruction = instructions [i];
+ DumpStartHandler (ref indent, body, instructions, i);
+ Console.Error.WriteLine ("{0}{1,-40}\t; {2}",
+ new string (' ', indent*2),
+ GetDescription (instructions, i),
+ instructions[i].ToString ());
+ DumpEndHandler (ref indent, body, instructions, i);
+ }
+ }
+
+ static void DumpStartHandler (ref int indent, MethodBody body, Mono.Collections.Generic.Collection instructions, int i)
+ {
+ var instruction = instructions [i];
+ if (!body.HasExceptionHandlers) {
+ return;
+ }
+ if (body.ExceptionHandlers.Any (e => e.TryStart == instruction)) {
+ Console.Error.WriteLine ($"{new string (' ', indent*2)}.try {{");
+ indent++;
+ return;
+ }
+ var f = body.ExceptionHandlers.FirstOrDefault (e => e.FilterStart == instruction);
+ if (f != null) {
+ Console.Error.WriteLine ($"{new string(' ', indent*2)}filter {{");
+ indent++;
+ return;
+
+ }
+ var h = body.ExceptionHandlers.FirstOrDefault (e => e.HandlerStart == instruction);
+ if (h != null) {
+ switch (h.HandlerType) {
+ case ExceptionHandlerType.Finally:
+ Console.Error.WriteLine ($"{new string (' ', indent*2)}finally {{");
+ break;
+ case ExceptionHandlerType.Catch:
+ Console.Error.WriteLine ($"{new string (' ', indent*2)}catch class {h.CatchType.FullName} {{");
+ break;
+ case ExceptionHandlerType.Filter:
+ Console.Error.WriteLine ($"{new string(' ', indent * 2)}{{ // handler");
+ break;
+ case ExceptionHandlerType.Fault:
+ default:
+ Console.Error.WriteLine ($"{new string (' ', indent*2)}{h.HandlerType} {{");
+ break;
+ }
+ indent++;
+ return;
+ }
+ }
+
+ static void DumpEndHandler (ref int indent, MethodBody body, Mono.Collections.Generic.Collection instructions, int i)
+ {
+ if (!body.HasExceptionHandlers) {
+ return;
+ }
+ if ((i + 1) >= instructions.Count) {
+ // End of instruction stream; clean up indentatino
+ if (indent == 0)
+ return;
+ indent--;
+ Console.Error.WriteLine ($"{new string (' ', indent)}}}");
+ return;
+ }
+ // Handler range is from first label ***prior to*** second (emphasis @jonpryor)
+ // Thus, look at *next* instruction.
+ var instruction = instructions[i+1];
+ if (body.ExceptionHandlers.Any (e => e.TryStart == instruction || e.FilterStart == instruction || e.HandlerStart == instruction ||
+ e.TryEnd == instruction || e.HandlerEnd== instruction)) {
+ indent--;
+ Console.Error.WriteLine ($"{new string (' ', indent)}}}");
+ }
+ }
+
+ // Cribbed with changes from `Instruction.ToString()`:
+ // https://github.com/dotnet/cecil/blob/e069cd8d25d5b61b0e28fe65e75959c20af7aa80/Mono.Cecil.Cil/Instruction.cs#L95-L134
+ //
+ // Don't want to use `Instruction.ToString()` as `Instruction.Offset` isn't updated until after
+ // `AssemblyDefinition.Write()`, and checking for `brfalse IL_0000` is not helpful.
+ static string GetDescription (IList instructions, int index)
+ {
+ if (index < 0) {
+ return "";
+ }
+ var instruction = instructions [index];
+ var description = new StringBuilder ();
+
+ AppendLabel (index)
+ .Append (": ")
+ .Append (instruction.OpCode.Name);
+
+ if (instruction.Operand == null) {
+ return description.ToString ();
+ }
+
+ description.Append (" ");
+
+ switch (instruction.OpCode.OperandType) {
+ case OperandType.ShortInlineBrTarget:
+ case OperandType.InlineBrTarget:
+ AppendLabel (instructions.IndexOf ((Instruction) instruction.Operand));
+ break;
+ case OperandType.InlineSwitch:
+ var labels = (Instruction []) instruction.Operand;
+ for (int i = 0; i < labels.Length; i++) {
+ if (i > 0)
+ description.Append (',');
+
+ AppendLabel (instructions.IndexOf (labels [i]));
+ }
+ break;
+ case OperandType.InlineString:
+ description.Append ('\"');
+ description.Append (instruction.Operand);
+ description.Append ('\"');
+ break;
+ default:
+ description.Append (instruction.Operand);
+ break;
+ }
+
+ return description.ToString ();
+
+ StringBuilder AppendLabel (int i)
+ {
+ return description.Append ("Instruction_")
+ .AppendFormat ("{0:x4}", i);
+ }
+ }
+}
diff --git a/tests/Java.Interop.Tools.Expressions-Tests/Usings.cs b/tests/Java.Interop.Tools.Expressions-Tests/Usings.cs
new file mode 100644
index 000000000..cefced496
--- /dev/null
+++ b/tests/Java.Interop.Tools.Expressions-Tests/Usings.cs
@@ -0,0 +1 @@
+global using NUnit.Framework;
\ No newline at end of file
diff --git a/tools/jnimarshalmethod-gen/App.cs b/tools/jnimarshalmethod-gen/App.cs
index d4020fd51..adf938648 100644
--- a/tools/jnimarshalmethod-gen/App.cs
+++ b/tools/jnimarshalmethod-gen/App.cs
@@ -1,17 +1,22 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.IO;
+using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
-using System.Reflection.Emit;
+using System.Runtime.Loader;
using System.Text.RegularExpressions;
+using System.Xml.Linq;
using Java.Interop;
using Mono.Cecil;
+using Mono.Cecil.Cil;
using Mono.Options;
using Mono.Collections.Generic;
using Java.Interop.Tools.Cecil;
+using Java.Interop.Tools.Expressions;
#if _DUMP_REGISTER_NATIVE_MEMBERS
using Mono.Linq.Expressions;
@@ -23,20 +28,37 @@ class App : MarshalByRefObject
{
internal const string Name = "jnimarshalmethod-gen";
- static DirectoryAssemblyResolver resolver = new DirectoryAssemblyResolver (logger: (l, v) => { Console.WriteLine (v); }, loadDebugSymbols: true, loadReaderParameters: new ReaderParameters () { ReadSymbols = true, InMemory = true });
+ static DirectoryAssemblyResolver resolver;
static readonly TypeDefinitionCache cache = new TypeDefinitionCache ();
- static Dictionary definedTypes = new Dictionary ();
static Dictionary typeMap = new Dictionary ();
static List references = new List ();
static public bool Debug;
- static public bool Verbose;
+ static public bool Verbose => Verbosity > 0;
+ static public int Verbosity;
static bool keepTemporary;
static bool forceRegeneration;
static List typeNameRegexes = new List ();
static string jvmDllPath;
List FilesToDelete = new List ();
+ // AssemblyLoadContext loadContext;
static string outDirectory;
+ static readonly string AppName;
+
+ static App()
+ {
+ AppName = Path.GetFileNameWithoutExtension (Environment.GetCommandLineArgs () [0]);
+ var r = new ReaderParameters {
+ ReadSymbols = true,
+ InMemory = true,
+ };
+ resolver = new DirectoryAssemblyResolver (
+ logger: Log,
+ loadDebugSymbols: true,
+ loadReaderParameters: r
+ );
+ }
+
public static int Main (string [] args)
{
var app = new App ();
@@ -52,6 +74,34 @@ public static int Main (string [] args)
return 0;
}
+ static void Log (TraceLevel level, string message)
+ {
+ switch (level) {
+ case TraceLevel.Error:
+ ColorMessage ($"{AppName}: error: ", ConsoleColor.Red, Console.Error, writeLine: false);
+ ColorMessage (message, ConsoleColor.Red, Console.Error);
+ break;
+ case TraceLevel.Warning:
+ ColorMessage ($"{AppName}: warning: ", ConsoleColor.Yellow, Console.Error, writeLine: false);
+ ColorMessage (message, ConsoleColor.Yellow, Console.Error);
+ break;
+ case TraceLevel.Info:
+ if (Verbose)
+ ColorMessage (message, ConsoleColor.Cyan, Console.Out);
+ break;
+ case TraceLevel.Verbose:
+ if (Verbosity > 1) {
+ Console.WriteLine (message);
+ }
+ break;
+ default:
+ if (level == 0 || ((int) level) > Verbosity) {
+ Console.WriteLine (message);
+ }
+ break;
+ }
+ }
+
void AddMonoPathToResolverSearchDirectories ()
{
var monoPath = Environment.GetEnvironmentVariable ("MONO_PATH");
@@ -111,9 +161,9 @@ List ProcessArguments (string [] args)
{ "t|type=",
"Generate marshaling methods only for types whose names match {TYPE-REGEX}.",
v => typeNameRegexes.Add (new Regex (v)) },
- { "v|verbose",
+ { "v|verbose:",
"Output information about progress during the run of the tool",
- v => Verbose = true },
+ (int? v) => Verbosity = v.HasValue ? v.Value : Verbosity + 1 },
new ResponseFileSource(),
};
@@ -151,36 +201,71 @@ void ProcessAssemblies (List assemblies)
{
CreateJavaVM (jvmDllPath);
- var readerParameters = new ReaderParameters {
+ var readerParameters = new ReaderParameters {
AssemblyResolver = resolver,
InMemory = true,
ReadSymbols = true,
- ReadWrite = string.IsNullOrEmpty (outDirectory),
+ ReadWrite = false,
};
var readerParametersNoSymbols = new ReaderParameters {
AssemblyResolver = resolver,
InMemory = true,
ReadSymbols = false,
- ReadWrite = string.IsNullOrEmpty (outDirectory),
+ ReadWrite = false,
+ };
+
+ foreach (var r in references) {
+ resolver.SearchDirectories.Add (Path.GetDirectoryName (r));
+ }
+ foreach (var assembly in assemblies) {
+ resolver.SearchDirectories.Add (Path.GetDirectoryName (assembly));
+ }
+ var corlibDir = Path.GetDirectoryName (typeof (object).Assembly.Location);
+ if (corlibDir != null) {
+ resolver.SearchDirectories.Add (corlibDir);
+ }
+
+ // loadContext = CreateLoadContext ();
+ AppDomain.CurrentDomain.AssemblyResolve += (o, e) => {
+ Log (TraceLevel.Verbose, $"# jonp: resolving assembly: {e.Name}");
+ foreach (var d in resolver.SearchDirectories) {
+ var a = Path.Combine (d, e.Name);
+ var f = a + ".dll";
+ if (File.Exists (f)) {
+ return Assembly.LoadFile (Path.GetFullPath (f));
+ }
+ f = a + ".exe";
+ if (File.Exists (f)) {
+ return Assembly.LoadFile (Path.GetFullPath (f));
+ }
+ }
+ return null;
};
foreach (var r in references) {
try {
+ // loadContext.LoadFromAssemblyPath (Path.GetFullPath (r));
Assembly.LoadFile (Path.GetFullPath (r));
- } catch (Exception) {
+ } catch (Exception e) {
+ Console.WriteLine (e);
ErrorAndExit (Message.ErrorUnableToPreloadReference, r);
}
- resolver.SearchDirectories.Add (Path.GetDirectoryName (r));
}
foreach (var assembly in assemblies) {
if (!File.Exists (assembly)) {
ErrorAndExit (Message.ErrorPathDoesNotExist, assembly);
}
+ bool inPlaceUpdate = string.IsNullOrEmpty (outDirectory) ||
+ string.Equals (Path.GetFullPath (outDirectory), Path.GetDirectoryName (Path.GetFullPath (assembly)), StringComparison.OrdinalIgnoreCase);
+
+ readerParameters.ReadWrite = readerParametersNoSymbols.ReadWrite = inPlaceUpdate;
- resolver.SearchDirectories.Add (Path.GetDirectoryName (assembly));
AssemblyDefinition ad;
try {
+ if (inPlaceUpdate) {
+ File.Copy (assembly, assembly + ".orig");
+ }
ad = AssemblyDefinition.ReadAssembly (assembly, readerParameters);
resolver.AddToCache (ad);
} catch (Exception) {
@@ -197,7 +282,6 @@ void ProcessAssemblies (List assemblies)
foreach (var assembly in assemblies) {
try {
CreateMarshalMethodAssembly (assembly);
- definedTypes.Clear ();
} catch (Exception e) {
ErrorAndExit (Message.ErrorUnableToProcessAssembly, assembly, Environment.NewLine, e.Message, e);
}
@@ -206,6 +290,9 @@ void ProcessAssemblies (List assemblies)
void CreateJavaVM (string jvmDllPath)
{
+ if (string.IsNullOrEmpty (jvmDllPath)) {
+ jvmDllPath = ReadJavaSdkDirectoryFromJdkInfoProps ();
+ }
var builder = new JreRuntimeOptions {
JvmLibraryPath = jvmDllPath,
};
@@ -217,27 +304,72 @@ void CreateJavaVM (string jvmDllPath)
}
}
- static JniRuntime.JniMarshalMemberBuilder CreateExportedMemberBuilder ()
+ static string ReadJavaSdkDirectoryFromJdkInfoProps ()
{
- return JniEnvironment.Runtime.MarshalMemberBuilder;
- }
+ var location = typeof (App).Assembly.Location; // …/bin/Debug-net7.0/jnimarshalmethod-gen.dll
+ var binDir = Path.GetDirectoryName (Path.GetDirectoryName (location)) ?? Environment.CurrentDirectory;
+ var dirName = Path.GetFileName (Path.GetDirectoryName (location));
+ if (binDir == null || dirName == null) {
+ return null;
+ }
+ if (!dirName.StartsWith ("Debug", StringComparison.OrdinalIgnoreCase) &&
+ !dirName.StartsWith ("Release", StringComparison.OrdinalIgnoreCase)) {
+ return null;
+ }
+ var buildName = "Build" + dirName;
+ if (buildName.Contains ('-')) {
+ buildName = buildName.Substring (0, buildName.IndexOf ('-'));
+ }
+ var jdkPropFile = Path.Combine (binDir, buildName, "JdkInfo.props");
+ if (!File.Exists (jdkPropFile)) {
+ return null;
+ }
- static TypeBuilder GetTypeBuilder (ModuleBuilder mb, Type type)
- {
- if (definedTypes.ContainsKey (type.FullName))
- return definedTypes [type.FullName];
-
- if (type.IsNested) {
- var outer = GetTypeBuilder (mb, type.DeclaringType);
- var nested = outer.DefineNestedType (type.Name, System.Reflection.TypeAttributes.NestedPublic);
- definedTypes [type.FullName] = nested;
- return nested;
+ var msbuild = XNamespace.Get ("http://schemas.microsoft.com/developer/msbuild/2003");
+
+ var jdkProps = XDocument.Load (jdkPropFile);
+ var jdkJvmPath = jdkProps.Elements ()
+ .Elements (msbuild + "Choose")
+ .Elements (msbuild + "When")
+ .Elements (msbuild + "PropertyGroup")
+ .Elements (msbuild + "JdkJvmPath")
+ .FirstOrDefault ();
+ if (jdkJvmPath == null) {
+ return null;
}
+ return jdkJvmPath.Value;
+ }
- var tb = mb.DefineType (type.FullName, System.Reflection.TypeAttributes.Public);
- definedTypes [type.FullName] = tb;
+ AssemblyLoadContext CreateLoadContext ()
+ {
+ var c = new AssemblyLoadContext ("jnimarshalmethod-gen", isCollectible: true);
+ c.Resolving += (context, name) => {
+ Log (TraceLevel.Verbose, $"# jonp: trying to load assembly: {name}");
+ if (name.Name == "Java.Interop") {
+ return typeof (IJavaPeerable).Assembly;
+ }
+ if (name.Name == "Java.Interop.Export") {
+ return typeof (JavaCallableAttribute).Assembly;
+ }
+ foreach (var d in resolver.SearchDirectories) {
+ var a = Path.Combine (d, name.Name);
+ var f = a + ".dll";
+ if (File.Exists (f)) {
+ return context.LoadFromAssemblyPath (Path.GetFullPath (f));
+ }
+ f = a + ".exe";
+ if (File.Exists (f)) {
+ return context.LoadFromAssemblyPath (Path.GetFullPath (f));
+ }
+ }
+ return null;
+ };
+ return c;
+ }
- return tb;
+ static JniRuntime.JniMarshalMemberBuilder CreateExportedMemberBuilder ()
+ {
+ return JniEnvironment.Runtime.MarshalMemberBuilder;
}
class MethodsComparer : IComparer
@@ -278,7 +410,6 @@ public int Compare (MethodInfo a, MethodInfo b)
void CreateMarshalMethodAssembly (string path)
{
- var assembly = Assembly.Load (File.ReadAllBytes (Path.GetFullPath (path)));
var baseName = Path.GetFileNameWithoutExtension (path);
var assemblyName = new AssemblyName (baseName + "-JniMarshalMethods");
var fileName = assemblyName.Name + ".dll";
@@ -289,17 +420,18 @@ void CreateMarshalMethodAssembly (string path)
if (Verbose)
ColorWriteLine ($"Preparing marshal method assembly '{assemblyName}'", ConsoleColor.Cyan);
- var da = AppDomain.CurrentDomain.DefineDynamicAssembly (
- assemblyName,
- AssemblyBuilderAccess.Save,
- destDir);
-
- var dm = da.DefineDynamicModule ("", fileName);
-
var ad = resolver.GetAssembly (path);
+ var assemblyBuilder = new ExpressionAssemblyBuilder (ad, Log) {
+ KeepTemporaryFiles = keepTemporary,
+ };
+
PrepareTypeMap (ad.MainModule);
+// var assembly = loadContext.LoadFromStream (File.OpenRead (path));
+ var assemblyBytes = File.ReadAllBytes (path);
+ var assembly = Assembly.Load (assemblyBytes);
+
Type[] types = null;
try {
types = assembly.GetTypes ();
@@ -307,6 +439,9 @@ void CreateMarshalMethodAssembly (string path)
types = e.Types;
foreach (var le in e.LoaderExceptions)
Warning (Message.WarningTypeLoadException, Environment.NewLine, le);
+ if (Verbose) {
+ ColorMessage ($"Exception: {e.ToString ()}", ConsoleColor.Red, Console.Error);
+ }
}
foreach (var systemType in types) {
@@ -349,9 +484,8 @@ void CreateMarshalMethodAssembly (string path)
if (Verbose)
ColorWriteLine ($"Processing {type} type", ConsoleColor.Yellow);
- var registrationElements = new List ();
+ var registrations = new List ();
var targetType = Expression.Variable (typeof(Type), "targetType");
- TypeBuilder dt = null;
var flags = BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Instance | BindingFlags.Static;
@@ -360,15 +494,27 @@ void CreateMarshalMethodAssembly (string path)
Array.Sort (methods, new MethodsComparer (type, td));
addedMethods.Clear ();
+ var mmTypeDef = new TypeDefinition (
+ @namespace: null,
+ name: TypeMover.NestedName,
+ attributes: Mono.Cecil.TypeAttributes.NestedPrivate
+ );
+ mmTypeDef.BaseType = assemblyBuilder.DeclaringAssemblyDefinition.MainModule.TypeSystem.Object;
foreach (var method in methods) {
// TODO: Constructors
var export = method.GetCustomAttribute ();
+ var exportObj = method.GetCustomAttributes (inherit:false).SingleOrDefault (a => a.GetType ().Name == "JavaCallableAttribute");
string signature = null;
string name = null;
string methodName = method.Name;
- if (export == null) {
+ if (exportObj != null) {
+ dynamic e = exportObj;
+ name = e.Name;
+ signature = e.Signature;
+ }
+ else {
if (method.IsGenericMethod || method.ContainsGenericParameters || method.IsGenericMethodDefinition || method.ReturnType.IsGenericType)
continue;
@@ -388,59 +534,63 @@ void CreateMarshalMethodAssembly (string path)
continue;
}
- if (dt == null)
- dt = GetTypeBuilder (dm, type);
-
- if (addedMethods.Contains (methodName))
+ if (addedMethods.Contains (methodName)) {
+ Log (TraceLevel.Verbose, $"# jonp: method `{methodName}` already added (?!)");
continue;
+ }
if (Verbose) {
Console.Write ("Adding marshal method for ");
ColorWriteLine ($"{method}", ConsoleColor.Green );
}
- var mb = dt.DefineMethod (
- methodName,
- System.Reflection.MethodAttributes.Public | System.Reflection.MethodAttributes.Static);
-
var lambda = builder.CreateMarshalToManagedExpression (method);
- lambda.CompileToMethod (mb);
+#if _DUMP_REGISTER_NATIVE_MEMBERS
+ Log (TraceLevel.Verbose, $"## Dumping contents of marshal method for `{td.FullName}::{method.Name}({string.Join (", ", method.GetParameters ().Select (p => p.ParameterType))})`:");
+ Console.WriteLine (lambda.ToCSharpCode ());
+#endif // _DUMP_REGISTER_NATIVE_MEMBERS
+ var mmDef = assemblyBuilder.Compile (lambda);
+ mmDef.Name = export?.Name ?? ("n_TODO" + lambda.GetHashCode ());
+ mmTypeDef.Methods.Add (mmDef);
if (export != null) {
name = export.Name;
signature = export.Signature;
}
- if (signature == null)
+ if (signature == null) {
signature = builder.GetJniMethodSignature (method);
+ }
- registrationElements.Add (CreateRegistration (name, signature, lambda, targetType, methodName));
+ registrations.Add (new ExpressionMethodRegistration (name, signature, mmDef));
addedMethods.Add (methodName);
}
- if (dt != null)
- AddRegisterNativeMembers (dt, targetType, registrationElements);
+ if (registrations.Count > 0) {
+ var m = assemblyBuilder.CreateRegistrationMethod (registrations);
+ mmTypeDef.Methods.Add (m);
+ td.NestedTypes.Add (mmTypeDef);
+ }
}
- foreach (var tb in definedTypes)
- tb.Value.CreateType ();
-
- da.Save (fileName);
-
if (Verbose)
ColorWriteLine ($"Marshal method assembly '{assemblyName}' created", ConsoleColor.Cyan);
resolver.SearchDirectories.Add (destDir);
- var dstAssembly = resolver.GetAssembly (fileName);
+ // var dstAssembly = resolver.GetAssembly (fileName);
- if (!string.IsNullOrEmpty (outDirectory))
+ if (!string.IsNullOrEmpty (outDirectory)) {
+ Directory.CreateDirectory (outDirectory);
path = Path.Combine (outDirectory, Path.GetFileName (path));
+ }
+
+ assemblyBuilder.Write (path);
- var mover = new TypeMover (dstAssembly, ad, path, definedTypes, resolver, cache);
- mover.Move ();
+ // var mover = new TypeMover (dstAssembly, ad, path, definedTypes, resolver, cache);
+ // mover.Move ();
- if (!keepTemporary)
- FilesToDelete.Add (dstAssembly.MainModule.FileName);
+ // if (!keepTemporary)
+ // FilesToDelete.Add (dstAssembly.MainModule.FileName);
}
static readonly MethodInfo Delegate_CreateDelegate = typeof (Delegate).GetMethod ("CreateDelegate", new[] {
@@ -482,32 +632,6 @@ static Expression CreateRegistration (string method, string signature, LambdaExp
d);
}
- static void AddRegisterNativeMembers (TypeBuilder dt, ParameterExpression targetType, List registrationElements)
- {
- if (Verbose) {
- Console.Write ("Adding registration method for ");
- ColorWriteLine ($"{dt.FullName}", ConsoleColor.Green);
- }
-
- var args = Expression.Parameter (typeof (JniNativeMethodRegistrationArguments), "args");
- var body = Expression.Block (
- new[]{targetType},
- Expression.Assign (targetType, Expression.Call (Type_GetType, Expression.Constant (dt.FullName))),
- Expression.Call (args, JniNativeMethodRegistrationArguments_AddRegistrations, Expression.NewArrayInit (typeof (JniNativeMethodRegistration), registrationElements.ToArray ())));
-
- var lambda = Expression.Lambda> (body, new[]{ args });
-
- var rb = dt.DefineMethod ("__RegisterNativeMembers",
- System.Reflection.MethodAttributes.Public | System.Reflection.MethodAttributes.Static);
- rb.SetParameters (typeof (JniNativeMethodRegistrationArguments));
- rb.SetCustomAttribute (new CustomAttributeBuilder (typeof (JniAddNativeMethodRegistrationAttribute).GetConstructor (Type.EmptyTypes), new object[0]));
-#if _DUMP_REGISTER_NATIVE_MEMBERS
- Console.WriteLine ($"## Dumping contents of `{dt.FullName}::__RegisterNativeMembers`: ");
- Console.WriteLine (lambda.ToCSharpCode ());
-#endif // _DUMP_REGISTER_NATIVE_MEMBERS
- lambda.CompileToMethod (rb);
- }
-
static void ColorMessage (string message, ConsoleColor color, TextWriter writer, bool writeLine = true)
{
Console.ForegroundColor = color;
@@ -702,3 +826,36 @@ public static bool NeedsMarshalMethod (this MethodDefinition md, DirectoryAssemb
}
}
}
+
+class _Jonp_ReferenceCodeGen
+{
+ static void A (IntPtr jnienv, IntPtr klass, int value)
+ {
+ JniRuntime jvm = JniEnvironment.Runtime;
+ JniRuntime.JniValueManager vm;
+
+ var envp = new JniTransition (jnienv);
+ try {
+ vm = jvm.ValueManager;
+ vm.WaitForGCBridgeProcessing ();
+ } catch (Exception e) when (jvm.ExceptionShouldTransitionToJni (e)) {
+ envp.SetPendingException (e);
+ } finally {
+ envp.Dispose ();
+ }
+ }
+
+ static void B ()
+ {
+ }
+
+ [JniAddNativeMethodRegistration]
+ static void RegisterNativeMethods (JniNativeMethodRegistrationArguments args)
+ {
+ var methods = new [] {
+ new JniNativeMethodRegistration ("a", "()V", new Action (A)),
+ new JniNativeMethodRegistration ("b", "()V", new Action (B)),
+ };
+ args.AddRegistrations (methods);
+ }
+}
diff --git a/tools/jnimarshalmethod-gen/Xamarin.Android.Tools.JniMarshalMethodGenerator.csproj b/tools/jnimarshalmethod-gen/Xamarin.Android.Tools.JniMarshalMethodGenerator.csproj
index c540acb30..746c6d749 100644
--- a/tools/jnimarshalmethod-gen/Xamarin.Android.Tools.JniMarshalMethodGenerator.csproj
+++ b/tools/jnimarshalmethod-gen/Xamarin.Android.Tools.JniMarshalMethodGenerator.csproj
@@ -1,7 +1,7 @@
- net472
+ $(DotNetTargetFramework)
Exe
8.0
jnimarshalmethod-gen
@@ -13,6 +13,7 @@
$(UtilityOutputFullPath)
+ <_DumpRegisterNativeMembers>True
@@ -35,6 +36,7 @@
+