Avoid non-blittable types in native callback methods
grendello opened this issue · comments
While working on implementation of marshal methods (a way to replace the current native JNI method registration with
generated code) which take advantage of the [UnmanagedCallersOnly]
attribute, I came across a problem that some of
our registered methods either return a bool
or take a parameter which is a bool
. The problem here is that bool
is a non-blittable type, while all [UnmanagedCallersOnly]
methods must use only blittable types (as they are
called directly by the native code, bypassing marshaling).
Marshal methods convert the code output by the generator from:
static Delegate? cb_setChildrenDrawingOrderEnabled_Z;
static Delegate GetSetChildrenDrawingOrderEnabled_ZHandler ()
{
if (cb_setChildrenDrawingOrderEnabled_Z == null)
cb_setChildrenDrawingOrderEnabled_Z = JNINativeWrapper.CreateDelegate ((_JniMarshal_PPZ_V) n_SetChildrenDrawingOrderEnabled_Z);
return cb_setChildrenDrawingOrderEnabled_Z;
}
static void n_SetChildrenDrawingOrderEnabled_Z (IntPtr jnienv, IntPtr native__this, bool enabled)
{
var __this = global::Java.Lang.Object.GetObject<Android.Views.ViewGroup> (jnienv, native__this, JniHandleOwnership.DoNotTransfer)!;
__this.ChildrenDrawingOrderEnabled = enabled;
}
protected virtual unsafe bool ChildrenDrawingOrderEnabled {
[Register ("setChildrenDrawingOrderEnabled", "(Z)V", "GetSetChildrenDrawingOrderEnabled_ZHandler")]
set {
// ...
}
}
to
[UnmanagedCallersOnly]
static void n_SetChildrenDrawingOrderEnabled_Z (IntPtr jnienv, IntPtr native__this, bool enabled)
{
var __this = global::Java.Lang.Object.GetObject<Android.Views.ViewGroup> (jnienv, native__this, JniHandleOwnership.DoNotTransfer)!;
__this.ChildrenDrawingOrderEnabled = enabled;
}
protected virtual unsafe bool ChildrenDrawingOrderEnabled {
[Register ("setChildrenDrawingOrderEnabled", "(Z)V", "GetSetChildrenDrawingOrderEnabled_ZHandler")]
set {
// ...
}
}
And at run time, a pointer to n_SetChildrenDrawingOrderEnabled_Z
is obtained using the Mono embedding API mono_method_get_unmanaged_callers_only_ftnptr
.
However, when a non-blittable type is encountered, the function will return an error:
method Android.Views.ViewGroup:n_SetChildrenDrawingOrderEnabled_Z(intptr,intptr,bool) with UnmanagedCallersOnlyAttribute has non-blittable parameters or return type assembly:<unknown assembly> type:<unknown ty
pe> member:(null)
The problem is quite common in MAUI apps, since in a simple Hello World app, out of 183 marshal method candidates, 133 have a bool
in their parameter list or as a return value.
I have implemented a workaround for this issue (which we will keep in the future, to deal with 3rd party libraries that weren't regenerated using the new generator) but the real fix is to make the generator instead output code equivalent to:
static void n_SetChildrenDrawingOrderEnabled_Z (IntPtr jnienv, IntPtr native__this, byte enabled)
{
var __this = global::Java.Lang.Object.GetObject<Android.Views.ViewGroup> (jnienv, native__this, JniHandleOwnership.DoNotTransfer)!;
__this.ChildrenDrawingOrderEnabled = enabled != 0;
}
protected virtual unsafe bool ChildrenDrawingOrderEnabled {
[Register ("setChildrenDrawingOrderEnabled", "(Z)V", "GetSetChildrenDrawingOrderEnabled_ZHandler")]
set {
// ...
}
}
or, for a method which returns a bool
:
static byte n_IsMarginRelative (IntPtr jnienv, IntPtr native__this)
{
var __this = global::Java.Lang.Object.GetObject<Android.Views.ViewGroup.MarginLayoutParams> (jnienv, native__this, JniHandleOwnership.DoNotTransfer)!;
return __this.IsMarginRelative ? 1 : 0;
}
public virtual unsafe bool IsMarginRelative {
[Register ("isMarginRelative", "()Z", "GetIsMarginRelativeHandler")]
get {
// ...
}
}
The reason to choose byte
is that JNI's jboolean
type is defined as an unsigned 8-bit value and the reason to explicitly return 1
or 0
instead of
just casting the managed bool
value is that the boolean type in dotnet always has value of 0
for false
, but can have -1
, 1
or != 0
for true
,
depending on the VM or context and so it's safer to "downcast" that set to the 0
/1
values common in other languages (including Java, C and C++ about
which we care)
In Mono.Android
generated MCW code we currently have 4824
native callback methods either returning bool
or with at least one bool
parameter.
The generator should take into account other System
namespace non-blittable types, not just bool
(within reason - only those that can potentially happen in bindings). The list can be found in this table,
for reference copied below:
Non-blittable type | Description |
---|---|
System.Array | Converts to a C-style array or a SAFEARRAY. |
System.Boolean | Converts to a 1, 2, or 4-byte value with true as 1 or -1. |
System.Char | Converts to a Unicode or ANSI character. |
System.Class | Converts to a class interface. |
System.Object | Converts to a variant or an interface. |
System.Mdarray | Converts to a C-style array or a SAFEARRAY. |
System.String | Converts to a string terminating in a null reference or to a BSTR. |
System.Valuetype | Converts to a structure with a fixed memory layout. |
System.Szarray | Converts to a C-style array or a SAFEARRAY. |