From b7fa7d48ac9eff4b360e83de4d974a92c84c0291 Mon Sep 17 00:00:00 2001 From: Vitor Sousa Date: Fri, 28 Jun 2019 10:29:01 -0300 Subject: [PATCH] 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 --- src/bin/eolian_mono/eolian/mono/klass.hh | 24 +++- src/bindings/mono/eo_mono/EoWrapper.cs | 112 +++++++++++++++++- src/bindings/mono/eo_mono/iwrapper.cs | 11 +- src/lib/efl_mono/efl_custom_exports_mono.c | 10 ++ src/tests/efl_mono/EoConstruction.cs | 96 +++++++++++++++ .../efl_mono/dummy_constructible_object.c | 77 ++++++++++++ .../efl_mono/dummy_constructible_object.eo | 53 +++++++++ src/tests/efl_mono/dummy_test_object.c | 6 +- src/tests/efl_mono/libefl_mono_native_test.h | 1 + src/tests/efl_mono/meson.build | 3 + 10 files changed, 380 insertions(+), 13 deletions(-) create mode 100644 src/tests/efl_mono/EoConstruction.cs create mode 100644 src/tests/efl_mono/dummy_constructible_object.c create mode 100644 src/tests/efl_mono/dummy_constructible_object.eo diff --git a/src/bin/eolian_mono/eolian/mono/klass.hh b/src/bin/eolian_mono/eolian/mono/klass.hh index 6ac4aa1c3c..8b018f6413 100644 --- a/src/bin/eolian_mono/eolian/mono/klass.hh +++ b/src/bin/eolian_mono/eolian/mono/klass.hh @@ -196,6 +196,17 @@ struct klass if (!generate_fields(sink, cls, concrete_cxt)) return false; + if (!as_generator + ( + scope_tab << "/// Constructor to be used when objects are expected to be constructed from native code.\n" + << scope_tab << "/// Tag struct storing the native handle of the object being constructed.\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(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("/// Wrapper for native methods and virtual method delegates.\n") << indent << "/// For internal use by generated code only.\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(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 << "/// Constructor to be used when objects are expected to be constructed from native code.\n" + << scope_tab << "/// Tag struct storing the native handle of the object being constructed.\n" + << scope_tab << "protected " << inherit_name << "(ConstructingHandle ch) : base(ch)\n" + << scope_tab << "{\n" + << scope_tab << "}\n\n" << scope_tab << "/// Initializes a new instance of the 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.\n" << scope_tab << "/// The native pointer to be wrapped.\n" diff --git a/src/bindings/mono/eo_mono/EoWrapper.cs b/src/bindings/mono/eo_mono/EoWrapper.cs index b6ea619923..7e512acf56 100644 --- a/src/bindings/mono/eo_mono/EoWrapper.cs +++ b/src/bindings/mono/eo_mono/EoWrapper.cs @@ -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); + + /// 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 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. + /// Tag struct storing the native handle of the object being constructed. + 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); + } + /// Initializes a new instance of the class. /// Internal usage: Constructs an instance from a native pointer. This is used when interacting with C code and should not be used directly. /// The native pointer to be wrapped. @@ -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 GetEoOps(Type type) + { + var descs = new System.Collections.Generic.List(); + + 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 diff --git a/src/bindings/mono/eo_mono/iwrapper.cs b/src/bindings/mono/eo_mono/iwrapper.cs index 82c08ccf3f..966759b314 100644 --- a/src/bindings/mono/eo_mono/iwrapper.cs +++ b/src/bindings/mono/eo_mono/iwrapper.cs @@ -44,10 +44,6 @@ public class Globals public static FunctionWrapper efl_object_shutdown_ptr = new FunctionWrapper(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); diff --git a/src/lib/efl_mono/efl_custom_exports_mono.c b/src/lib/efl_mono/efl_custom_exports_mono.c index ad74babe52..8d57ad730f 100644 --- a/src/lib/efl_mono/efl_custom_exports_mono.c +++ b/src/lib/efl_mono/efl_custom_exports_mono.c @@ -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 diff --git a/src/tests/efl_mono/EoConstruction.cs b/src/tests/efl_mono/EoConstruction.cs new file mode 100644 index 0000000000..4708befc5b --- /dev/null +++ b/src/tests/efl_mono/EoConstruction.cs @@ -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(); + } +} + +} diff --git a/src/tests/efl_mono/dummy_constructible_object.c b/src/tests/efl_mono/dummy_constructible_object.c new file mode 100644 index 0000000000..870895066c --- /dev/null +++ b/src/tests/efl_mono/dummy_constructible_object.c @@ -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" diff --git a/src/tests/efl_mono/dummy_constructible_object.eo b/src/tests/efl_mono/dummy_constructible_object.eo new file mode 100644 index 0000000000..ea508ed15c --- /dev/null +++ b/src/tests/efl_mono/dummy_constructible_object.eo @@ -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; + } +} + diff --git a/src/tests/efl_mono/dummy_test_object.c b/src/tests/efl_mono/dummy_test_object.c index 4fdc69bbed..57d2a06ae6 100644 --- a/src/tests/efl_mono/dummy_test_object.c +++ b/src/tests/efl_mono/dummy_test_object.c @@ -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) diff --git a/src/tests/efl_mono/libefl_mono_native_test.h b/src/tests/efl_mono/libefl_mono_native_test.h index 9251118fc6..b726bd05c1 100644 --- a/src/tests/efl_mono/libefl_mono_native_test.h +++ b/src/tests/efl_mono/libefl_mono_native_test.h @@ -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 diff --git a/src/tests/efl_mono/meson.build b/src/tests/efl_mono/meson.build index f75a5b8bb9..e464cdd2a8 100644 --- a/src/tests/efl_mono/meson.build +++ b/src/tests/efl_mono/meson.build @@ -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',