csharp: Make classes abstract and rework casting

Summary:
Abstract Eo classes are now proper C# abstract classes.

As a side effect, returning Eo instances from native code was reworked
to return instances of their actual Eo classes instead of previous
behavior of returning a generic Efl.Object and using static_cast.

Instead of `var window = Efl.Ui.Win.static_cast(widget.GetParent());`
Use `var window = widget.GetParent() as Efl.Ui.Win;`

Another side effect was that `efl_constructor` was removed from the list
of supported `Efl.Object` overrides. It is invoked inside
`efl_add_internal_start`, before the bindings makes the association of
the newly created EoId with the C# instance that created it, making the
managed delegate meaningless. C# users then can use regular C#
constructors to initialize fields.

Also changed to set the private data of C#-inherited classes before the
call to constructing methods (aka constructor parameters) so C# classes
can override them correctly.

Fixes T7778
Fixes T7757

Reviewers: vitor.sousa, felipealmeida, segfaultxavi

Reviewed By: vitor.sousa, segfaultxavi

Subscribers: cedric, #reviewers, #committers

Tags: #efl

Maniphest Tasks: T7778, T7757, T7702

Differential Revision: https://phab.enlightenment.org/D8550
This commit is contained in:
Lauro Moura 2019-04-05 19:53:37 -03:00 committed by Vitor Sousa
parent 4edf8036e0
commit 1e22db1150
11 changed files with 168 additions and 67 deletions

View File

