Compare commits

...

5 Commits

Author SHA1 Message Date
Lauro Moura 23cf1162f2 WIP - Start changing the MarshalType for non-owned string returned 2019-12-11 00:20:44 -03:00
Lauro Moura 590cd74906 csharp: Add context information about marshaling direction
This will be used to differentiate annotations between calls from C# to
C functions (DllImport-like) and delegates C# gives to C to be called
back (virtual methods and callbacks).

By default, the root context assumes the managed code calling the native
code.
2019-12-11 00:20:44 -03:00
Lauro Moura 9472ec86ec csharp: Disable annotations for out non-own strings
They will be handled manually to avoid leaks, as the control would
return to C before the custom marshaller could release them.
2019-12-11 00:20:44 -03:00
Lauro Moura c1293edbd2 csharp: Update string marshalers
Mostly done, needs work on strings being returned without ownership from
C# to C.
2019-12-11 00:20:44 -03:00
Lauro Moura 52da69510f csharp: Move string marshalers into own files
Preparing for future refactor
2019-12-11 00:20:44 -03:00
12 changed files with 278 additions and 101 deletions

View File

@ -54,6 +54,8 @@ struct native_function_definition_generator
auto const& indent = current_indentation(context);
auto native_to_managed_context = marshall_direction::from_native_to_managed(context);
// Delegate for the C# method we will export to EO as a method implementation.
if(!as_generator
(
@ -69,7 +71,7 @@ struct native_function_definition_generator
(marshall_annotation << " " << marshall_parameter)
) % ", ")
<< ");\n\n")
.generate(sink, std::make_tuple(f.return_type, f.return_type, f.c_name, f.parameters), context))
.generate(sink, std::make_tuple(f.return_type, f.return_type, f.c_name, f.parameters), native_to_managed_context))
return false;
// API delegate is the wrapper for the Eo methods exported from C that we will use from C#.

View File

