csharp: make inherited C# classes constructible from native C

Summary:
With this commit it is now possible for a class that inherits from a C# binding
class to be instantiated from native C code. It only has to provide a
constructor that receives an `Efl.Eo.EoWrapper.ConstructingHandle` struct,
and which calls the base binding constructor passing it.
For example:

`private Type(ConstructingHandle ch) : base(ch) {}`.

Add some test files to validate the proper behavior of this feature.

Add some small fixes in generation contexts in order to properly
generate base constructors.

Depends on D9070

Test Plan: `meson test` and `make check`

Reviewers: lauromoura, felipealmeida, segfaultxavi, woohyun, YOhoho

Reviewed By: YOhoho

Subscribers: YOhoho, cedric, #reviewers, #committers

Tags: #efl

Differential Revision: https://phab.enlightenment.org/D9071
This commit is contained in:
Vitor Sousa 2019-06-28 10:29:01 -03:00
parent 549c417853
commit b7fa7d48ac
10 changed files with 380 additions and 13 deletions

View File

@ -196,6 +196,17 @@ struct klass
if (!generate_fields(sink, cls, concrete_cxt))
return false;
if (!as_generator
(
scope_tab << "/// <summary>Constructor to be used when objects are expected to be constructed from native code.</summary>\n"
<< scope_tab << "/// <param name=\"ch\">Tag struct storing the native handle of the object being constructed.</param>\n"
<< scope_tab << "private " << concrete_name << "(ConstructingHandle ch) : base(ch)\n"
<< scope_tab << "{\n"
<< scope_tab << "}\n\n"
)
.generate(sink, attributes::unused, concrete_cxt))
return false;
if (!as_generator
(
scope_tab << "[System.Runtime.InteropServices.DllImport(" << context_find_tag<library_context>(concrete_cxt).actual_library_name(cls.filename)
@ -246,7 +257,7 @@ struct klass
).generate(sink, attributes::unused, concrete_cxt))
return false;
if(!generate_native_inherit_class(sink, cls, change_indentation(indent.inc(), context)))
if(!generate_native_inherit_class(sink, cls, change_indentation(indent.inc(), concrete_cxt)))
return true;
if(!as_generator("}\n").generate(sink, attributes::unused, concrete_cxt)) return false;
@ -318,7 +329,7 @@ struct klass
).generate(sink, attributes::unused, inherit_cxt))
return false;
if(!generate_native_inherit_class(sink, cls, change_indentation(indent.inc(), context)))
if(!generate_native_inherit_class(sink, cls, change_indentation(indent.inc(), inherit_cxt)))
return true;
if(!as_generator("}\n").generate(sink, attributes::unused, inherit_cxt)) return false;
@ -357,7 +368,7 @@ struct klass
(
indent << lit("/// <summary>Wrapper for native methods and virtual method delegates.\n")
<< indent << "/// For internal use by generated code only.</summary>\n"
<< indent << "public " << (root ? "" : "new " ) << "class " << native_inherit_name << " " << (root ? " : Efl.Eo.NativeClass" : (": " + base_name)) <<"\n"
<< indent << "public new class " << native_inherit_name << " : " << (root ? "Efl.Eo.EoWrapper.NativeMethods" : base_name) << "\n"
<< indent << "{\n"
).generate(sink, attributes::unused, inative_cxt))
return false;
@ -396,7 +407,7 @@ struct klass
).generate(sink, attributes::unused, inative_cxt))
return false;
if(!root)
if (!root || context_find_tag<class_context>(context).current_wrapper_kind != class_context::concrete)
if(!as_generator(indent << scope_tab << scope_tab << "descs.AddRange(base.GetEoOps(type));\n").generate(sink, attributes::unused, inative_cxt))
return false;
@ -492,6 +503,11 @@ struct klass
<< (*(scope_tab << scope_tab << constructor_invocation << "\n"))
<< scope_tab << scope_tab << "FinishInstantiation();\n"
<< scope_tab << "}\n\n"
<< scope_tab << "/// <summary>Constructor to be used when objects are expected to be constructed from native code.</summary>\n"
<< scope_tab << "/// <param name=\"ch\">Tag struct storing the native handle of the object being constructed.</param>\n"
<< scope_tab << "protected " << inherit_name << "(ConstructingHandle ch) : base(ch)\n"
<< scope_tab << "{\n"
<< scope_tab << "}\n\n"
<< scope_tab << "/// <summary>Initializes a new instance of the <see cref=\"" << inherit_name << "\"/> class.\n"
<< scope_tab << "/// Internal usage: Constructs an instance from a native pointer. This is used when interacting with C code and should not be used directly.</summary>\n"
<< scope_tab << "/// <param name=\"raw\">The native pointer to be wrapped.</param>\n"