@ -16,6 +16,7 @@ inline bool is_function_blacklisted(std::string const& c_name)
{
return
c_name == "efl_event_callback_array_priority_add"
|| c_name == "efl_constructor"
|| c_name == "efl_player_position_get"
|| c_name == "efl_ui_widget_focus_set"
|| c_name == "efl_ui_widget_focus_get"

View File

@ -73,7 +73,7 @@ struct unpack_event_args_visitor
}
bool operator()(grammar::attributes::klass_name const& cls) const
{
return as_generator("new " + name_helpers::klass_full_concrete_name(cls) + "(evt.Info)").generate(sink, attributes::unused, *context);
return as_generator("(Efl.Eo.Globals.CreateWrapperFor(evt.Info) as " + name_helpers::klass_full_concrete_name(cls) + ")").generate(sink, attributes::unused, *context);
}
bool operator()(attributes::complex_type_def const&) const
{

View File

@ -103,7 +103,7 @@ struct native_function_definition_generator
/****/
<< scope_tab << scope_tab << "Eina.Log.Debug(\"function " << string << " was called\");\n"
/****/
<< scope_tab << scope_tab << "Efl.Eo.IWrapper wrapper = Efl.Eo.Globals.data_get(pd);\n"
<< scope_tab << scope_tab << "Efl.Eo.IWrapper wrapper = Efl.Eo.Globals.PrivateDataGet(pd);\n"
<< scope_tab << scope_tab << "if(wrapper != null) {\n"
<< scope_tab << scope_tab << scope_tab << eolian_mono::native_function_definition_preamble()
<< scope_tab << scope_tab << scope_tab << "try {\n"

View File

@ -31,20 +31,6 @@
namespace eolian_mono {
template <typename OutputIterator, typename Context>
static bool generate_static_cast_method(OutputIterator sink, grammar::attributes::klass_def const& cls, Context const &context)
{
return as_generator(
scope_tab << "///<summary>Casts obj into an instance of this type.</summary>\n"
<< scope_tab << "public " << (helpers::has_regular_ancestor(cls) ? "new " : "") <<"static " << name_helpers::klass_concrete_name(cls) << " static_cast(Efl.Object obj)\n"
<< scope_tab << "{\n"
<< scope_tab << scope_tab << "if (obj == null)\n"
<< scope_tab << scope_tab << scope_tab << "throw new System.ArgumentNullException(\"obj\");\n"
<< scope_tab << scope_tab << "return new " << name_helpers::klass_concrete_name(cls) << "(obj.NativeHandle);\n"
<< scope_tab << "}\n"
).generate(sink, nullptr, context);
}
template <typename OutputIterator, typename Context>
static bool generate_equals_method(OutputIterator sink, Context const &context)
{
@ -110,7 +96,7 @@ struct klass
suffix = "CLASS";
break;
case attributes::class_type::abstract_:
class_type = "class";
class_type = "abstract class";
suffix = "CLASS";
break;
case attributes::class_type::mixin:
@ -207,7 +193,7 @@ struct klass
});
// Concrete class for interfaces, mixins, etc.
if(class_type != "class")
if(class_type != "class" && class_type != "abstract class")
{
auto concrete_cxt = context_add_tag(class_context{class_context::concrete}, context);
auto concrete_name = name_helpers::klass_concrete_name(cls);
@ -234,7 +220,7 @@ struct klass
<< ")] internal static extern System.IntPtr\n"
<< scope_tab << scope_tab << name_helpers::klass_get_name(cls) << "();\n"
<< scope_tab << "///<summary>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 << "public " << concrete_name << "(System.IntPtr raw)" << (root ? "" : " : base(raw)") << "\n"
<< scope_tab << "private " << concrete_name << "(System.IntPtr raw)" << (root ? "" : " : base(raw)") << "\n"
<< scope_tab << "{\n"
<< scope_tab << scope_tab << (root ? "handle = raw;\n" : "")
<< scope_tab << scope_tab << "RegisterEventProxies();\n"
@ -246,9 +232,6 @@ struct klass
if (!generate_dispose_methods(sink, cls, concrete_cxt))
return false;
if (!generate_static_cast_method(sink, cls, concrete_cxt))
return false;
if (!generate_equals_method(sink, concrete_cxt))
return false;
@ -296,7 +279,7 @@ struct klass
}
// Inheritable class
if(class_type == "class")
if(class_type == "class" || class_type == "abstract class")
{
auto inherit_cxt = context_add_tag(class_context{class_context::inherit}, context);
@ -327,9 +310,6 @@ struct klass
if (!generate_dispose_methods(sink, cls, inherit_cxt))
return false;
if (!generate_static_cast_method(sink, cls, inherit_cxt))
return false;
if (!generate_equals_method(sink, inherit_cxt))
return false;
@ -430,7 +410,7 @@ struct klass
<< scope_tab << "}\n"
).generate(sink, attributes::unused, inative_cxt))
return false;
// Native method definitions
if(!as_generator(*(native_function_definition(cls)))
.generate(sink, helpers::get_all_implementable_methods(cls), inative_cxt)) return false;
@ -534,7 +514,7 @@ struct klass
<< scope_tab << scope_tab << "FinishInstantiation();\n"
<< scope_tab << "}\n"
<< scope_tab << "///<summary>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 << "public " << inherit_name << "(System.IntPtr raw)" << (root ? "" : " : base(raw)") << "\n"
<< scope_tab << "protected " << inherit_name << "(System.IntPtr raw)" << (root ? "" : " : base(raw)") << "\n"
<< scope_tab << "{\n"
<< scope_tab << scope_tab << (root ? "handle = raw;\n" : "")
<< scope_tab << scope_tab << "RegisterEventProxies();\n"
@ -542,6 +522,23 @@ struct klass
).generate(sink, std::make_tuple(constructors, constructors, constructors), context))
return false;
// Some abstract classes (like Efl.App) have a simple regular class that is used to instantiate them
// in a controlled manner. These fake-private classes can be returned from C and we use a similarly-named
// private class to be able to instantiate them when they get to the C# world.
if (cls.type == attributes::class_type::abstract_)
{
if (!as_generator(
scope_tab << "[Efl.Eo.PrivateNativeClass]\n"
<< scope_tab << "private class " << inherit_name << "Realized : " << inherit_name << "\n"
<< scope_tab << "{\n"
<< scope_tab << scope_tab << "private " << inherit_name << "Realized(IntPtr ptr) : base(ptr)\n"
<< scope_tab << scope_tab << "{\n"
<< scope_tab << scope_tab << "}\n"
<< scope_tab << "}\n"
).generate(sink, attributes::unused, context))
return false;
}
// Internal constructors
if (!root)
{
@ -564,13 +561,14 @@ struct klass
<< scope_tab << scope_tab << "}\n"
<< scope_tab << scope_tab << "handle = Efl.Eo.Globals.instantiate_start(actual_klass, parent);\n"
<< scope_tab << scope_tab << "RegisterEventProxies();\n"
<< scope_tab << scope_tab << "if (inherited)\n"
<< scope_tab << scope_tab << "{\n"
<< scope_tab << scope_tab << scope_tab << "Efl.Eo.Globals.PrivateDataSet(this);\n"
<< scope_tab << scope_tab << "}\n"
<< scope_tab << "}\n"
<< scope_tab << "protected void FinishInstantiation()\n"
<< scope_tab << "{\n"
<< scope_tab << scope_tab << "if (inherited) {\n"
<< scope_tab << scope_tab << scope_tab << "Efl.Eo.Globals.data_set(this);\n"
<< scope_tab << scope_tab << "}\n"
<< scope_tab << scope_tab << "handle = Efl.Eo.Globals.instantiate_end(handle);\n"
<< scope_tab << scope_tab << "Eina.Error.RaiseIfUnhandledException();\n"
<< scope_tab << "}\n"

View File

@ -27,8 +27,7 @@ struct part_definition_generator
<< scope_tab << "{\n"
<< scope_tab << scope_tab << "get\n"
<< scope_tab << scope_tab << "{\n"
<< scope_tab << scope_tab << scope_tab << "Efl.Object obj = Efl.IPartNativeInherit.efl_part_get_ptr.Value.Delegate(NativeHandle, \"" << part.name << "\");\n"
<< scope_tab << scope_tab << scope_tab << "return " << part_klass_name << ".static_cast(obj);\n"
<< scope_tab << scope_tab << scope_tab << "return Efl.IPartNativeInherit.efl_part_get_ptr.Value.Delegate(NativeHandle, \"" << part.name << "\") as " << part_klass_name << ";\n"
<< scope_tab << scope_tab << "}\n"
<< scope_tab << "}\n"
).generate(sink, part.documentation, context);

