Skip to content

[jnimarshalmethod-gen] Create delegate types for marshaling methods with 15+ parameters #711

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 10 additions & 11 deletions src/Java.Interop.Export/Java.Interop/MarshalMemberBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -254,23 +254,22 @@ static Type GetMarshalerType (Type returnType, List<Type> funcTypeParams, Type d
funcTypeParams.RemoveRange (0, 2);
var marshalDelegateName = new StringBuilder ();
marshalDelegateName.Append ("_JniMarshal_PP");
foreach (var paramType in funcTypeParams) {
marshalDelegateName.Append (GetJniMarshalDelegateParameterIdentifier (paramType));
}
marshalDelegateName.Append ("_");
if (returnType == null) {
marshalDelegateName.Append ("V");
} else {
marshalDelegateName.Append (GetJniMarshalDelegateParameterIdentifier (returnType));
}
AddMarshalerTypeNameSuffix (marshalDelegateName, returnType, funcTypeParams);

Type marshalDelegateType = declaringType.Assembly.GetType (marshalDelegateName.ToString (), throwOnError: false);

// Punt?; System.Linq.Expressions will automagically produce the needed delegate type.
// Unfortunately, this won't work with jnimarshalmethod-gen.exe.
return marshalDelegateType;
}

public static void AddMarshalerTypeNameSuffix (StringBuilder sb, Type returnType, List<Type> funcTypeParams)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ask because I'm wondering if there's some way to not add this public export, or at least find a "more suitable" location for it?

{
foreach (var paramType in funcTypeParams)
sb.Append (GetJniMarshalDelegateParameterIdentifier (paramType));

sb.Append ("_");
sb.Append (returnType == null ? 'V' : GetJniMarshalDelegateParameterIdentifier (returnType));
}

static char GetJniMarshalDelegateParameterIdentifier (Type type)
{
if (type == typeof (bool)) return 'Z';
Expand Down
60 changes: 60 additions & 0 deletions tests/Java.Interop.Export-Tests/Java.Interop/ExportTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,66 @@ public static bool StaticFuncThisMethodTakesLotsOfParameters (
return false;
return true;
}

[JavaCallable ("staticManyParametersWithAutogeneratedDelegateType", Signature="(ZBCSIJFDLjava/lang/Object;Ljava/lang/String;Ljava/util/ArrayList;Ljava/lang/String;Ljava/lang/Object;DFJII)I")]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand how this method differs from the existing "lots of parameters" test:

https://github.com/xamarin/java.interop/blob/03c227228955622511bafabe61a12e30e45dc6f7/tests/Java.Interop.Export-Tests/Java.Interop/ExportTest.cs#L109-L162

How does this (pair of) method(s) differ from the current ExportType.staticFuncThisMethodTakesLotsOfParameters() method? They both have lots of parameters, and they both appear to have "similar" [JavaCallable] custom attribute values. How does this test differ?

public static int staticManyParametersWithAutogeneratedDelegateType (
bool a,
sbyte b,
char c,
short d,
int e,
long f,
float g,
double h,
IntPtr i, // java.lang.Object
IntPtr j, // java.lang.String
IntPtr k, // java.util.ArrayList<String>
IntPtr l, // java.lang.String
IntPtr m, // java.lang.Object
double n,
float o,
long p,
int q,
int rv)
{
if (a != false)
return -1;
if (b != (byte) 0xb)
return -1;
if (c != 'c')
return -1;
if (d != (short) 0xd)
return -1;
if (e != 0xe)
return -1;
if (f != 0xf)
return -1;
if (g != 1.0f)
return -1;
if (h != 2.0)
return -1;
if (i == IntPtr.Zero)
return -1;
if (j == IntPtr.Zero)
return -1;
if (k == IntPtr.Zero)
return -1;
if (l == IntPtr.Zero)
return -1;
if (m == IntPtr.Zero)
return -1;
if (n != 3.0)
return -1;
if (o != 4.0f)
return -1;
if (p != 0x70)
return -1;
if (q != 111111)
return -1;
if (rv != 222222)
return -1;
return rv;
}
}

[JniValueMarshaler (typeof (MyColorValueMarshaler))]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public void AddExportMethods ()
var methods = CreateBuilder ()
.GetExportedMemberRegistrations (typeof (ExportTest))
.ToList ();
Assert.AreEqual (11, methods.Count);
Assert.AreEqual (12, methods.Count);