View File

@ -2,6 +2,7 @@ using System;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Reflection;
namespace Efl
{
@ -18,6 +19,33 @@ public abstract class EoWrapper : IWrapper, IDisposable
private static Efl.EventCb ownershipUniqueDelegate = new Efl.EventCb(OwnershipUniqueCallback);
private static Efl.EventCb ownershipSharedDelegate = new Efl.EventCb(OwnershipSharedCallback);
/// <summary>Constructor to be used when objects are expected to be constructed from native code.
/// For a class that inherited from an EFL# class to be properly constructed from native code
/// one must create a constructor with this signature and calls this base constructor from it.
/// This constructor will take care of calling base constructors of the native classes and
/// perform additional setup so objects are ready to use.
/// It is advisable to check for the <see cref="NativeHandle"/> property in the top level
/// constructor and signal an error when it has a value of IntPtr.Zero after this
/// constructor completion.
/// Warning: Do not use this constructor directly from a `new` statement.</summary>
/// <param name="ch">Tag struct storing the native handle of the object being constructed.</param>
protected EoWrapper(ConstructingHandle ch)
{
inherited = true;
handle = Efl.Eo.Globals.efl_constructor(Efl.Eo.Globals.efl_super(ch.NativeHandle, Efl.Eo.Globals.efl_class_get(ch.NativeHandle)));
if (handle == IntPtr.Zero)
{
Eina.Log.Warning("Natice constructor returned NULL");
return;
}
AddWrapperSupervisor();
// Make an additional reference to C#
// - Will also call EVENT_OWNERSHIP_SHARED
Efl.Eo.Globals.efl_ref(handle);
}
/// <summary>Initializes a new instance of the <see cref="Object"/> class.
/// Internal usage: Constructs an instance from a native pointer. This is used when interacting with C code and should not be used directly.</summary>
/// <param name="raw">The native pointer to be wrapped.</param>
@ -54,7 +82,17 @@ public abstract class EoWrapper : IWrapper, IDisposable
parent_ptr = parent.NativeHandle;
}
handle = Efl.Eo.Globals._efl_add_internal_start(file, line, actual_klass, parent_ptr, 1, 0);
if (!inherited)
{
handle = Efl.Eo.Globals._efl_add_internal_start(file, line, actual_klass, parent_ptr, 1, 0);
}
else
{
handle = Efl.Eo.Globals._efl_add_internal_start_bindings(file, line, actual_klass, parent_ptr, 1, 0,
Efl.Eo.Globals.efl_mono_avoid_top_level_constructor_callback_addr_get(),
IntPtr.Zero);
}
if (handle == System.IntPtr.Zero)
{
throw new Exception("Instantiation failed");
@ -246,8 +284,78 @@ public abstract class EoWrapper : IWrapper, IDisposable
AddNativeEventHandler("eo", "_EFL_EVENT_OWNERSHIP_SHARED", ownershipSharedDelegate, ownershipSharedDelegate);
Eina.Error.RaiseIfUnhandledException();
}
protected struct ConstructingHandle
{
public ConstructingHandle(IntPtr h)
{
NativeHandle = h;
}
public IntPtr NativeHandle { get; set; }
}
public abstract class NativeMethods : Efl.Eo.NativeClass
{
private static EflConstructorDelegate csharpEflConstructorStaticDelegate = new EflConstructorDelegate(Constructor);
private static Efl.Eo.NativeModule EoModule = new Efl.Eo.NativeModule("eo");
private delegate IntPtr EflConstructorDelegate(IntPtr obj, IntPtr pd);
public override System.Collections.Generic.List<Efl_Op_Description> GetEoOps(Type type)
{
var descs = new System.Collections.Generic.List<Efl_Op_Description>();
descs.Add(new Efl_Op_Description()
{
api_func = Efl.Eo.FunctionInterop.LoadFunctionPointer(EoModule.Module, "efl_constructor"),
func = Marshal.GetFunctionPointerForDelegate(csharpEflConstructorStaticDelegate)
});
return descs;
}
private static IntPtr Constructor(IntPtr obj, IntPtr pd)
{
try
{
var eoKlass = Efl.Eo.Globals.efl_class_get(obj);
var managedType = ClassRegister.GetManagedType(eoKlass);
if (managedType == null)
{
IntPtr nativeName = Efl.Eo.Globals.efl_class_name_get(eoKlass);
var name = Eina.StringConversion.NativeUtf8ToManagedString(nativeName);
Eina.Log.Warning($"Can't get Managed class for object handle 0x{(UInt64)obj:x} with native class [{name}]");
return IntPtr.Zero;
}
var flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
ConstructorInfo constructor = managedType.GetConstructor(flags, null, new Type[1] { typeof(ConstructingHandle) }, null);
if (constructor == null)
{
Eina.Log.Error($"Type {managedType.FullName} lacks a constructor that receives a ConstructingHandle. It can not be constructed from native code.");
return IntPtr.Zero;
}
var eoWrapper = (Efl.Eo.IWrapper) constructor.Invoke(new object[1] { new ConstructingHandle(obj) });
if (eoWrapper == null)
{
Eina.Log.Warning("Constructor was unable to create a new object");
return IntPtr.Zero;
}
return eoWrapper.NativeHandle;
}
catch (Exception e)
{
Eina.Log.Warning($"Inherited constructor error: {e.ToString()}");
Eina.Error.Set(Eina.Error.UNHANDLED_EXCEPTION);
return IntPtr.Zero;
}
}
}
}
} // namespace Global
} // namespace Eo
} // namespace Efl