View File

@ -159,10 +159,8 @@ struct to_external_field_convert_generator
if (!as_generator(
"\n"
<< indent << scope_tab << scope_tab << "_external_struct." << string
<< " = (" << concrete_name << ") System.Activator.CreateInstance(typeof("
<< concrete_name << "), new System.Object[] {_internal_struct." << string << "});\n"
<< indent << scope_tab << scope_tab << "Efl.Eo.Globals.efl_ref(_internal_struct." << string << ");\n")
.generate(sink, std::make_tuple(field_name, field_name, field_name), context))
<< " = (" << concrete_name << ") Efl.Eo.Globals.CreateWrapperFor(_internal_struct." << string << ");\n"
).generate(sink, std::make_tuple(field_name, field_name), context))
return false;
}
else if (field.type.c_type == "Eina_Binbuf *" || field.type.c_type == "const Eina_Binbuf *")

View File

@ -4,6 +4,7 @@ using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Reflection;
using Eina.Callbacks;
using static Eina.HashNativeFunctions;
@ -196,13 +197,6 @@ public class StringElementTraits : IBaseElementTraits<string>
public class EflObjectElementTraits<T> : IBaseElementTraits<T>
{
private System.Type concreteType = null;
public EflObjectElementTraits(System.Type concrete)
{
concreteType = concrete;
}
public IntPtr ManagedToNativeAlloc(T man)
{
IntPtr h = ((Efl.Eo.IWrapper)man).NativeHandle;
@ -290,7 +284,7 @@ public class EflObjectElementTraits<T> : IBaseElementTraits<T>
return default(T);
}
return (T)Activator.CreateInstance(concreteType, Efl.Eo.Globals.efl_ref(nat));
return (T) Efl.Eo.Globals.CreateWrapperFor(nat, shouldIncRef: true);
}
public T NativeToManagedRef(IntPtr nat)
@ -762,7 +756,9 @@ public static class TraitFunctions
throw new Exception("Failed to get a suitable concrete class for this type.");
}
traits = new EflObjectElementTraits<T>(concrete);
// No need to pass concrete as the traits class will use reflection to get the actually most
// derived type returned.
traits = new EflObjectElementTraits<T>();
}
else if (IsString(type))
{

View File

@ -4,6 +4,7 @@ using System;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Threading;
using static Eina.NativeCustomExportFunctions;
@ -289,7 +290,7 @@ public class Globals
return ifaces_lst;
}
private static Efl.Eo.NativeClass get_native_class(System.Type type)
private static Efl.Eo.NativeClass GetNativeClass(System.Type type)
{
var attrs = System.Attribute.GetCustomAttributes(type);
foreach (var attr in attrs)
@ -306,7 +307,7 @@ public class Globals
public static byte class_initializer_call(IntPtr klass, System.Type type)
{
Eina.Log.Debug($"called with 0x{klass.ToInt64():x} {type}");
Efl.Eo.NativeClass nativeClass = get_native_class(type.BaseType);
Efl.Eo.NativeClass nativeClass = GetNativeClass(type.BaseType);
if (nativeClass != null)
{
@ -320,7 +321,7 @@ public class Globals
{
if (!System.Array.Exists(base_interfaces, element => element == iface))
{
var nc = get_native_class(iface);
var nc = GetNativeClass(iface);
if (nc != null)
{
var moredescs = nc.GetEoOps(type);
@ -442,7 +443,7 @@ public class Globals
return eo;
}
public static void data_set(Efl.Eo.IWrapper obj)
public static void PrivateDataSet(Efl.Eo.IWrapper obj)
{
Eina.Log.Debug($"Calling data_scope_get with obj {obj.NativeHandle.ToInt64():x} and klass {obj.NativeClass.ToInt64():x}");
IntPtr pd = Efl.Eo.Globals.efl_data_scope_get(obj.NativeHandle, obj.NativeClass);
@ -454,7 +455,7 @@ public class Globals
}
}
public static Efl.Eo.IWrapper data_get(IntPtr pd)
public static Efl.Eo.IWrapper PrivateDataGet(IntPtr pd)
{
EolianPD epd = (EolianPD)Marshal.PtrToStructure(pd, typeof(EolianPD));
if (epd.pointer != IntPtr.Zero)
@ -542,6 +543,80 @@ public class Globals
return tcs.Task;
}
/// <summary>Returns whether the given type was generated by eolian-mono</summary>
/// <param name="managedType">The type to check.</param>
/// <returns>True if generated by eolian-mono. False otherwise.</returns>
private static bool IsGeneratedClass(System.Type managedType)
{
return GetNativeClass(managedType) != null;
}
/// <summary>Creates a new wrapper for the given Eo id.
///
/// <para>If the Eo was created from a non-generated class (i.e. C#-pure class), it returns
/// the C# instance handle stored in the Eo's private data.</para>
///
/// <para>For generated-class Eo instance, we use reflection to get the correct C# type to re-wrap
/// it.</para>
/// </summary>
///
/// <param name="handle">The Eo id to be wrapped.</param>
/// <param name="shouldIncRef">Whether we should increase the refcount of the Eo instance.</param>
/// <returns>The C# wrapper for this instance.</returns>
public static Efl.Eo.IWrapper CreateWrapperFor(System.IntPtr handle, bool shouldIncRef=true)
{
IntPtr eoKlass = efl_class_get(handle);
if (eoKlass == IntPtr.Zero)
{
throw new InvalidOperationException($"Can't get Eo class for object handle 0x{handle.ToInt64():x}");
}
var managedType = ClassRegister.GetManagedType(eoKlass);
if (managedType == null)
{
IntPtr nativeName = efl_class_name_get(eoKlass);
var name = Eina.StringConversion.NativeUtf8ToManagedString(nativeName);
throw new InvalidOperationException($"Can't get Managed class for object handle 0x{handle.ToInt64():x} with native class [{name}]");
}
// Pure C# classes that inherit from generated classes store their C# instance in their
// Eo private data field.
if (!IsGeneratedClass(managedType))
{
Efl.Eo.IWrapper instance = null;
IntPtr pd = efl_data_scope_get(handle, eoKlass);
if (pd != IntPtr.Zero)
{
instance = PrivateDataGet(pd);
}
return instance;
}
System.Reflection.ConstructorInfo constructor = null;
try
{
var flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
constructor = managedType.GetConstructor(flags, null, new Type[1] { typeof(System.IntPtr) }, null);
}
catch (InvalidOperationException)
{
throw new InvalidOperationException($"Can't get constructor for type {managedType}");
}
var ret = constructor.Invoke(new object[1] { handle }) as Efl.Eo.IWrapper;
if (ret != null && shouldIncRef)
Efl.Eo.Globals.efl_ref(handle);
return ret;
}
} // Globals
public static class Config
@ -560,7 +635,7 @@ public static class Config
[System.AttributeUsage(System.AttributeTargets.Class |
System.AttributeTargets.Interface,
AllowMultiple = false,
Inherited = true)
Inherited = false)
]
public abstract class NativeClass : System.Attribute
{
@ -568,6 +643,22 @@ public abstract class NativeClass : System.Attribute
public abstract System.Collections.Generic.List<Efl_Op_Description> GetEoOps(System.Type type);
}
/// <summary>Attribute for private native classes.
///
/// <para>For internal usage by generated code only.</para></summary>
public class PrivateNativeClass : NativeClass
{
public override IntPtr GetEflClass()
{
return IntPtr.Zero;
}
public override System.Collections.Generic.List<Efl_Op_Description> GetEoOps(System.Type type)
{
return null;
}
}
public interface IWrapper
{
/// <summary>Pointer to internal Eo instance.</summary>
@ -605,6 +696,16 @@ public static class ClassRegister
var klass_type = Efl.Eo.Globals.efl_class_type_get(klass);
// Check if this is an internal implementation of an abstract class
var abstract_impl_suffix = "Realized";
if (name.EndsWith(abstract_impl_suffix))
{
name = name.Substring(0, name.Length - abstract_impl_suffix.Length);
var lastDot = name.LastIndexOf(".");
var klassName = name.Substring(lastDot + 1);
name += "+" + klassName + abstract_impl_suffix; // '+' is the separator for nested classes
}
// When converting to managed, interfaces and mixins gets the 'I' prefix.
if (klass_type == Efl.Eo.Globals.EflClassType.Interface || klass_type == Efl.Eo.Globals.EflClassType.Mixin)
{
@ -795,6 +896,12 @@ public class MarshalTest<T, U> : ICustomMarshaler
public IntPtr MarshalManagedToNative(object ManagedObj)
{
Eina.Log.Debug("MarshalTest.MarshallManagedToNative");
if (ManagedObj == null)
{
return IntPtr.Zero;
}
var r = ((IWrapper)ManagedObj).NativeHandle;
if (typeof(U) == typeof(OwnTag))
{
@ -806,14 +913,7 @@ public class MarshalTest<T, U> : ICustomMarshaler
public object MarshalNativeToManaged(IntPtr pNativeData)
{
Eina.Log.Debug("MarshalTest.MarshalNativeToManaged");
if (typeof(U) != typeof(OwnTag))
{
Efl.Eo.Globals.efl_ref(pNativeData);
}
return Activator.CreateInstance(typeof(T), new System.Object[] {pNativeData});
//return null;
return Efl.Eo.Globals.CreateWrapperFor(pNativeData, shouldIncRef : typeof(U) != typeof(OwnTag));
}
}

View File

@ -131,7 +131,7 @@ class TestEoParent
Test.AssertEquals(parent, child.GetParent());
var parent_retrieved = Dummy.TestObject.static_cast(child.GetParent());
var parent_retrieved = child.GetParent() as Dummy.TestObject;
Test.AssertEquals(parent, parent_retrieved);
}
@ -142,7 +142,7 @@ class TestEoParent
Test.AssertEquals(parent, child.GetParent());
Dummy.Numberwrapper parent_retrieved = Dummy.Numberwrapper.static_cast(child.GetParent());
Dummy.Numberwrapper parent_retrieved = child.GetParent() as Dummy.Numberwrapper;
Test.AssertEquals(parent, parent_retrieved);
}
@ -160,7 +160,7 @@ class TestEoParent
Test.AssertEquals(parent, child.GetParent());
var parent_from_cast = Dummy.TestObject.static_cast(child.GetParent());
var parent_from_cast = child.GetParent() as Derived;
Test.AssertEquals(parent, parent_from_cast);
}
}
@ -430,7 +430,7 @@ class TestInterfaceConcrete
public static void test_iface_concrete_methods()
{
var obj = new Dummy.TestObject();
Dummy.ITestIface iface = Dummy.ITestIfaceConcrete.static_cast(obj);
Dummy.ITestIface iface = obj.ReturnIface();
iface.IfaceProp = 1970;
Test.AssertEquals(iface.IfaceProp, 1970);
@ -443,7 +443,7 @@ class TestProvider
{
// Tests only the direction C# -> C
var obj = new Dummy.TestObject();
Dummy.Numberwrapper provider = Dummy.Numberwrapper.static_cast(obj.FindProvider(typeof(Dummy.Numberwrapper)));
Dummy.Numberwrapper provider = obj.FindProvider(typeof(Dummy.Numberwrapper)) as Dummy.Numberwrapper;
Test.AssertEquals(provider.GetType(), typeof(Dummy.Numberwrapper));
Test.AssertEquals(provider.GetNumber(), 1999);
}

View File

@ -111,6 +111,10 @@ class Dummy.Test_Object extends Efl.Object implements Dummy.Test_Iface {
return: Dummy.Test_Object;
}
return_iface {
return: Dummy.Test_Iface;
}
int_out {
params {
@in x: int;

View File

@ -45,11 +45,11 @@
# endif
#endif
#include "dummy_test_iface.eo.h"
#include "dummy_inherit_iface.eo.h"
#include "dummy_numberwrapper.eo.h"
#include "dummy_test_object.eo.h"
#include "dummy_child.eo.h"
#include "dummy_test_iface.eo.h"
#include "dummy_inherit_iface.eo.h"
#include "dummy_inherit_helper.eo.h"
#include "dummy_part_holder.eo.h"
@ -172,6 +172,11 @@ Efl_Object *_dummy_test_object_return_null_object(Eo *obj EINA_UNUSED, EINA_UNUS
return NULL;
}
Dummy_Test_Iface *_dummy_test_object_return_iface(Eo *obj, EINA_UNUSED Dummy_Test_Object_Data *pd)
{
return obj;
}
void _dummy_test_object_int_out(EINA_UNUSED Eo *obj, EINA_UNUSED Dummy_Test_Object_Data *pd, int x, int *y)
{
*y = -x;