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',