View File

@ -44,10 +44,6 @@ public class Globals
public static FunctionWrapper<efl_object_shutdown_delegate> efl_object_shutdown_ptr = new FunctionWrapper<efl_object_shutdown_delegate>(efl.Libs.EoModule, "efl_object_shutdown");
public static void efl_object_shutdown() => efl_object_shutdown_ptr.Value.Delegate();
// [DllImport(efl.Libs.Eo)] public static extern void efl_object_shutdown();
public static FunctionWrapper<_efl_add_internal_start_delegate> _efl_add_internal_start_ptr = new FunctionWrapper<_efl_add_internal_start_delegate>(efl.Libs.EoModule, "_efl_add_internal_start");
public delegate IntPtr
_efl_add_internal_start_delegate([MarshalAs(UnmanagedType.LPStr)] String file, int line,
IntPtr klass, IntPtr parent, byte is_ref, byte is_fallback);
[DllImport(efl.Libs.CustomExports)] public static extern IntPtr efl_mono_wrapper_supervisor_get(IntPtr eo);
[DllImport(efl.Libs.CustomExports)] public static extern void efl_mono_wrapper_supervisor_set(IntPtr eo, IntPtr ws);
@ -55,6 +51,9 @@ public class Globals
[DllImport(efl.Libs.Eo)] public static extern IntPtr
_efl_add_internal_start([MarshalAs(UnmanagedType.LPStr)] String file, int line,
IntPtr klass, IntPtr parent, byte is_ref, byte is_fallback);
[DllImport(efl.Libs.Eo)] public static extern IntPtr
_efl_add_internal_start_bindings([MarshalAs(UnmanagedType.LPStr)] String file, int line, IntPtr klass, IntPtr parent,
byte is_ref, byte is_fallback, IntPtr substitute_ctor, IntPtr data);
public delegate IntPtr
_efl_add_end_delegate(IntPtr eo, byte is_ref, byte is_fallback);
[DllImport(efl.Libs.Eo)] public static extern IntPtr
@ -196,6 +195,10 @@ public class Globals
public delegate IntPtr dlerror_delegate();
[DllImport(efl.Libs.Evil)] public static extern IntPtr dlerror();
[DllImport(efl.Libs.Eo)] public static extern IntPtr efl_constructor(IntPtr obj);
[DllImport(efl.Libs.CustomExports)] public static extern IntPtr efl_mono_avoid_top_level_constructor_callback_addr_get();
[DllImport(efl.Libs.Eo)] [return: MarshalAs(UnmanagedType.U1)] public static extern bool
efl_event_callback_priority_add(IntPtr obj, IntPtr desc, short priority, IntPtr cb, IntPtr data);

View File

