[runtime] Support automatic GCHandle -> MonoObject* conversion for parameters and return values when calling managed delegates.

This commit is contained in:
Rolf Bjarne Kvinge 2020-05-04 11:08:25 +02:00
Родитель 95a9fe33fd
Коммит 1c33bff5b3
5 изменённых файлов: 128 добавлений и 41 удалений

Просмотреть файл

@ -26,7 +26,7 @@ namespace ObjCRuntime {
public unsafe partial class Runtime {
<# foreach (var d in delegates) { #>
internal delegate <#= d.MReturnType #> <#= d.SimpleEntryPoint #>_delegate (<#= d.MArgumentSignature #>);
internal delegate <#= d.ReturnType.MType #> <#= d.SimpleEntryPoint #>_delegate (<#= d.MArgumentSignature #>);
<# } #>
internal struct Delegates {
@ -37,7 +37,7 @@ namespace ObjCRuntime {
<# foreach (var d in delegates) { #>
[MonoPInvokeCallback (typeof (<#= d.SimpleEntryPoint #>_delegate))]
static <#= d.MReturnType #> <#= d.SimpleEntryPoint #> (<#= d.MArgumentSignature #>)
static <#= d.ReturnType.MType #> <#= d.SimpleEntryPoint #> (<#= d.MArgumentSignature #>)
<# if (d.ExceptionHandling) { #>
{
exception_gchandle = 0;
@ -45,14 +45,14 @@ namespace ObjCRuntime {
<# if (string.IsNullOrEmpty (d.WrappedManagedFunction)) { #>
throw new NotImplementedException ();
<# } else { #>
<# if (d.MReturnType != "void") { #>return <# } #><#=d.WrappedManagedFunction#> (<#=d.MArgumentNames#>);
<# if (d.ReturnType.MType != "void") { #>return <# } #><#=d.WrappedManagedFunction#> (<#=d.MArgumentNames#>);
<# } #>
} catch (Exception ex) {
var handle = GCHandle.Alloc (ex, GCHandleType.Normal);
exception_gchandle = GCHandle.ToIntPtr (handle).ToInt32 ();
<# if (d.SimpleEntryPoint == "get_nsobject_with_type") { #> created = false;
<# } #>
<# if (d.MReturnType != "void") { #> return default (<#= d.MReturnType #>);
<# if (d.ReturnType.MType != "void") { #> return default (<#= d.ReturnType.MType #>);
<# } #>
}
}
@ -61,7 +61,7 @@ namespace ObjCRuntime {
<# if (string.IsNullOrEmpty (d.WrappedManagedFunction)) { #>
throw new NotImplementedException ();
<# } else { #>
<# if (d.MReturnType != "void") { #>return <# } #><#=d.WrappedManagedFunction#> (<#=d.MArgumentNames#>);
<# if (d.ReturnType.MType != "void") { #>return <# } #><#=d.WrappedManagedFunction#> (<#=d.MArgumentNames#>);
<# } #>
}
<# } #>

Просмотреть файл

@ -22,8 +22,8 @@ extern "C" {
if (!d.OnlyDynamicUsage)
continue;
#>
<#= d.CReturnType #>
<#= d.EntryPoint #> (<#= d.CArgumentSignature #>);
<#= d.ReturnType.ExposedCType #>
<#= d.EntryPoint #> (<#= d.CArgumentSignatureExposed #>);
<# } #>

Просмотреть файл

@ -16,7 +16,7 @@
#include "delegates.h"
<# foreach (var d in delegates) { #>
typedef <#= d.CReturnType #><#= d.AlignCReturnType #> (*func_<#= d.EntryPoint #>)<#= d.AlignEntryPoint #> (<#= d.CArgumentSignature #>);
typedef <#= d.ReturnType.InterfaceCType #><#= d.AlignCReturnType #> (*func_<#= d.EntryPoint #>)<#= d.AlignEntryPoint #> (<#= d.CArgumentSignature #>);
<# } #>
struct Delegates {
@ -37,21 +37,5 @@ create_linked_away_exception (const char *func)
}
<# foreach (var d in delegates) { #>
<#= d.CReturnType #>
<#= d.EntryPoint #> (<#= d.CArgumentSignature #>)
{
<#if (d.ExceptionHandling && d.OnlyDynamicUsage) {#> if (delegates.<#= d.EntryPoint.Substring ("xamarin_".Length) #> == NULL) {
*exception_gchandle = create_linked_away_exception ("<#= d.EntryPoint.Substring ("xamarin_".Length) #>");
return<# if (d.CReturnType != "void") { #> (<#= d.CReturnType #>) 0<# } #>;
}
<#} else {#>#if DEBUG
if (delegates.<#= d.EntryPoint.Substring ("xamarin_".Length) #> == NULL) {
NSLog (@PRODUCT ": The managed function <#= d.EntryPoint.Substring ("xamarin_".Length) #> could not be loaded.");
xamarin_assertion_message ("The managed function <#= d.EntryPoint.Substring ("xamarin_".Length) #> could not be loaded.");
}
#endif
<#}#>
<# if (d.CReturnType != "void") { #>return <# } #>delegates.<#= d.EntryPoint.Substring ("xamarin_".Length) #> (<#= d.CArgumentNames #>);
}
<#= d.Function #>
<# } #>

Просмотреть файл

@ -279,9 +279,24 @@
#><#+
class Arg
{
public string CType;
public string MangledCType;
public string ExposedCType; // the CType exposed to native code
public string InterfaceCType; // the CType as interfaced with managed code
public string MType;
public string Name;
public bool IsGCHandleConversion => MangledCType.Contains ("->");
public Arg (string mangledCType, string mType, string name)
{
MangledCType = mangledCType;
var is_conv = IsGCHandleConversion;
var interfaced = is_conv ? "GCHandle" : mangledCType;
var exposed = is_conv ? XDelegate.GetConvertedGCHandleType (mangledCType) : mangledCType;
ExposedCType = exposed;
InterfaceCType = interfaced;
MType = mType;
Name = name;
}
public bool IsVoid => ExposedCType == "void";
}
class XDelegates : List<XDelegate>
@ -290,7 +305,7 @@
{
foreach (var x in this) {
MaxEntryPointLength = Math.Max (MaxEntryPointLength, x.EntryPoint.Length);
MaxCReturnTypeLength = Math.Max (MaxCReturnTypeLength, x.CReturnType.Length);
MaxCReturnTypeLength = Math.Max (MaxCReturnTypeLength, x.ReturnType.ExposedCType.Length);
x.Delegates = this;
}
}
@ -301,8 +316,7 @@
class XDelegate
{
public string CReturnType;
public string MReturnType;
public Arg ReturnType;
public string EntryPoint;
public List<Arg> Arguments;
public string WrappedManagedFunction;
@ -310,12 +324,22 @@
// Detemines whether the function is only used by the dynamic registrar (in which case we might be able to link the function away if the static registrar is being used)
public bool OnlyDynamicUsage;
public string DelegateName {
get {
return EntryPoint.Substring ("xamarin_".Length);
}
}
public static string GetConvertedGCHandleType (string type)
{
return type.Substring ("GCHandle->".Length);
}
public XDelegates Delegates;
public XDelegate (string cReturnType, string mReturnType, string entryPoint, params string [] arguments)
{
CReturnType = cReturnType;
MReturnType = mReturnType;
ReturnType = new Arg (cReturnType, mReturnType, string.Empty);
EntryPoint = entryPoint;
if (arguments == null || arguments.Length == 0)
@ -329,11 +353,86 @@
Arguments = new List<Arg> ();
for (var i = 0; i < arguments.Length; i += 3)
Arguments.Add (new Arg {
CType = arguments [i],
MType = arguments [i + 1],
Name = arguments [i + 2]
});
Arguments.Add (new Arg (arguments [i], arguments [i + 1], arguments [i + 2]));
}
public string Function {
get {
var sb = new StringBuilder ();
// This function generates the helper function that actually calls the managed delegate
// It supports converting input arguments of MonoObject* (and equivalent types) to GCHandle,
// and converting GCHandle return values to MonoObject* (or equivalent types). In both cases
// the GCHandle will be freed before the generated function returns.
sb.AppendLine (ReturnType.ExposedCType);
sb.Append (EntryPoint);
sb.Append (" (");
sb.Append (CArgumentSignatureExposed);
sb.AppendLine (")");
sb.AppendLine ("{");
if (ExceptionHandling && OnlyDynamicUsage) {
sb.AppendLine ($"\tif (delegates.{DelegateName} == NULL) {{");
sb.AppendLine ($"\t\t*exception_gchandle = create_linked_away_exception (\"{DelegateName}\");");
sb.AppendLine ($"\t\treturn{(ReturnType.IsVoid ? string.Empty : $" ({ReturnType.ExposedCType}) 0")};");
sb.AppendLine ($"\t}}");
} else {
sb.AppendLine ($"#if DEBUG");
sb.AppendLine ($"\tif (delegates.{DelegateName} == NULL) {{");
sb.AppendLine ($"\t\tNSLog (@PRODUCT \": The managed function {DelegateName} could not be loaded.\");");
sb.AppendLine ($"\t\txamarin_assertion_message (\"The managed function {DelegateName} could not be loaded.\");");
sb.AppendLine ($"\t}}");
sb.AppendLine ($"#endif");
}
var invoke_args = new StringBuilder ();
var post_invoke = new StringBuilder ();
for (var i = 0; i < Arguments.Count; i++) {
var arg = Arguments [i];
if (i > 0)
invoke_args.Append (", ");
if (arg.IsGCHandleConversion) {
// Convert to GCHandle before calling the managed functino
var argname = $"{arg.Name}__handle";
sb.AppendLine ($"\tGCHandle {argname} = xamarin_gchandle_new ((MonoObject *) {arg.Name}, false);");
invoke_args.Append (argname);
// and free the GCHandle after returning from the managed function
post_invoke.AppendLine ($"\txamarin_gchandle_free ({argname});");
} else {
invoke_args.Append (arg.Name);
}
}
sb.Append ("\t");
if (!ReturnType.IsVoid) {
sb.Append ($"{ReturnType.ExposedCType} rv = ");
// Unwrap the GCHandle and free it
if (ReturnType.IsGCHandleConversion)
sb.Append ("xamarin_gchandle_unwrap (");
}
sb.Append ("delegates.");
sb.Append (EntryPoint.Substring ("xamarin_".Length));
sb.Append (" (");
sb.Append (invoke_args);
if (ExceptionHandling)
sb.Append (", exception_gchandle");
if (ReturnType.IsGCHandleConversion)
sb.Append (")");
sb.AppendLine (");");
sb.Append (post_invoke);
if (!ReturnType.IsVoid)
sb.AppendLine ("\treturn rv;");
sb.AppendLine ("}");
return sb.ToString ();
}
}
public string SimpleEntryPoint {
@ -350,11 +449,11 @@
public string AlignCReturnType {
get {
return new string (' ', Delegates.MaxCReturnTypeLength - CReturnType.Length);
return new string (' ', Delegates.MaxCReturnTypeLength - ReturnType.ExposedCType.Length);
}
}
string CFormatArgs (string empty, bool nameOnly)
string CFormatArgs (string empty, bool nameOnly, bool exposed = false)
{
if (Arguments == null || Arguments.Count == 0)
return empty;
@ -363,7 +462,7 @@
foreach (var arg in Arguments) {
if (!nameOnly) {
builder.Append (arg.CType);
builder.Append (exposed ? arg.ExposedCType : arg.InterfaceCType);
builder.Append (' ');
}
@ -422,6 +521,10 @@
get { return CFormatArgs ("void", nameOnly: false); }
}
public string CArgumentSignatureExposed {
get { return CFormatArgs ("void", nameOnly: false, exposed: true); }
}
public string CArgumentNames {
get { return CFormatArgs (String.Empty, nameOnly: true); }
}

Просмотреть файл

@ -25,8 +25,8 @@ extern "C" {
if (d.OnlyDynamicUsage)
continue;
#>
<#= d.CReturnType #>
<#= d.EntryPoint #> (<#= d.CArgumentSignature #>);
<#= d.ReturnType.ExposedCType #>
<#= d.EntryPoint #> (<#= d.CArgumentSignatureExposed #>);
<# } #>