@ -50,6 +50,38 @@ struct class_context
{}
};
struct marshall_direction
{
enum direction {
managed_to_native, // Used for DllImport'd methods
native_to_managed, // Used for delegates of functions passed to C to be called back
};
direction current_dir;
constexpr marshall_direction(direction direction)
: current_dir(direction)
{}
template<typename Context>
static inline constexpr Context from_native_to_managed(Context const& context)
{
return efl::eolian::grammar::context_replace_tag(marshall_direction(marshall_direction::native_to_managed), context);
}
template<typename Context>
static inline constexpr Context from_managed_to_native(Context const& context)
{
return efl::eolian::grammar::context_replace_tag(marshall_direction(marshall_direction::managed_to_native), context);
}
template<typename Context>
static inline constexpr marshall_direction::direction current_direction(Context const& context)
{
return efl::eolian::grammar::context_find_tag<marshall_direction>(context).current_dir;
}
};
struct indentation_context
{
constexpr indentation_context(indentation_context const& other) = default;

View File

@ -51,23 +51,22 @@ struct marshall_annotation_visitor_generate
eina::optional<bool> has_own;
std::function<std::string()> function;
};
// These two tables are currently the same but will hold different marshallers
// for @in and @out/return semantics in a future commit.
match const parameter_match_table[] =
match const input_match_table[] =
{
// signed primitives
{"bool", nullptr, [] { return "MarshalAs(UnmanagedType.U1)"; }},
{"string", true, [] {
return "MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(Efl.Eo.StringPassOwnershipMarshaler))";
return "MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(Efl.Eo.StringInPassOwnershipMarshaler))";
}},
{"string", false, [] {
return "MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(Efl.Eo.StringKeepOwnershipMarshaler))";
return "MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(Efl.Eo.StringInKeepOwnershipMarshaler))";
}},
{"mstring", true, [] {
return "MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(Efl.Eo.StringPassOwnershipMarshaler))";
return "MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(Efl.Eo.StringInPassOwnershipMarshaler))";
}},
{"mstring", false, [] {
return "MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(Efl.Eo.StringKeepOwnershipMarshaler))";
return "MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(Efl.Eo.StringInKeepOwnershipMarshaler))";
}},
{"stringshare", true, [] {
return "MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(Efl.Eo.StringsharePassOwnershipMarshaler))";
@ -96,16 +95,20 @@ struct marshall_annotation_visitor_generate
// signed primitives
{"bool", nullptr, [] { return "MarshalAs(UnmanagedType.U1)"; }},
{"string", true, [] {
return "MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(Efl.Eo.StringPassOwnershipMarshaler))";
return "MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(Efl.Eo.StringOutPassOwnershipMarshaler))";
}},
{"string", false, [] {
return "MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(Efl.Eo.StringKeepOwnershipMarshaler))";
// Non-owned returned strings are marshalled manually due to lifetime issues
return "";
// return "MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(Efl.Eo.StringOutKeepOwnershipMarshaler))";
}},
{"mstring", true, [] {
return "MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(Efl.Eo.StringPassOwnershipMarshaler))";
return "MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(Efl.Eo.StringOutPassOwnershipMarshaler))";
}},
{"mstring", false, [] {
return "MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(Efl.Eo.StringKeepOwnershipMarshaler))";
// Non-owned returned strings are marshalled manually due to lifetime issues
return "";
// return "MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(Efl.Eo.StringOutKeepOwnershipMarshaler))";
}},
{"stringshare", true, [] {
return "MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(Efl.Eo.StringsharePassOwnershipMarshaler))";
@ -139,11 +142,14 @@ struct marshall_annotation_visitor_generate
auto acceptCb = [this] (std::string const& marshalTag)
{
if (marshalTag.empty())
return true;
std::string prefix = is_return ? "return: " : "";
return as_generator("[" << prefix << marshalTag << "]").generate(sink, nullptr, *context);
};
const auto& match_table = is_return ? return_match_table : parameter_match_table;
const auto& match_table = (is_return || is_out) ? return_match_table : input_match_table;
if(eina::optional<bool> b = type_match::get_match(match_table, predicate, acceptCb))
{

View File

@ -50,6 +50,8 @@ struct marshall_type_visitor_generate
{
using attributes::regular_type_def;
auto is_native_dir = marshall_direction::current_direction(*context) == marshall_direction::native_to_managed;
struct match
{
eina::optional<std::string> name;
@ -69,6 +71,12 @@ struct marshall_type_visitor_generate
{
regular_type_def r = regular;
r.base_qualifier.qualifier ^= qualifier_info::is_ref;
// Non-owned strings returned will be manually handled
// due to lifetime issues.
if (is_native_dir && (is_out || is_return))
return replace_base_type(r, "System.IntPtr");
return replace_base_type(r, "System.String");
}}
, {"mstring", true, [&]
@ -81,6 +89,12 @@ struct marshall_type_visitor_generate
{
regular_type_def r = regular;
r.base_qualifier.qualifier ^= qualifier_info::is_ref;
// Non-owned strings returned will be manually handled
// due to lifetime issues.
if (is_native_dir && (is_out || is_return))
return replace_base_type(r, "System.IntPtr");
return replace_base_type(r, "System.String");
}}
, {"stringshare", true, [&]

View File

@ -834,6 +834,7 @@ struct convert_out_variable_generator
} const convert_out_variable {};
// Generates intermediate @out variables for native method wrappers (wrappers that are called from C)
struct native_convert_out_variable_generator
{
template <typename OutputIterator, typename Context>
@ -850,13 +851,19 @@ struct native_convert_out_variable_generator
).generate(sink, std::make_tuple(param, out_variable_name(param.param_name), param), context);
}
else if (helpers::need_struct_conversion(regular)
|| param_is_acceptable(param, "const char *", !WANT_OWN, WANT_OUT)
|| param_is_acceptable(param, "Eina_Stringshare *", !WANT_OWN, WANT_OUT))
{
return as_generator(
type << " " << string << " = default(" << type << ");\n"
).generate(sink, std::make_tuple(param, out_variable_name(param.param_name), param), context);
}
else if (param_is_acceptable(param, "const char *", !WANT_OWN, WANT_OUT))
{
// FIXME Replace with IntPtr interemediate variable
return as_generator(
type << " " << string << " = default(" << type << ");\n"
).generate(sink, std::make_tuple(param, out_variable_name(param.param_name), param), context);
}
else if (param_is_acceptable(param, "Eina_Binbuf *", WANT_OWN, WANT_OUT)
|| param_is_acceptable(param, "Eina_Binbuf *", !WANT_OWN, WANT_OUT)
|| param_is_acceptable(param, "const Eina_Binbuf *", WANT_OWN, WANT_OUT)

View File

@ -181,6 +181,7 @@ run(options_type const& opts)
using efl::eolian::grammar::context_add_tag;
auto context = context_add_tag(eolian_mono::indentation_context{0},
context_add_tag(eolian_mono::marshall_direction{eolian_mono::marshall_direction::managed_to_native},
context_add_tag(eolian_mono::eolian_state_context{opts.state},
context_add_tag(eolian_mono::options_context{opts.want_beta,
opts.examples_dir},
@ -188,7 +189,7 @@ run(options_type const& opts)
opts.v_major,
opts.v_minor,
opts.references_map},
efl::eolian::grammar::context_null()))));
efl::eolian::grammar::context_null())))));
EINA_ITERATOR_FOREACH(aliases, tp)
{

View File

@ -43,7 +43,7 @@ internal static class Environment
internal static partial class NativeCustomExportFunctions
{
[DllImport(efl.Libs.CustomExports, CharSet=CharSet.Ansi)]
[return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(Efl.Eo.StringKeepOwnershipMarshaler))]
[return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(Efl.Eo.StringOutKeepOwnershipMarshaler))]
public static extern string efl_mono_native_getenv(string name);
[DllImport(efl.Libs.CustomExports, CharSet=CharSet.Ansi)]

View File

@ -51,13 +51,13 @@ static internal class StrbufNativeMethods
[DllImport(efl.Libs.Eina, CharSet=CharSet.Ansi)]
[return:
MarshalAs(UnmanagedType.CustomMarshaler,
MarshalTypeRef=typeof(Efl.Eo.StringPassOwnershipMarshaler))]
MarshalTypeRef=typeof(Efl.Eo.StringOutPassOwnershipMarshaler))]
internal static extern string eina_strbuf_string_steal(IntPtr buf);
[DllImport(efl.Libs.Eina, CharSet=CharSet.Ansi)]
[return:
MarshalAs(UnmanagedType.CustomMarshaler,
MarshalTypeRef=typeof(Efl.Eo.StringKeepOwnershipMarshaler))]
MarshalTypeRef=typeof(Efl.Eo.StringOutKeepOwnershipMarshaler))]
internal static extern string eina_strbuf_string_get(IntPtr buf);
[DllImport(efl.Libs.Eina)]