@ -166,6 +166,16 @@ EAPI Eina_Free_Cb efl_mono_native_efl_unref_addr_get()
return (Eina_Free_Cb)efl_mono_thread_safe_efl_unref;
}
static Eo *_efl_mono_avoid_top_level_constructor_cb(void *data EINA_UNUSED, Eo *obj)
{
return efl_constructor(efl_super(obj, efl_class_get(obj)));
}
EAPI Efl_Substitute_Ctor_Cb efl_mono_avoid_top_level_constructor_callback_addr_get()
{
return &_efl_mono_avoid_top_level_constructor_cb;
}
// Iterator Wrapper //
typedef struct _Eina_Iterator_Wrapper_Mono

View File

@ -0,0 +1,96 @@
using System;
class InheritedConstructibleObject : Dummy.ConstructibleObject
{
public InheritedConstructibleObject() : base()
{
if (this.NativeConstructionCount != 1)
{
DefaultConstrutorCallCount = -100;
}
++DefaultConstrutorCallCount;
this.IncrementDefaultConstructionCount();
}
private InheritedConstructibleObject(ConstructingHandle ch) : base(ch)
{
if (this.NativeConstructionCount != 1)
{
SpecialConstrutorCallCount = -100;
}
++SpecialConstrutorCallCount;
this.IncrementSpecialConstructionCount();
}
public int DefaultConstrutorCallCount { get; set; } = 0;
public int SpecialConstrutorCallCount { get; set; } = 0;
public bool InheritedFlag
{
get { return inherited; }
}
public override int MultiplyIntegerValue(int v)
{
return 3 * v;
}
}
namespace TestSuite
{
class TestEoConstruction
{
public static void TestGeneratedEoDirectConstruction()
{
var obj = new Dummy.ConstructibleObject();
Test.AssertEquals(obj.NativeConstructionCount, 1);
Test.AssertEquals(obj.DefaultConstructionCount, 0);
Test.AssertEquals(obj.SpecialConstructionCount, 0);
Test.AssertEquals(obj.MultiplyIntegerValue(21), 42);
obj.Dispose();
}
public static void TestInheritedEoDirectConstruction()
{
var obj = new InheritedConstructibleObject();
Test.AssertEquals(obj.InheritedFlag, true);
Test.AssertEquals(obj.NativeConstructionCount, 1);
Test.AssertEquals(obj.DefaultConstructionCount, 1);
Test.AssertEquals(obj.SpecialConstructionCount, 0);
Test.AssertEquals(obj.DefaultConstrutorCallCount, 1);
Test.AssertEquals(obj.SpecialConstrutorCallCount, 0);
Test.AssertEquals(obj.MultiplyIntegerValue(21), 63);
obj.Dispose();
}
public static void TestInheritedEoIndirectConstruction()
{
var obj = new Dummy.ConstructibleObject();
Test.AssertEquals(obj.NativeConstructionCount, 1);
Test.AssertEquals(obj.DefaultConstructionCount, 0);
Test.AssertEquals(obj.SpecialConstructionCount, 0);
Test.AssertEquals(obj.MultiplyIntegerValue(21), 42);
var obj2 = (InheritedConstructibleObject) obj.ConstructTypeAndStore(typeof(InheritedConstructibleObject));
Test.AssertEquals(obj2.InheritedFlag, true);
Test.AssertEquals(obj2.NativeConstructionCount, 1);
Test.AssertEquals(obj2.DefaultConstructionCount, 0);
Test.AssertEquals(obj2.SpecialConstructionCount, 1);
Test.AssertEquals(obj2.DefaultConstrutorCallCount, 0);
Test.AssertEquals(obj2.SpecialConstrutorCallCount, 1);
Test.AssertEquals(obj2.MultiplyIntegerValue(21), 63);
var internalObj = obj.InternalObject;
Test.Assert(ReferenceEquals(obj2, internalObj)); // Ensure it always use the same object instance
Test.AssertEquals(obj2.NativeConstructionCount, 1); // And that constructors are not called again
Test.AssertEquals(obj2.MultiplyIntegerValue(32), 96); // And that it is still usable
obj.Dispose();
obj2.Dispose();
}
}
}

View File