Assert.AreEqual ("action", methods [0].Name);
Assert.AreEqual ("()V", methods [0].Signature);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,26 @@ public static void testStaticMethods () {
public static native void staticActionInt32String (int i, String s);
public static native int staticFuncMyLegacyColorMyColor_MyColor (int color1, int color2);

public static native int staticManyParametersWithAutogeneratedDelegateType (
boolean a,
byte b,
char c,
short d,
int e,
long f,
float g,
double h,
Object i,
String j,
ArrayList<String> k,
String l,
Object m,
double n,
float o,
long p,
int q,
int rv);

public static native boolean staticFuncThisMethodTakesLotsOfParameters (
boolean a,
byte b,
Expand Down Expand Up @@ -64,6 +84,29 @@ public void testMethods () {
staticActionInt (1);
staticActionFloat (2.0f);

int ivalue = staticManyParametersWithAutogeneratedDelegateType (
false,
(byte) 0xb,
'c',
(short) 0xd,
0xe,
0xf,
1.0f,
2.0,
new Object (),
"j",
new ArrayList<String>(),
"l",
new Object (),
3.0,
4.0f,
0x70,
111111,
222222
);
if (ivalue != 222222)
throw new Error ("staticManyParametersWithAutogeneratedDelegateType should return last parameter value, which is 222222");

boolean r = staticFuncThisMethodTakesLotsOfParameters (
false,
(byte) 0xb,
Expand Down
64 changes: 60 additions & 4 deletions tools/jnimarshalmethod-gen/App.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
Expand All @@ -12,6 +13,7 @@
using Mono.Options;
using Mono.Collections.Generic;
using Java.Interop.Tools.Cecil;
using System.Text;

#if _DUMP_REGISTER_NATIVE_MEMBERS
using Mono.Linq.Expressions;
Expand Down Expand Up @@ -290,6 +292,7 @@ void CreateMarshalMethodAssembly (string path)
var destDir = string.IsNullOrEmpty (outDirectory) ? Path.GetDirectoryName (path) : outDirectory;
var builder = CreateExportedMemberBuilder ();
var matchType = typeNameRegexes.Count > 0;
var newDelegates = new List<Type> ();

if (Verbose)
ColorWriteLine ($"Preparing marshal method assembly '{assemblyName}'", ConsoleColor.Cyan);
Expand Down Expand Up @@ -419,7 +422,7 @@ void CreateMarshalMethodAssembly (string path)
if (signature == null)
signature = builder.GetJniMethodSignature (method);

registrationElements.Add (CreateRegistration (name, signature, lambda, targetType, methodName));
registrationElements.Add (CreateRegistration (name, signature, lambda, targetType, methodName, dm, newDelegates));

addedMethods.Add (methodName);
}
Expand All @@ -441,7 +444,7 @@ void CreateMarshalMethodAssembly (string path)
if (!string.IsNullOrEmpty (outDirectory))
path = Path.Combine (outDirectory, Path.GetFileName (path));

var mover = new TypeMover (dstAssembly, ad, path, definedTypes, resolver, cache);
var mover = new TypeMover (dstAssembly, ad, path, newDelegates, definedTypes, resolver, cache);
mover.Move ();

if (!keepTemporary)
Expand All @@ -465,17 +468,70 @@ void CreateMarshalMethodAssembly (string path)
typeof (string),
});

static Expression CreateRegistration (string method, string signature, LambdaExpression lambda, ParameterExpression targetType, string methodName)
static void CreateDelegateRuntimeManagedMethod (TypeBuilder tb, string name, Type returnType, Type[] parameterTypes)
{
var mb = tb.DefineMethod (name,
System.Reflection.MethodAttributes.Public |
System.Reflection.MethodAttributes.HideBySig |
System.Reflection.MethodAttributes.NewSlot |
System.Reflection.MethodAttributes.Virtual,
CallingConventions.Standard, returnType, parameterTypes);
mb.SetImplementationFlags (System.Reflection.MethodImplAttributes.Runtime | System.Reflection.MethodImplAttributes.Managed);
}

static Expression CreateRegistration (string method, string signature, LambdaExpression lambda, ParameterExpression targetType, string methodName, ModuleBuilder dm, List<Type> createdDelegateList)
{
Expression registrationDelegateType = null;
if (lambda.Type.Assembly == typeof (object).Assembly ||
lambda.Type.Assembly == typeof (System.Linq.Enumerable).Assembly) {
registrationDelegateType = Expression.Constant (lambda.Type, typeof (Type));
}
else {
string delegateTypeName;
if (lambda.Type.Assembly.IsDynamic) {
var typeNameBuilder = new StringBuilder ("__<$>_jni_marshal_");
var parameterTypes = new List<Type> ();
foreach (var p in lambda.Parameters) {
parameterTypes.Add (p.Type);
}

MarshalMemberBuilder.AddMarshalerTypeNameSuffix (typeNameBuilder, lambda.ReturnType, parameterTypes);

var typeName = typeNameBuilder.ToString ();
var existingType = dm.GetType (typeName);
if (existingType == null) {
var dtb = dm.DefineType (typeName,
System.Reflection.TypeAttributes.AnsiClass |
System.Reflection.TypeAttributes.Sealed,
typeof (MulticastDelegate));

var dc = dtb.DefineConstructor (
System.Reflection.MethodAttributes.Public |
System.Reflection.MethodAttributes.HideBySig |
System.Reflection.MethodAttributes.RTSpecialName |
System.Reflection.MethodAttributes.SpecialName,
CallingConventions.Standard, new Type [] { typeof (object), typeof (IntPtr) });
dc.SetImplementationFlags (System.Reflection.MethodImplAttributes.Runtime | System.Reflection.MethodImplAttributes.Managed);

CreateDelegateRuntimeManagedMethod (dtb, "Invoke", typeof (bool), parameterTypes.ToArray ());

parameterTypes.Add (typeof (AsyncCallback));
parameterTypes.Add (typeof (object));

CreateDelegateRuntimeManagedMethod (dtb, "BeginInvoke", typeof (IAsyncResult), parameterTypes.ToArray ());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to declare BeginInvoke() and EndInvoke() if these are generated types, with type names that are "inaccessible", and jnimarshalmethod-gen isn't emitting references to these methods?

CreateDelegateRuntimeManagedMethod (dtb, "EndInvoke", typeof (bool), new Type [] { typeof (IAsyncResult) });

existingType = dtb.CreateType ();
createdDelegateList.Add (existingType);
}

delegateTypeName = existingType.FullName;
} else
delegateTypeName = lambda.Type.FullName;

Func<string, bool, Type> getType = Type.GetType;
registrationDelegateType = Expression.Call (getType.GetMethodInfo (),
Expression.Constant (lambda.Type.FullName, typeof (string)),
Expression.Constant (delegateTypeName, typeof (string)),
Expression.Constant (true, typeof (bool)));
registrationDelegateType = Expression.Convert (registrationDelegateType, typeof (Type));
}
Expand Down
38 changes: 37 additions & 1 deletion tools/jnimarshalmethod-gen/TypeMover.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,19 @@ public class TypeMover
AssemblyDefinition Source { get; }
AssemblyDefinition Destination { get; }
string DestinationPath { get; }
List<Type> DelegateTypes { get; }
Dictionary<string, System.Reflection.Emit.TypeBuilder> Types { get; }
DirectoryAssemblyResolver Resolver { get; }

MethodReference consoleWriteLine;
TypeDefinitionCache cache;

public TypeMover (AssemblyDefinition source, AssemblyDefinition destination, string destinationPath, Dictionary<string, System.Reflection.Emit.TypeBuilder> types, DirectoryAssemblyResolver resolver, TypeDefinitionCache cache)
public TypeMover (AssemblyDefinition source, AssemblyDefinition destination, string destinationPath, List<Type> delegateTypes, Dictionary<string, System.Reflection.Emit.TypeBuilder> types, DirectoryAssemblyResolver resolver, TypeDefinitionCache cache)
{
Source = source;
Destination = destination;
DestinationPath = destinationPath;
DelegateTypes = delegateTypes;
Types = types;
Resolver = resolver;
this.cache = cache;
Expand All @@ -45,6 +47,11 @@ public void Move ()
typeMap.Clear ();
resolvedTypeMap.Clear ();

foreach (var type in DelegateTypes) {
MoveDelegate (type);
movedTypesCount++;
}

foreach (var type in Types.Values) {
Move (type);
movedTypesCount++;
Expand All @@ -71,6 +78,34 @@ bool TypeIsEmptyOrHasOnlyDefaultConstructor (TypeDefinition type)
return !type.HasMethods || (type.Methods.Count == 1 && type.Methods [0].IsConstructor);
}

void MoveDelegate (Type type)
{
var typeSrc = Source.MainModule.GetType (type.GetCecilName ());
var typeDst = new TypeDefinition ("", typeSrc.Name, typeSrc.Attributes);
var module = Destination.MainModule;

if (App.Verbose) {
Console.Write ($"Moving delegate type ");
App.ColorWrite ($"{typeSrc.FullName},{typeSrc.Module.FileName}", ConsoleColor.Yellow);
Console.Write (" to ");
App.ColorWriteLine ($"{Destination.MainModule.FileName}", ConsoleColor.Yellow);
}

typeDst.BaseType = GetUpdatedType (typeSrc.BaseType, module);

foreach (var m in typeSrc.Methods) {
var md = new MethodDefinition (m.Name, m.Attributes, GetUpdatedType (m.ReturnType, module));
md.ImplAttributes = m.ImplAttributes;

foreach (var p in m.Parameters)
md.Parameters.Add (new ParameterDefinition (p.Name, p.Attributes, GetUpdatedType (p.ParameterType, module)));

typeDst.Methods.Add (md);
}

Destination.MainModule.Types.Add (typeDst);
}

void Move (Type type)
{
var typeSrc = Source.MainModule.GetType (type.GetCecilName ());
Expand Down Expand Up @@ -343,6 +378,7 @@ MethodReference GetActionConstructor (TypeReference type, ModuleDefinition modul
var mr = GetUpdatedMethod (m, module);
if (type is GenericInstanceType)
mr.DeclaringType = type;

return mr;
}
}
Expand Down