View File

@ -194,7 +194,7 @@ static internal class UnsafeNativeMethods
[DllImport(efl.Libs.Eina, CharSet=CharSet.Ansi)]
[return:
MarshalAs(UnmanagedType.CustomMarshaler,
MarshalTypeRef=typeof(Efl.Eo.StringPassOwnershipMarshaler))]
MarshalTypeRef=typeof(Efl.Eo.StringOutPassOwnershipMarshaler))]
internal static extern string eina_value_to_string(IntPtr handle); // We take ownership of the returned string.
[DllImport(efl.Libs.CustomExports)]

View File

@ -0,0 +1,194 @@
using System;
using System.Runtime.InteropServices;
namespace Efl
{
namespace Eo
{
/// <summary>
/// Marshaler for <c>@in</c> parameters that have their ownership transfered.
/// </summary>
class StringInPassOwnershipMarshaler : ICustomMarshaler
{
// Called when C calls a C# method passing data to it
public object MarshalNativeToManaged(IntPtr pNativeData)
{
var ret = Eina.StringConversion.NativeUtf8ToManagedString(pNativeData);
Eina.MemoryNative.Free(pNativeData);
return ret;
}
// Called when C# calls a C method passing data to it
public IntPtr MarshalManagedToNative(object managedObj)
{
return Eina.MemoryNative.StrDup((string)managedObj);
}
// Called when the C call returns, cleaning the result from MarshalManagedToNative
public void CleanUpNativeData(IntPtr pNativeData)
{
}
// Called when the C# call returns, cleaning the result from MarshalNativeToManaged
public void CleanUpManagedData(object managedObj)
{
}
public int GetNativeDataSize()
{
return -1;
}
internal static ICustomMarshaler GetInstance(string cookie)
{
if (marshaler == null)
{
marshaler = new StringInPassOwnershipMarshaler();
}
return marshaler;
}
static private ICustomMarshaler marshaler;
}
/// <summary>
/// Marshaler for <c>@in</c> parameters that do not change their ownership.
/// </summary>
class StringInKeepOwnershipMarshaler: ICustomMarshaler
{
// Called when C calls a C# method passing data to it
public object MarshalNativeToManaged(IntPtr pNativeData)
{
return Eina.StringConversion.NativeUtf8ToManagedString(pNativeData);
}
// Called when C# calls a C method passing data to it
public IntPtr MarshalManagedToNative(object managedObj)
{
return Eina.StringConversion.ManagedStringToNativeUtf8Alloc((string)managedObj);
}
// Called when the C call returns, cleaning the result from MarshalManagedToNative
public void CleanUpNativeData(IntPtr pNativeData)
{
Eina.MemoryNative.Free(pNativeData);
}
// Called when the C# call returns, cleaning the result from MarshalNativeToManaged
public void CleanUpManagedData(object managedObj)
{
// GC will should take care of it.
}
public int GetNativeDataSize()
{
return -1;
}
internal static ICustomMarshaler GetInstance(string cookie)
{
if (marshaler == null)
{
marshaler = new StringInKeepOwnershipMarshaler();
}
return marshaler;
}
static private ICustomMarshaler marshaler;
}
class StringOutPassOwnershipMarshaler : ICustomMarshaler
{
// Called when C# calls a C method and receives data from it
public object MarshalNativeToManaged(IntPtr pNativeData)
{
var ret = Eina.StringConversion.NativeUtf8ToManagedString(pNativeData);
// C gave us ownership. Let's free.
Eina.MemoryNative.Free(pNativeData);
return ret;
}
// Called when C calls a C# method and receives data from it
public IntPtr MarshalManagedToNative(object managedObj)
{
// As we're passing up the ownership, no need to free it.
return Eina.MemoryNative.StrDup((string)managedObj);
}
// Called when the C call returns, cleaning the result it gave to C#
public void CleanUpNativeData(IntPtr pNativeData)
{
}
// Called when the C# call returns, cleaning the result it gave to C.
public void CleanUpManagedData(object managedObj)
{
}
public int GetNativeDataSize()
{
return -1;
}
internal static ICustomMarshaler GetInstance(string cookie)
{
if (marshaler == null)
{
marshaler = new StringOutPassOwnershipMarshaler();
}
return marshaler;
}
static private ICustomMarshaler marshaler;
}
class StringOutKeepOwnershipMarshaler: ICustomMarshaler
{
// Called when C# calls a C method and receives data from it
public object MarshalNativeToManaged(IntPtr pNativeData)
{
return Eina.StringConversion.NativeUtf8ToManagedString(pNativeData);
}
// Called when C calls a C# method and receives data from it
public IntPtr MarshalManagedToNative(object managedObj)
{
// FIXME This will be the tricky one, as it can leak.
return Eina.StringConversion.ManagedStringToNativeUtf8Alloc((string)managedObj);
}
// Called when the C call returns, cleaning the result it gave to C#
public void CleanUpNativeData(IntPtr pNativeData)
{
// No need to free. The Native side will keep the ownership.
}
// Called when the C call returns, cleaning the result it gave to C#
public void CleanUpManagedData(object managedObj)
{
}
public int GetNativeDataSize()
{
return -1;
}
internal static ICustomMarshaler GetInstance(string cookie)
{
if (marshaler == null)
{
marshaler = new StringOutKeepOwnershipMarshaler();
}
return marshaler;
}
static private ICustomMarshaler marshaler;
}
}
}