@ -0,0 +1,77 @@
#include "libefl_mono_native_test.h"
typedef struct _Dummy_Constructible_Object_Data
{
Eo *internal_obj;
int native_construction_count;
int default_construction_count;
int special_construction_count;
} Dummy_Constructible_Object_Data;
EOLIAN static Eo *
_dummy_constructible_object_efl_object_constructor(Eo *obj, Dummy_Constructible_Object_Data *pd)
{
++(pd->native_construction_count);
return efl_constructor(efl_super(obj, DUMMY_CONSTRUCTIBLE_OBJECT_CLASS));
}
EOLIAN static void
_dummy_constructible_object_efl_object_destructor(Eo *obj, Dummy_Constructible_Object_Data *pd)
{
if (pd->internal_obj)
efl_unref(pd->internal_obj);
efl_destructor(efl_super(obj, DUMMY_CONSTRUCTIBLE_OBJECT_CLASS));
}
EOLIAN static Efl_Object *
_dummy_constructible_object_construct_type_and_store(Eo *obj EINA_UNUSED, Dummy_Constructible_Object_Data *pd, const Efl_Class *klass)
{
pd->internal_obj = efl_add_ref(klass, NULL);
return pd->internal_obj;
}
EOLIAN static void
_dummy_constructible_object_increment_default_construction_count(Eo *obj EINA_UNUSED, Dummy_Constructible_Object_Data *pd)
{
++(pd->default_construction_count);
}
EOLIAN static void
_dummy_constructible_object_increment_special_construction_count(Eo *obj EINA_UNUSED, Dummy_Constructible_Object_Data *pd)
{
++(pd->special_construction_count);
}
EOLIAN static int
_dummy_constructible_object_native_construction_count_get(const Eo *obj EINA_UNUSED, Dummy_Constructible_Object_Data *pd)
{
return pd->native_construction_count;
}
EOLIAN static int
_dummy_constructible_object_default_construction_count_get(const Eo *obj EINA_UNUSED, Dummy_Constructible_Object_Data *pd)
{
return pd->default_construction_count;
}
EOLIAN static int
_dummy_constructible_object_special_construction_count_get(const Eo *obj EINA_UNUSED, Dummy_Constructible_Object_Data *pd)
{
return pd->special_construction_count;
}
EOLIAN static Efl_Object *
_dummy_constructible_object_internal_object_get(const Eo *obj EINA_UNUSED, Dummy_Constructible_Object_Data *pd)
{
return pd->internal_obj;
}
EOLIAN static int
_dummy_constructible_object_multiply_integer_value(const Eo *obj EINA_UNUSED, Dummy_Constructible_Object_Data *pd, int v)
{
return 2 * v;
}
#include "dummy_constructible_object.eo.c"

View File

@ -0,0 +1,53 @@
class Dummy.Constructible_Object extends Efl.Object {
methods {
construct_type_and_store {
params {
@in type: const(Efl.Class);
}
return: Efl.Object;
}
increment_default_construction_count {
}
increment_special_construction_count {
}
@property native_construction_count {
get {
}
values {
value: int;
}
}
@property default_construction_count {
get {
}
values {
value: int;
}
}
@property special_construction_count {
get {
}
values {
value: int;
}
}
@property internal_object {
get {
}
values {
value: Efl.Object;
}
}
multiply_integer_value @const {
params {
v: int;
}
return: int;
}
}
implements {
Efl.Object.constructor;
Efl.Object.destructor;
}
}

View File

@ -59,9 +59,9 @@ Dummy_Numberwrapper **_new_obj_ref(int n)
return &r;
}
// ############ //
// Test.Testing //
// ############ //
// ################# //
// Dummy.Test_Object //
// ################# //
static Efl_Object*
_dummy_test_object_efl_object_constructor(Eo *obj, Dummy_Test_Object_Data *pd)

View File

@ -55,6 +55,7 @@
#include "dummy_inherit_helper.eo.h"
#include "dummy_part_holder.eo.h"
#include "dummy_event_manager.eo.h"
#include "dummy_constructible_object.eo.h"
#include <interfaces/efl_part.eo.h>

View File

@ -7,6 +7,7 @@ eo_files = [
'dummy_inherit_iface.eo',
'dummy_part_holder.eo',
'dummy_event_manager.eo',
'dummy_constructible_object.eo',
]
eo_file_targets = []
@ -33,6 +34,7 @@ efl_mono_native_test = library('efl_mono_native_test',
'dummy_part_holder.c',
'dummy_test_object.c',
'dummy_event_manager.c',
'dummy_constructible_object.c',
],
dependencies : [ecore, eo, efl],
)
@ -65,6 +67,7 @@ efl_mono_src = [
'Eldbus.cs',
'Eo.cs',
'EoPromises.cs',
'EoConstruction.cs',
'Errors.cs',
'Events.cs',
'FunctionPointers.cs',