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/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..351ff035a 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..18874c9e3
--- /dev/null
+++ b/src/Java.Interop.Tools.Expressions/Java.Interop.Tools.Expressions/CecilCompilerExpressionVisitor.cs
@@ -0,0 +1,904 @@
+namespace Java.Interop.Tools.Expressions;
+
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Linq.Expressions;
+
+using Mono.Cecil;
+using Mono.Cecil.Cil;
+
+class CecilCompilerExpressionVisitor : ExpressionVisitor
+{
+ public CecilCompilerExpressionVisitor (AssemblyDefinition declaringAssembly, MethodBody body, LambdaExpression lambda, VariableDefinitions variables)
+ {
+ this.assemblyDef = declaringAssembly;
+ this.lambda = lambda;
+ this.body = body;
+ this.variables = variables;
+ il = body.GetILProcessor ();
+ }
+
+ AssemblyDefinition assemblyDef;
+ MethodBody body;
+ ILProcessor il;
+ LambdaExpression lambda;
+ VariableDefinitions variables;
+ Dictionary> returnFixups = new ();
+
+ ///
+ /// 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)
+ {
+ Console.WriteLine ($"# 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)
+ {
+ Console.WriteLine ($"# jonp: CecilCompilerExpressionVisitor.VisitBinary: {node} [{node.NodeType}]");
+ switch (node.NodeType) {
+ case ExpressionType.Assign:
+ Visit (node.Right);
+ Console.WriteLine ($"# jonp: CecilCompilerExpressionVisitor.VisitBinary: visited right");
+ if (node.Left is ParameterExpression dest) {
+ variables [dest].Store (il);
+ } else {
+ Console.WriteLine ($"# jonp: don't know where to assign `{node.Left}`!");
+ }
+ break;
+ case ExpressionType.Equal:
+ Visit (node.Left);
+ Visit (node.Right);
+ il.Emit (OpCodes.Ceq);
+ break;
+ default:
+ Console.WriteLine ($"# jonp: don't know how to emit binary expr {node.NodeType}!");
+ base.VisitBinary (node);
+ break;
+ }
+ 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 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);
+ Console.WriteLine ($"# 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)
+ {
+ Console.WriteLine ($"# 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)
+ {
+ Console.WriteLine ($"# 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)) {
+ Console.WriteLine ($"# jonp: TODO load type {node.Value}");
+ break;
+ } else if (node.Value == null) {
+ Console.WriteLine ($"# jonp: TODO ldnull {node.Value}");
+ il.Emit (OpCodes.Ldnull);
+ break;
+ }
+ goto default;
+ default:
+ Console.WriteLine ($"# 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)
+ {
+ Console.WriteLine ($"# 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)
+ {
+ Console.WriteLine ($"# 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)
+ {
+ Console.WriteLine ($"# 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)
+ {
+ Console.WriteLine ($"# 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;
+ if (!returnFixups.TryGetValue (node.Target, out fixups)) {
+ returnFixups.Add (node.Target, fixups = new ());
+ }
+ fixups.Add (il.Body.Instructions.Last ());
+ Console.WriteLine ($"# 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)
+ {
+ Console.WriteLine ($"# 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)
+ {
+ Console.WriteLine ($"# jonp: CecilCompilerExpressionVisitor.VisitLabelTarget: {node}");
+ if (node != null) {
+ 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)
+ {
+ Console.WriteLine ($"# jonp: CecilCompilerExpressionVisitor.VisitLabel: {node}");
+ var target = il.Body.Instructions.Last ();
+ if (returnFixups.TryGetValue (node.Target, out var fixups)) {
+ foreach (var replace in fixups) {
+ 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)
+ {
+ Console.WriteLine ($"# 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)
+ {
+ Console.WriteLine ($"# 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)
+ {
+ Console.WriteLine ($"# 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 (
+ getter!.IsStatic ? OpCodes.Call: OpCodes.Callvirt,
+ 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)
+ {
+ Console.WriteLine ($"# 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)
+ {
+ Console.WriteLine ($"# jonp: CecilCompilerExpressionVisitor.VisitMethodCall: {node}");
+
+/*
+ foreach (var a in node.Arguments) {
+ switch (a.NodeType) {
+ case ExpressionType.Constant:
+ var c = (ConstantExpression) a;
+ switch (Type.GetTypeCode (a.Type)) {
+ case TypeCode.String:
+ il.Emit (OpCodes.Ldstr, (string) c.Value);
+ break;
+ case TypeCode.Int32:
+ il.Emit (OpCodes.Ldc_I4, (int) c.Value);
+ break;
+ default:
+ // throw new NotSupportedException ();
+ break;
+ }
+ break;
+ default:
+ // throw new NotSupportedException ();
+ break;
+ }
+ }
+ Console.WriteLine ($"# jonp: node.Method? {node.Method != null}");
+ var methodRef = assemblyDef.MainModule.ImportReference (node.Method);
+ Console.WriteLine ($"# jonp: methodRef? {methodRef != null}");
+ il.Emit (OpCodes.Call, methodRef);
+
+ Console.WriteLine ($"# jonp: begin base.VisitMethodCall {node}");
+ base.VisitMethodCall (node);
+ Console.WriteLine ($"# jonp: end base.VisitMethodCall {node}");
+*/
+ // Visit (node.Object);
+ base.VisitMethodCall (node);
+ il.Emit (OpCodes.Call, assemblyDef.MainModule.ImportReference (node.Method));
+ 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 VisitNewArray (
+ NewArrayExpression node)
+ {
+ Console.WriteLine ($"# 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)
+ {
+ Console.WriteLine ($"# jonp: CecilCompilerExpressionVisitor.VisitNew: {node}");
+ Console.WriteLine ($"# 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)
+ {
+ Console.WriteLine ($"# jonp: CecilCompilerExpressionVisitor.VisitParameter: {node}");
+
+ 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)
+ {
+ Console.WriteLine ($"# 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)
+ {
+ Console.WriteLine ($"# 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)
+ {
+ Console.WriteLine ($"# 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.
+
+ Console.WriteLine($"# 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) {
+ variables [node.Variable!].Store (il);
+ } else {
+ il.Emit (OpCodes.Pop);
+ }
+
+ Visit (node.Body);
+
+ 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)
+ {
+ Console.WriteLine ($"# 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 ());
+ }
+ }
+
+#if false
+ protected override Expression VisitTry (
+ TryExpression node)
+ {
+ var tryStart = this.tryStart;
+ this.tryStart = il.
+ Console.WriteLine ($"# jonp: CecilCompilerExpressionVisitor.VisitTry: {node}");
+ // base.VisitTry (node);
+ // Console.WriteLine ($"# jonp: CecilCompilerExpressionVisitor.VisitTry-END: {node}");
+ var fixupLeaveOffsets = new List ();
+
+ var tryStart = EmitBlockBoundary (node.Body);
+ foreach (var handler in node.Handlers) {
+ var catchIndex = il.Body.Instructions.Count;
+ variables [handler.Variable].Store (il);
+ EmitBlockBoundary (handler.Body);
+
+ var catchStart = il.Body.Instructions [catchIndex];
+
+ var handlerDef = new ExceptionHandler (ExceptionHandlerType.Catch) {
+ TryStart = tryStart,
+ HandlerStart = catchStart,
+ CatchType = assemblyDef.MainModule.ImportReference (handler.Test),
+ };
+ body.ExceptionHandlers.Add (handlerDef);
+ }
+ if (node.Finally != null) {
+ var finallyStart = EmitBlockBoundary (node.Finally, emitLeave: false);
+ il.Emit (OpCodes.Endfinally);
+
+ var finallyDef = new ExceptionHandler (ExceptionHandlerType.Finally) {
+ TryStart = tryStart,
+ HandlerStart = finallyStart,
+ };
+ body.ExceptionHandlers.Add (finallyDef);
+ }
+
+ il.Emit (OpCodes.Nop);
+ var fixupTarget = il.Body.Instructions.Last ();
+
+ // ECMA 335 Partition X § 19 Exception Handling
+ // HandlerBlock ::= `handler` Label to Label
+ // Handler range is from first label ***prior to*** second (emphasis @jonpryor)
+
+ for (int i = 0; i < (body.ExceptionHandlers.Count-1); ++i) {
+ var c = body.ExceptionHandlers [i];
+ var n = body.ExceptionHandlers [i+1];
+ c.TryEnd = c.HandlerStart;
+ c.HandlerEnd = 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;
+ }
+
+ this.tryStart = tryStart;
+
+ return node;
+ // return base.VisitTry (node);
+
+ Instruction EmitBlockBoundary (Expression block, bool emitLeave = true)
+ {
+ var startIndex = il.Body.Instructions.Count;
+ Visit (block);
+ if (il.Body.Instructions.Count == startIndex) {
+ il.Emit (OpCodes.Nop);
+ }
+ var startInstruction = il.Body.Instructions [startIndex];
+
+ // keep in sync w/ VisitGoto()
+ // Prevent multiple `leave OFFSET`s in the output
+ if (emitLeave && il.Body.Instructions.Last ().OpCode.Code != Code.Ret) {
+ il.Emit (OpCodes.Leave, startInstruction);
+ fixupLeaveOffsets.Add (il.Body.Instructions.Last ());
+ }
+
+ return startInstruction;
+ }
+ }
+#endif
+
+ ///
+ /// 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)
+ {
+ Console.WriteLine ($"# 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)
+ {
+ Console.WriteLine ($"# 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)
+ {
+ Console.WriteLine ($"# 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)
+ {
+ Console.WriteLine ($"# 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)
+ {
+ Console.WriteLine ($"# 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)
+ {
+ Console.WriteLine ($"# 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)
+ {
+ Console.WriteLine ($"# 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)
+ {
+ Console.WriteLine ($"# 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)
+ {
+ Console.WriteLine ($"# 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)
+ {
+ Console.WriteLine ($"# 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..0d0cb5811
--- /dev/null
+++ b/src/Java.Interop.Tools.Expressions/Java.Interop.Tools.Expressions/ExpressionAssemblyBuilder.cs
@@ -0,0 +1,321 @@
+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;
+
+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 MethodDefinition Compile (LambdaExpression expression)
+ {
+ var mmDef = CreateMethodDefinition (DeclaringAssemblyDefinition, expression);
+ var decls = new VariableDefinitions (DeclaringAssemblyDefinition, mmDef, expression);
+ var mmBody = mmDef.Body;
+
+ var v = new CecilCompilerExpressionVisitor (DeclaringAssemblyDefinition, mmBody, expression, decls);
+ v.Visit (expression);
+
+ var il = mmBody.GetILProcessor ();
+ 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)
+ );
+ 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
+ );
+ 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 ();
+ il.Emit (OpCodes.Ldc_I4, methods.Count);
+ il.Emit (OpCodes.Newarr, DeclaringAssemblyDefinition.MainModule.ImportReference (typeof (JniNativeMethodRegistration)));
+ 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.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); // args [i] = new JniNativeMethodRegistration (…)
+ }
+
+ Action> addRegistrations = new JniNativeMethodRegistrationArguments ().AddRegistrations;
+ il.Emit (OpCodes.Ldarg_0);
+ il.Emit (OpCodes.Ldloc_0);
+ il.Emit (OpCodes.Call, DeclaringAssemblyDefinition.MainModule.ImportReference (addRegistrations.Method));
+ il.Emit (OpCodes.Ret);
+
+
+ return registrations;
+ }
+
+ // 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 = new StringBuilder ();
+ delegateName.Append ("_JniMarshal_PP");
+
+ for (int i = 2; i < method.Parameters.Count; i++) {
+ delegateName.Append (GetJniMarshalDelegateParameterIdentifier (method.Parameters [i].ParameterType));
+ }
+ delegateName.Append ("_");
+ delegateName.Append (GetJniMarshalDelegateParameterIdentifier (method.ReturnType));
+
+ var delegateDef = DeclaringAssemblyDefinition.MainModule.GetType (delegateName.ToString ());
+ if (delegateDef != null) {
+ return delegateDef.Methods.First (m => m.Name == ".ctor");
+ }
+ delegateDef = new TypeDefinition (
+ @namespace: "",
+ name: delegateName.ToString (),
+ 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: method.ReturnType
+ );
+ invoke.ImplAttributes = MethodImplAttributes.Runtime | MethodImplAttributes.Managed;
+ foreach (var p in method.Parameters) {
+ invoke.Parameters.Add (new ParameterDefinition (p.Name, p.Attributes, p.ParameterType));
+ }
+ delegateDef.Methods.Add (invoke);
+
+ // BeginInvoke() EndInvoke() appear to be automagically added, in that ikdasm shows they exist, even if we don't add them.
+
+ DeclaringAssemblyDefinition.MainModule.Types.Add (delegateDef);
+
+ return delegateCtor;
+ }
+
+ 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 void Write (string path)
+ {
+ DeclaringAssemblyDefinition.Write (path);
+ }
+}
+
+sealed class VariableInfo {
+ public VariableInfo (Action load, Action store)
+ {
+ Load = load;
+ Store = store;
+ }
+
+ public Action Load;
+ public Action Store;
+}
+
+sealed class VariableDefinitions {
+
+ Dictionary variables = new ();
+
+ public VariableDefinitions (AssemblyDefinition declaringAssembly, MethodDefinition declaringMethod, LambdaExpression expression)
+ {
+ 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.Starg, 0));
+ break;
+ case 1:
+ v = new VariableInfo (il => il.Emit (OpCodes.Ldarg_1), il => il.Emit (OpCodes.Starg, 1));
+ break;
+ case 2:
+ v = new VariableInfo (il => il.Emit (OpCodes.Ldarg_2), il => il.Emit (OpCodes.Starg, 2));
+ break;
+ case 3:
+ v = new VariableInfo (il => il.Emit (OpCodes.Ldarg_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.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);
+ 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.Stloc_0));
+ break;
+ case 1:
+ v = new VariableInfo (il => il.Emit (OpCodes.Ldloc_1), il => il.Emit (OpCodes.Stloc_1));
+ break;
+ case 2:
+ v = new VariableInfo (il => il.Emit (OpCodes.Ldloc_2), il => il.Emit (OpCodes.Stloc_2));
+ break;
+ case 3:
+ v = new VariableInfo (il => il.Emit (OpCodes.Ldloc_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.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)
+ {
+ this.arguments = arguments;
+ }
+
+ ICollection arguments;
+
+ public List Variables = new ();
+ public ParameterExpression? ReturnValue;
+
+ protected override Expression VisitGoto (
+ GotoExpression node)
+ {
+ Console.WriteLine ($"# jonp: VariableExpressionVisitor.Goto: {node}");
+ if (node.Kind != GotoExpressionKind.Return) {
+ return base.VisitGoto (node);
+ }
+ if (node.Type == typeof (void)) {
+ return base.VisitGoto (node);
+ }
+ if (ReturnValue != null) {
+ return base.VisitGoto (node);
+ }
+ var p = Expression.Parameter (node.Type, "__goto.Return.Temporary");
+ Variables.Add (p);
+ 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/src/Java.Interop.Tools.Expressions/Java.Interop.Tools.Expressions/RemappingReflectionImporter.cs b/src/Java.Interop.Tools.Expressions/Java.Interop.Tools.Expressions/RemappingReflectionImporter.cs
new file mode 100644
index 000000000..fef5b25df
--- /dev/null
+++ b/src/Java.Interop.Tools.Expressions/Java.Interop.Tools.Expressions/RemappingReflectionImporter.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Reflection;
+using Mono.Cecil;
+
+namespace Java.Interop.Tools.Expressions;
+
+class RemappingReflectionImporter : DefaultReflectionImporter
+{
+ static RemappingReflectionImporter ()
+ {
+ var rp = new ReaderParameters {
+ ReadingMode = ReadingMode.Deferred,
+ InMemory = true,
+ ReadSymbols = false,
+ ReadWrite = false,
+ ApplyWindowsRuntimeProjections = false,
+
+ };
+ var runtimeDir = Path.GetDirectoryName (typeof (object).Assembly.Location) ?? throw new InvalidOperationException ("Should not be reached; can't find assembly containing System.Object?!");
+ var runtimePath = Path.Combine (runtimeDir, "System.Runtime.dll");
+ if (!File.Exists (runtimePath)) {
+ throw new InvalidOperationException ($"Should not be reached; unable to find `System.Runtime.dll` at `{runtimePath}`.");
+ }
+ using var runtime = AssemblyDefinition.ReadAssembly (runtimePath, rp);
+ System_RuntimeAssemblyNameReference = new AssemblyNameReference (runtime.Name.Name, runtime.Name.Version) {
+ PublicKeyToken = runtime.Name.PublicKeyToken,
+ Culture = runtime.Name.Culture,
+ HashAlgorithm = runtime.Name.HashAlgorithm,
+ };
+ }
+
+ static readonly AssemblyNameReference System_RuntimeAssemblyNameReference;
+
+ public RemappingReflectionImporter (ModuleDefinition module)
+ : base (module)
+ {
+ }
+
+ bool IsSameAssembly (Assembly assembly) =>
+ base.module.Mvid == assembly.ManifestModule.ModuleVersionId;
+
+ protected override IMetadataScope ImportScope (Type type)
+ {
+ // Needed to prevent adding a self-referencing reference, i.e. `Foo.dll` references `Foo.dll`.
+ if (!IsSameAssembly (type.Assembly)) {
+ return base.ImportScope (type);
+ }
+ return base.module;
+ }
+
+ public override AssemblyNameReference ImportReference (AssemblyName name)
+ {
+ switch (name.Name) {
+ case "System.Private.CoreLib":
+ if (!base.module.AssemblyReferences.Any (r => r.FullName == System_RuntimeAssemblyNameReference.FullName)) {
+ base.module.AssemblyReferences.Add (System_RuntimeAssemblyNameReference);
+ }
+ return System_RuntimeAssemblyNameReference;
+ }
+ return base.ImportReference (name);
+ }
+}
+
diff --git a/src/Java.Interop.Tools.Expressions/Java.Interop.Tools.Expressions/RemappingReflectionImporterProvider.cs b/src/Java.Interop.Tools.Expressions/Java.Interop.Tools.Expressions/RemappingReflectionImporterProvider.cs
new file mode 100644
index 000000000..c729ab53e
--- /dev/null
+++ b/src/Java.Interop.Tools.Expressions/Java.Interop.Tools.Expressions/RemappingReflectionImporterProvider.cs
@@ -0,0 +1,18 @@
+using System;
+
+using Mono.Cecil;
+
+namespace Java.Interop.Tools.Expressions;
+
+public class RemappingReflectionImporterProvider : IReflectionImporterProvider
+{
+ public RemappingReflectionImporterProvider ()
+ {
+ }
+
+ public IReflectionImporter GetReflectionImporter (ModuleDefinition module)
+ {
+ return new RemappingReflectionImporter (module);
+ }
+}
+
diff --git a/tests/Java.Interop.Export-Tests/Java.Interop/MarshalMemberBuilderTest.cs b/tests/Java.Interop.Export-Tests/Java.Interop/MarshalMemberBuilderTest.cs
index 3d117c508..4612dd932 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,11 @@ public void AddExportMethods ()
Assert.AreEqual ("action", methods [0].Name);
Assert.AreEqual ("()V", methods [0].Signature);
- Assert.IsTrue (methods [0].Marshaler is Action);
+ Assert.AreEqual ("_JniMarshal_PP_V", methods [0].Marshaler.GetType ().FullName);
- Assert.AreEqual ("staticAction", methods [1].Name);
- Assert.AreEqual ("()V", methods [1].Signature);
- Assert.IsTrue (methods [1].Marshaler is Action);
+ Assert.AreEqual ("staticAction", methods [1].Name);
+ Assert.AreEqual ("()V", methods [1].Signature);
+ Assert.AreEqual ("_JniMarshal_PP_V", methods [1].Marshaler.GetType ().FullName);
var m = t.GetStaticMethod ("testStaticMethods", "()V");
JniEnvironment.StaticMethods.CallStaticVoidMethod (t.PeerReference, m);
@@ -201,6 +200,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 +221,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 +561,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..21d5cc5e6
--- /dev/null
+++ b/tests/Java.Interop.Tools.Expressions-Tests/Java.Interop.Tools.ExpressionsTests/ExpressionAssemblyBuilderTests.cs
@@ -0,0 +1,423 @@
+namespace Java.Interop.Tools.ExpressionsTests;
+
+using System.IO;
+using System.Linq.Expressions;
+using System.Text;
+
+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 {
+ ReflectionImporterProvider = new RemappingReflectionImporterProvider (),
+ 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);
+ }
+
+ [OneTimeTearDown]
+ public void TearDownTestEnvironment ()
+ {
+ var path = Path.GetDirectoryName (typeof (ExpressionAssemblyBuilderTests).Assembly.Location);
+ 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 Compile_MethodCall ()
+ {
+ Expression e = () => Console.WriteLine ("constant");
+ var m = ExpressionAssemblyBuilder.Compile (e);
+
+ AddMethod (m);
+
+ var expected = new[]{
+ "IL_0000: ldstr \"constant\"",
+ "IL_0000: call System.Void System.Console::WriteLine(System.String)",
+ "IL_0000: ret",
+ };
+ var actual = m.Body.Instructions;
+ Assert.AreEqual (expected.Length, actual.Count);
+ for (int i = 0; i < expected.Length; ++i) {
+ Assert.AreEqual (expected [i], actual [i].ToString ());
+ }
+ }
+
+ [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_0023",
+ // }
+ // 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: stloc.1",
+ "Instruction_0014: ldstr \"filtered\"",
+ "Instruction_0015: call System.Void System.Console::WriteLine(System.String)",
+ "Instruction_0016: ldc.i4 3",
+ "Instruction_0017: stloc.0",
+ "Instruction_0018: leave Instruction_0023",
+ // }
+ // catch class System.Exception {
+ "Instruction_0019: stloc.2",
+ "Instruction_001a: ldloc.2",
+ "Instruction_001b: call System.String System.Object::ToString()",
+ "Instruction_001c: call System.Void System.Console::WriteLine(System.String)",
+ "Instruction_001d: ldc.i4 4",
+ "Instruction_001e: stloc.0",
+ "Instruction_001f: leave Instruction_0023",
+ // }
+ // finally {
+ "Instruction_0020: ldstr \"finally\"",
+ "Instruction_0021: call System.Void System.Console::WriteLine(System.String)",
+ "Instruction_0022: endfinally",
+ // }
+ "Instruction_0023: nop",
+ "Instruction_0024: ldloc.0",
+ "Instruction_0025: 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..1e66a4040 100644
--- a/tools/jnimarshalmethod-gen/App.cs
+++ b/tools/jnimarshalmethod-gen/App.cs
@@ -1,17 +1,21 @@
using System;
using System.Collections.Generic;
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 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,9 +27,8 @@ 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;
@@ -35,8 +38,23 @@ class App : MarshalByRefObject
static List typeNameRegexes = new List ();
static string jvmDllPath;
List FilesToDelete = new List ();
+ // AssemblyLoadContext loadContext;
static string outDirectory;
+ static App()
+ {
+ var r = new ReaderParameters {
+ ReadSymbols = true,
+ InMemory = true,
+ ReflectionImporterProvider = new RemappingReflectionImporterProvider (),
+ };
+ resolver = new DirectoryAssemblyResolver (
+ logger: (l, v) => { Console.WriteLine(v); },
+ loadDebugSymbols: true,
+ loadReaderParameters: r
+ );
+ }
+
public static int Main (string [] args)
{
var app = new App ();
@@ -156,21 +174,48 @@ void ProcessAssemblies (List assemblies)
InMemory = true,
ReadSymbols = true,
ReadWrite = string.IsNullOrEmpty (outDirectory),
+ ReflectionImporterProvider = new RemappingReflectionImporterProvider (),
};
var readerParametersNoSymbols = new ReaderParameters {
AssemblyResolver = resolver,
InMemory = true,
ReadSymbols = false,
ReadWrite = string.IsNullOrEmpty (outDirectory),
+ ReflectionImporterProvider = new RemappingReflectionImporterProvider (),
+ };
+
+ foreach (var r in references) {
+ resolver.SearchDirectories.Add (Path.GetDirectoryName (r));
+ }
+ foreach (var assembly in assemblies) {
+ resolver.SearchDirectories.Add (Path.GetDirectoryName (assembly));
+ }
+
+ // loadContext = CreateLoadContext ();
+ AppDomain.CurrentDomain.AssemblyResolve += (o, e) => {
+ Console.WriteLine ($"# jonp: resolving: {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) {
@@ -178,7 +223,6 @@ void ProcessAssemblies (List assemblies)
ErrorAndExit (Message.ErrorPathDoesNotExist, assembly);
}
- resolver.SearchDirectories.Add (Path.GetDirectoryName (assembly));
AssemblyDefinition ad;
try {
ad = AssemblyDefinition.ReadAssembly (assembly, readerParameters);
@@ -197,7 +241,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);
}
@@ -217,27 +260,36 @@ void CreateJavaVM (string jvmDllPath)
}
}
- static JniRuntime.JniMarshalMemberBuilder CreateExportedMemberBuilder ()
+ AssemblyLoadContext CreateLoadContext ()
{
- return JniEnvironment.Runtime.MarshalMemberBuilder;
+ var c = new AssemblyLoadContext ("jnimarshalmethod-gen", isCollectible: true);
+ c.Resolving += (context, name) => {
+ Console.WriteLine ($"# 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;
}
- static TypeBuilder GetTypeBuilder (ModuleBuilder mb, Type type)
+ static JniRuntime.JniMarshalMemberBuilder CreateExportedMemberBuilder ()
{
- 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 tb = mb.DefineType (type.FullName, System.Reflection.TypeAttributes.Public);
- definedTypes [type.FullName] = tb;
-
- return tb;
+ return JniEnvironment.Runtime.MarshalMemberBuilder;
}
class MethodsComparer : IComparer
@@ -278,7 +330,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 +340,15 @@ 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);
+
PrepareTypeMap (ad.MainModule);
+// var assembly = loadContext.LoadFromStream (File.OpenRead (path));
+ var assembly = Assembly.LoadFrom (Path.GetFullPath (path));
+
Type[] types = null;
try {
types = assembly.GetTypes ();
@@ -307,6 +356,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,26 +401,49 @@ 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;
var methods = type.GetMethods (flags);
Array.Sort (methods, new MethodsComparer (type, td));
+ Console.WriteLine ($"# jonp: methods: {string.Join (Environment.NewLine, methods)}");
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 ();
+ Console.WriteLine ($"# jonp: checking method {method}; export? {export != null}");
+ #if false
+ if (method.Name == "InstanceAction") {
+ foreach (var x in method.GetCustomAttributes (inherit:false)) {
+ if (x.GetType ().Name == "JavaCallableAttribute") {
+ Console.WriteLine ($"# jonp: {x} == {typeof (JavaCallableAttribute)}? {x.GetType () == typeof (JavaCallableAttribute)}");
+ }
+ }
+ }
+ #endif
+ 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 +463,62 @@ void CreateMarshalMethodAssembly (string path)
continue;
}
- if (dt == null)
- dt = GetTypeBuilder (dm, type);
-
- if (addedMethods.Contains (methodName))
+ if (addedMethods.Contains (methodName)) {
+ Console.WriteLine ($"# jonp: method 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
+ Console.WriteLine ($"## 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);
+ Console.WriteLine ($"# jonp: marshal method body instructions count: {mmDef.Body.Instructions.Count}");
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))
path = Path.Combine (outDirectory, Path.GetFileName (path));
- var mover = new TypeMover (dstAssembly, ad, path, definedTypes, resolver, cache);
- mover.Move ();
+ assemblyBuilder.Write (path);
+
+ // 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 +560,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;
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 @@
+