View File

@ -1242,86 +1242,6 @@ class MarshalEflClass : ICustomMarshaler
}
}
class StringPassOwnershipMarshaler : ICustomMarshaler
{
public object MarshalNativeToManaged(IntPtr pNativeData)
{
var ret = Eina.StringConversion.NativeUtf8ToManagedString(pNativeData);
Eina.MemoryNative.Free(pNativeData);
return ret;
}
public IntPtr MarshalManagedToNative(object managedObj)
{
return Eina.MemoryNative.StrDup((string)managedObj);
}
public void CleanUpNativeData(IntPtr pNativeData)
{
// No need to cleanup. C will take care of it.
}
public void CleanUpManagedData(object managedObj)
{
}
public int GetNativeDataSize()
{
return -1;
}
internal static ICustomMarshaler GetInstance(string cookie)
{
if (marshaler == null)
{
marshaler = new StringPassOwnershipMarshaler();
}
return marshaler;
}
static private StringPassOwnershipMarshaler marshaler;
}
class StringKeepOwnershipMarshaler: ICustomMarshaler
{
public object MarshalNativeToManaged(IntPtr pNativeData)
{
return Eina.StringConversion.NativeUtf8ToManagedString(pNativeData);
}
public IntPtr MarshalManagedToNative(object managedObj)
{
return Eina.StringConversion.ManagedStringToNativeUtf8Alloc((string)managedObj);
}
public void CleanUpNativeData(IntPtr pNativeData)
{
// No need to free. The Native side will keep the ownership.
}
public void CleanUpManagedData(object managedObj)
{
}
public int GetNativeDataSize()
{
return -1;
}
internal static ICustomMarshaler GetInstance(string cookie)
{
if (marshaler == null)
{
marshaler = new StringKeepOwnershipMarshaler();
}
return marshaler;
}
static private StringKeepOwnershipMarshaler marshaler;
}
class StringsharePassOwnershipMarshaler : ICustomMarshaler
{
public object MarshalNativeToManaged(IntPtr pNativeData)

View File

@ -1,10 +1,11 @@
mono_files += files(
'iwrapper.cs',
'workaround.cs',
'EoWrapper.cs',
'FunctionWrapper.cs',
'NativeModule.cs',
'EoWrapper.cs',
'WrapperSupervisor.cs'
'StringMarshalers.cs',
'WrapperSupervisor.cs',
)
if host_machine.system() == 'windows'