Compare commits

...

7 Commits

Author SHA1 Message Date
Lauro Moura 2b3b3b7704 csharp: Add a SafePtr wrapper in debug mode
This wrapper will trigger `ObjectDisposedException` when trying to call
a method on object after it was disposed.

It is enabled only with `--buildtype=debug` when configuring meson.
2019-05-24 15:31:07 -03:00
Lauro Moura a902b56d98 csharp: Do some iterations on exit.
So we can kinda properly clean up things before shutting down.
2019-05-24 15:31:07 -03:00
Vitor Sousa f6b9e2aa57 csharp: Refactor wrapper lifetime.
Summary:
This commit makes use of the `ownership,shared` and `ownership,unique`
events from Efl.Object in order to avoid the C# wrapper from being
collected while C code holds a reference to the object.

For example, creating a list of items in a for loop and attaching events to
them would fails without this commit, as the C# GC may collect the wrapper.

The basic idea is that we use a `WrapperSupervisor`, which is stored in
the Eo data storage, with a GCHandle allocated for the lifetime of the
underlying Eo object. This supervisor takes care of holding either a
weak C# reference (when in unique mode, allowing the wrapper to be GC'd)
or a hard C# reference (when in shared mode, making the wrapper
non-collectable while the Eo has extra references).

One limitation is that object graphs can leak if a shared object in the
graph - an Eo child for example - stores a hard reference to another
object in the graph as a C# field. In this example, this causes the
parent to always have a hard C# reference (from the child) as the child
is non-collectable due to the parent holding an Eo reference to it.

Test Plan: `ninja test` and `make test`

Reviewers: lauromoura, felipealmeida, woohyun, segfaultxavi

Subscribers: cedric, #reviewers, #committers

Tags: #efl

Differential Revision: https://phab.enlightenment.org/D9014
2019-05-24 15:31:07 -03:00
Lauro Moura fd01e096f7 csharp: Fix build under dotnet 2019-05-24 15:31:06 -03:00
Vitor Sousa 8538b00da6 eo: add events to track the ownership status of an Eo object
Summary:
Some user code may want to track an object ownership in regard to whether it is
kept by just one owner or shared between many owners.

This is specially true for code provided by bindings to other programming
languages, where different kinds of resource management may take place.

The event `ownership,unique` is triggered whenever the object refcount goes
from two to one, as a signal that it has just one owner from now on.

The event `ownership,shared` is triggered whenever the object refcount goes
from one to two, as a signal that it has multiple owners from now on.
It will not trigger when further increasing the refcount to any value beyond
two.

We also add benchmarks for sharing (i.e. increasing the refcount) and them
unsharing objects, in order to evaluate the performance impact of this patch.

Reviewers: lauromoura, felipealmeida, cedric, bu5hm4n, segfaultxavi

Subscribers: #reviewers, woohyun, #committers

Tags: #efl

Differential Revision: https://phab.enlightenment.org/D8678
2019-05-24 15:31:06 -03:00
Lauro Moura 5d70b2c09f eolian-cxx: Fix some warnings from cppcheck 2019-05-24 15:31:06 -03:00
Lauro Moura 97b56c9bcd csharp: Add some tests
- Wrappers losing C# ref while alive in Eo
- Inherited instances being collected

Also cleanup GC and loop queue before each test
2019-05-24 15:31:06 -03:00
31 changed files with 1169 additions and 484 deletions

View File

@ -6,7 +6,9 @@ efl_eo_mono_files = \
bindings/mono/eo_mono/iwrapper.cs \
bindings/mono/eo_mono/FunctionWrapper.cs \
bindings/mono/eo_mono/NativeModule.cs \
bindings/mono/eo_mono/workaround.cs
bindings/mono/eo_mono/workaround.cs \
bindings/mono/eo_mono/EoWrapper.cs \
bindings/mono/eo_mono/WrapperSupervisor.cs
if HAVE_WIN32

View File

@ -38,10 +38,44 @@ bench_efl_add_jump_by_2(int request)
free(objs);
}
static void
bench_efl_add_shared_ownership(int request)
{
int i;
Eo **objs = calloc(request, sizeof(Eo *));
Eo *p = efl_add_ref(SIMPLE_CLASS, NULL);
for (i = 0; i < request; i++)
objs[i] = efl_add_ref(SIMPLE_CLASS, p);
efl_unref(p);
for (i = 0; i < request; i++)
efl_unref(objs[i]);
free(objs);
}
static void
bench_efl_add_shared_ownership_alternative(int request)
{
int i;
Eo **objs = calloc(request, sizeof(Eo *));
Eo *p = efl_add_ref(SIMPLE_CLASS, NULL);
for (i = 0; i < request; i++)
objs[i] = efl_add(SIMPLE_CLASS, p);
for (i = 0; i < request; i++)
efl_ref(objs[i]);
for (i = 0; i < request; i++)
efl_unref(objs[i]);
efl_unref(p);
free(objs);
}
void eo_bench_efl_add(Eina_Benchmark *bench)
{
eina_benchmark_register(bench, "efl_add_linear",
EINA_BENCHMARK(bench_efl_add_linear), _EO_BENCH_TIMES(1000, 10, 50000));
eina_benchmark_register(bench, "efl_add_jump_by_2",
EINA_BENCHMARK(bench_efl_add_jump_by_2), _EO_BENCH_TIMES(1000, 10, 50000));
eina_benchmark_register(bench, "efl_add_shared_ownership",
EINA_BENCHMARK(bench_efl_add_shared_ownership), _EO_BENCH_TIMES(1000, 10, 50000));
eina_benchmark_register(bench, "efl_add_shared_ownership_alternative",
EINA_BENCHMARK(bench_efl_add_shared_ownership_alternative), _EO_BENCH_TIMES(1000, 10, 50000));
}

View File

@ -431,10 +431,9 @@ struct event_definition_generator
<< scope_tab << scope_tab << "{\n"
<< scope_tab << scope_tab << scope_tab << "lock (eventLock)\n"
<< scope_tab << scope_tab << scope_tab << "{\n"
<< scope_tab << scope_tab << scope_tab << scope_tab << "var wRef = new WeakReference(this);\n"
<< scope_tab << scope_tab << scope_tab << scope_tab << "Efl.EventCb callerCb = (IntPtr data, ref Efl.Event.NativeStruct evt) =>\n"
<< scope_tab << scope_tab << scope_tab << scope_tab << "{\n"
<< scope_tab << scope_tab << scope_tab << scope_tab << scope_tab << "var obj = wRef.Target as Efl.Eo.IWrapper;\n"
<< scope_tab << scope_tab << scope_tab << scope_tab << scope_tab << "var obj = Efl.Eo.Globals.WrapperSupervisorPtrToManaged(data).Target;\n"
<< scope_tab << scope_tab << scope_tab << scope_tab << scope_tab << "if (obj != null)\n"
<< scope_tab << scope_tab << scope_tab << scope_tab << scope_tab << "{\n"
<< scope_tab << scope_tab << scope_tab << scope_tab << scope_tab << scope_tab << event_args

View File

@ -104,14 +104,14 @@ struct native_function_definition_generator
<< ")\n"
<< indent << "{\n"
<< indent << scope_tab << "Eina.Log.Debug(\"function " << string << " was called\");\n"
<< indent << scope_tab << "Efl.Eo.IWrapper wrapper = Efl.Eo.Globals.PrivateDataGet(pd);\n"
<< indent << scope_tab << "if (wrapper != null)\n"
<< indent << scope_tab << "var ws = Efl.Eo.Globals.GetWrapperSupervisor(obj);\n"
<< indent << scope_tab << "if (ws != null)\n"
<< indent << scope_tab << "{\n"
<< eolian_mono::native_function_definition_preamble()
<< indent << scope_tab << scope_tab << "try\n"
<< indent << scope_tab << scope_tab << "{\n"
<< indent << scope_tab << scope_tab << scope_tab << (return_type != "void" ? "_ret_var = " : "")
<< (f.is_static ? "" : "((") << klass_cast_name << (f.is_static ? "." : ")wrapper).") << string
<< (f.is_static ? "" : "((") << klass_cast_name << (f.is_static ? "." : ")ws.Target).") << string
<< "(" << (native_argument_invocation % ", ") << ");\n"
<< indent << scope_tab << scope_tab << "}\n"
<< indent << scope_tab << scope_tab << "catch (Exception e)\n"

View File

@ -31,37 +31,6 @@
namespace eolian_mono {
template <typename OutputIterator, typename Context>
static bool generate_equals_method(OutputIterator sink, Context const &context)
{
return as_generator(
scope_tab << "/// <summary>Verifies if the given object is equal to this one.</summary>\n"
<< scope_tab << "/// <param name=\"instance\">The object to compare to.</param>\n"
<< scope_tab << "/// <returns>True if both objects point to the same native object.</returns>\n"
<< scope_tab << "public override bool Equals(object instance)\n"
<< scope_tab << "{\n"
<< scope_tab << scope_tab << "var other = instance as Efl.Object;\n"
<< scope_tab << scope_tab << "if (other == null)\n"
<< scope_tab << scope_tab << "{\n"
<< scope_tab << scope_tab << scope_tab << "return false;\n"
<< scope_tab << scope_tab << "}\n"
<< scope_tab << scope_tab << "return this.NativeHandle == other.NativeHandle;\n"
<< scope_tab << "}\n\n"
<< scope_tab << "/// <summary>Gets the hash code for this object based on the native pointer it points to.</summary>\n"
<< scope_tab << "/// <returns>The value of the pointer, to be used as the hash code of this object.</returns>\n"
<< scope_tab << "public override int GetHashCode()\n"
<< scope_tab << "{\n"
<< scope_tab << scope_tab << "return this.NativeHandle.ToInt32();\n"
<< scope_tab << "}\n\n"
<< scope_tab << "/// <summary>Turns the native pointer into a string representation.</summary>\n"
<< scope_tab << "/// <returns>A string with the type and the native pointer for this object.</returns>\n"
<< scope_tab << "public override String ToString()\n"
<< scope_tab << "{\n"
<< scope_tab << scope_tab << "return $\"{this.GetType().Name}@[{this.NativeHandle.ToInt32():x}]\";\n"
<< scope_tab << "}\n\n"
).generate(sink, nullptr, context);
}
/* Get the actual number of functions of a class, checking for blacklisted ones */
template<typename Context>
static std::size_t
@ -216,9 +185,9 @@ struct klass
if(!as_generator
(
documentation
<< "sealed public class " << concrete_name << " : " << "\n"
<< (klass_full_concrete_or_interface_name % ",") << "\n"
<< (inherit_classes.size() > 0 ? ", " : "" ) << interface_name << "\n"
<< "sealed public class " << concrete_name << " :\n"
<< scope_tab << (root ? "Efl.Eo.EoWrapper" : "") << (klass_full_concrete_or_interface_name % "") << "\n"
<< scope_tab << ", " << interface_name << "\n"
<< scope_tab << *(", " << name_helpers::klass_full_concrete_or_interface_name) << "\n"
<< "{\n"
).generate(sink, std::make_tuple(cls, inherit_classes, inherit_interfaces), concrete_cxt))
@ -227,7 +196,6 @@ struct klass
if (!generate_fields(sink, cls, concrete_cxt))
return false;
bool root = !helpers::has_regular_ancestor(cls);
if (!as_generator
(
scope_tab << "[System.Runtime.InteropServices.DllImport(" << context_find_tag<library_context>(concrete_cxt).actual_library_name(cls.filename)
@ -235,20 +203,13 @@ struct klass
<< scope_tab << scope_tab << name_helpers::klass_get_name(cls) << "();\n"
<< scope_tab << "/// <summary>Initializes a new instance of the <see cref=\"" << interface_name << "\"/> class.\n"
<< scope_tab << "/// Internal usage: This is used when interacting with C code and should not be used directly.</summary>\n"
<< scope_tab << "private " << concrete_name << "(System.IntPtr raw)" << (root ? "" : " : base(raw)") << "\n"
<< scope_tab << "private " << concrete_name << "(System.IntPtr raw) : base(raw)\n"
<< scope_tab << "{\n"
<< scope_tab << scope_tab << (root ? "handle = raw;\n" : "")
<< scope_tab << "}\n"
<< scope_tab << "}\n\n"
)
.generate(sink, attributes::unused, concrete_cxt))
return false;
if (!generate_dispose_methods(sink, cls, concrete_cxt))
return false;
if (!generate_equals_method(sink, concrete_cxt))
return false;
if (!generate_events(sink, cls, concrete_cxt))
return false;
@ -305,10 +266,9 @@ struct klass
<< "[" << name_helpers::klass_full_native_inherit_name(cls) << "]\n"
<< "public " << class_type << " " << name_helpers::klass_concrete_name(cls) << " : "
<< (klass_full_concrete_or_interface_name % ",") // classes
<< (inherit_classes.empty() ? "" : ",")
<< " Efl.Eo.IWrapper" << (root ? ", IDisposable" : "")
<< (inherit_interfaces.empty() ? "" : ",")
<< (klass_full_concrete_or_interface_name % ",") // interfaces
<< (root ? "Efl.Eo.EoWrapper" : "") // ... or root
<< (inherit_interfaces.empty() ? "" : ", ")
<< (klass_full_concrete_or_interface_name % ", ") // interfaces
<< "\n{\n"
)
.generate(sink, std::make_tuple(cls, inherit_classes, inherit_interfaces), inherit_cxt))
@ -322,12 +282,6 @@ struct klass
if (!generate_constructors(sink, cls, inherit_cxt))
return false;
if (!generate_dispose_methods(sink, cls, inherit_cxt))
return false;
if (!generate_equals_method(sink, inherit_cxt))
return false;
if (!generate_events(sink, cls, inherit_cxt))
return false;
@ -479,22 +433,14 @@ struct klass
bool generate_fields(OutputIterator sink, attributes::klass_def const& cls, Context const& context) const
{
std::string visibility = is_inherit_context(context) ? "protected " : "private ";
bool root = !helpers::has_regular_ancestor(cls);
bool is_inherit = is_inherit_context(context);
std::string class_getter = name_helpers::klass_get_name(cls) + "()";
std::string native_inherit_full_name = name_helpers::klass_full_native_inherit_name(cls);
auto inherit_name = name_helpers::klass_concrete_name(cls);
std::string raw_klass_modifier;
if (!root)
raw_klass_modifier = "override ";
else if (is_inherit)
raw_klass_modifier = "virtual ";
if(!as_generator(
scope_tab << "///<summary>Pointer to the native class description.</summary>\n"
<< scope_tab << "public " << raw_klass_modifier << "System.IntPtr NativeClass\n"
<< scope_tab << "public override System.IntPtr NativeClass\n"
<< scope_tab << "{\n"
<< scope_tab << scope_tab << "get\n"
<< scope_tab << scope_tab << "{\n"
@ -511,42 +457,12 @@ struct klass
).generate(sink, attributes::unused, context))
return false;
// The remaining fields aren't needed in children classes.
if (!root)
return true;
if (cls.get_all_events().size() > 0)
if (!as_generator(
scope_tab << "/// <summary>Internal usage by derived classes to track native events.</summary>\n"
<< scope_tab << visibility << "Dictionary<(IntPtr desc, object evtDelegate), (IntPtr evtCallerPtr, Efl.EventCb evtCaller)> eoEvents = new Dictionary<(IntPtr desc, object evtDelegate), (IntPtr evtCallerPtr, Efl.EventCb evtCaller)>();\n"
<< scope_tab << "/// <summary>Internal usage by derived classes to lock native event handlers.</summary>\n"
<< scope_tab << visibility << "readonly object eventLock = new object();\n")
.generate(sink, attributes::unused, context))
return false;
if (is_inherit)
{
if (!as_generator(
scope_tab << "/// <summary>Internal usage to detect whether this instance is from a generated class or not.</summary>\n"
<< scope_tab << "protected bool inherited;\n"
).generate(sink, attributes::unused, context))
return false;
}
return as_generator(
scope_tab << visibility << " System.IntPtr handle;\n"
<< scope_tab << "///<summary>Pointer to the native instance.</summary>\n"
<< scope_tab << "public System.IntPtr NativeHandle\n"
<< scope_tab << "{\n"
<< scope_tab << scope_tab << "get { return handle; }\n"
<< scope_tab << "}\n\n"
).generate(sink, attributes::unused, context);
return true;
}
template <typename OutputIterator, typename Context>
bool generate_constructors(OutputIterator sink, attributes::klass_def const& cls, Context const& context) const
{
bool root = !helpers::has_regular_ancestor(cls);
auto inherit_name = name_helpers::klass_concrete_name(cls);
if(!as_generator(
@ -571,7 +487,7 @@ struct klass
// For constructors with arguments, the parent is also required, as optional parameters can't come before non-optional paramenters.
<< scope_tab << "public " << inherit_name << "(Efl.Object parent" << ((constructors.size() > 0) ? "" : "= null") << "\n"
<< scope_tab << scope_tab << scope_tab << *(", " << constructor_param ) << ") : "
<< (root ? "this" : "base") << "(" << name_helpers::klass_get_name(cls) << "(), typeof(" << inherit_name << "), parent)\n"
<< "base(" << name_helpers::klass_get_name(cls) << "(), typeof(" << inherit_name << "), parent)\n"
<< scope_tab << "{\n"
<< (*(scope_tab << scope_tab << constructor_invocation << "\n"))
<< scope_tab << scope_tab << "FinishInstantiation();\n"
@ -579,9 +495,8 @@ struct klass
<< 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"
<< scope_tab << "protected " << inherit_name << "(System.IntPtr raw)" << (root ? "" : " : base(raw)") << "\n"
<< scope_tab << "protected " << inherit_name << "(System.IntPtr raw) : base(raw)\n"
<< scope_tab << "{\n"
<< scope_tab << scope_tab << (root ? "handle = raw;\n" : "")
<< scope_tab << "}\n\n"
).generate(sink, std::make_tuple(constructors, constructors, constructors), context))
return false;
@ -603,124 +518,16 @@ struct klass
return false;
}
// Internal constructors
if (!root)
{
return as_generator(
scope_tab << "/// <summary>Initializes a new instance of the <see cref=\"" << inherit_name << "\"/> class.\n"
<< scope_tab << "/// Internal usage: Constructor to forward the wrapper initialization to the root class that interfaces with native code. Should not be used directly.</summary>\n"
<< scope_tab << "/// <param name=\"baseKlass\">The pointer to the base native Eo class.</param>\n"
<< scope_tab << "/// <param name=\"managedType\">The managed type of the public constructor that originated this call.</param>\n"
<< scope_tab << "/// <param name=\"parent\">The Efl.Object parent of this instance.</param>\n"
<< scope_tab << "protected " << inherit_name << "(IntPtr baseKlass, System.Type managedType, Efl.Object parent) : base(baseKlass, managedType, parent)\n"
<< scope_tab << "{\n"
<< scope_tab << "}\n\n"
).generate(sink, attributes::unused, context);
}
// Detailed constructors go only in root classes.
return as_generator(
/// Actual root costructor that creates class and instantiates
scope_tab << "/// <summary>Initializes a new instance of the <see cref=\"" << inherit_name << "\"/> class.\n"
<< scope_tab << "/// Internal usage: Constructor to actually call the native library constructors. C# subclasses\n"
<< scope_tab << "/// must use the public constructor only.</summary>\n"
<< scope_tab << "/// <param name=\"baseKlass\">The pointer to the base native Eo class.</param>\n"
<< scope_tab << "/// <param name=\"managedType\">The managed type of the public constructor that originated this call.</param>\n"
<< scope_tab << "/// <param name=\"parent\">The Efl.Object parent of this instance.</param>\n"
<< scope_tab << "protected " << inherit_name << "(IntPtr baseKlass, System.Type managedType, Efl.Object parent)\n"
<< scope_tab << "{\n"
<< scope_tab << scope_tab << "inherited = ((object)this).GetType() != managedType;\n"
<< scope_tab << scope_tab << "IntPtr actual_klass = baseKlass;\n"
<< scope_tab << scope_tab << "if (inherited)\n"
<< scope_tab << scope_tab << "{\n"
<< scope_tab << scope_tab << scope_tab << "actual_klass = Efl.Eo.ClassRegister.GetInheritKlassOrRegister(baseKlass, ((object)this).GetType());\n"
<< scope_tab << scope_tab << "}\n\n"
<< scope_tab << scope_tab << "handle = Efl.Eo.Globals.instantiate_start(actual_klass, parent);\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\n"
<< scope_tab << "/// <summary>Finishes instantiating this object.\n"
<< scope_tab << "/// Internal usage by generated code.</summary>\n"
<< scope_tab << "protected void FinishInstantiation()\n"
<< 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\n"
).generate(sink, attributes::unused, context);
}
template <typename OutputIterator, typename Context>
bool generate_dispose_methods(OutputIterator sink, attributes::klass_def const& cls, Context const& context) const
{
if (helpers::has_regular_ancestor(cls))
return true;
std::string visibility = is_inherit_context(context) ? "protected virtual " : "private ";
auto inherit_name = name_helpers::klass_concrete_name(cls);
std::string events_gchandle;
if (cls.get_all_events().size() > 0)
{
auto events_gchandle_sink = std::back_inserter(events_gchandle);
if (!as_generator(scope_tab << scope_tab << scope_tab << "if (eoEvents.Count != 0)\n"
<< scope_tab << scope_tab << scope_tab << "{\n"
<< scope_tab << scope_tab << scope_tab << scope_tab << "GCHandle gcHandle = GCHandle.Alloc(eoEvents);\n"
<< scope_tab << scope_tab << scope_tab << scope_tab << "gcHandlePtr = GCHandle.ToIntPtr(gcHandle);\n"
<< scope_tab << scope_tab << scope_tab << "}\n\n")
.generate(events_gchandle_sink, attributes::unused, context))
return false;
}
return as_generator(
scope_tab << "///<summary>Destructor.</summary>\n"
<< scope_tab << "~" << inherit_name << "()\n"
<< scope_tab << "{\n"
<< scope_tab << scope_tab << "Dispose(false);\n"
<< scope_tab << "}\n\n"
<< scope_tab << "///<summary>Releases the underlying native instance.</summary>\n"
<< scope_tab << visibility << "void Dispose(bool disposing)\n"
<< scope_tab << "{\n"
<< scope_tab << scope_tab << "if (handle != System.IntPtr.Zero)\n"
<< scope_tab << scope_tab << "{\n"
<< scope_tab << scope_tab << scope_tab << "IntPtr h = handle;\n"
<< scope_tab << scope_tab << scope_tab << "handle = IntPtr.Zero;\n\n"
<< scope_tab << scope_tab << scope_tab << "IntPtr gcHandlePtr = IntPtr.Zero;\n"
<< events_gchandle
<< scope_tab << scope_tab << scope_tab << "if (disposing)\n"
<< scope_tab << scope_tab << scope_tab << "{\n"
<< scope_tab << scope_tab << scope_tab << scope_tab << "Efl.Eo.Globals.efl_mono_native_dispose(h, gcHandlePtr);\n"
<< scope_tab << scope_tab << scope_tab << "}\n"
<< scope_tab << scope_tab << scope_tab << "else\n"
<< scope_tab << scope_tab << scope_tab << "{\n"
<< scope_tab << scope_tab << scope_tab << scope_tab << "Monitor.Enter(Efl.All.InitLock);\n"
<< scope_tab << scope_tab << scope_tab << scope_tab << "if (Efl.All.MainLoopInitialized)\n"
<< scope_tab << scope_tab << scope_tab << scope_tab << "{\n"
<< scope_tab << scope_tab << scope_tab << scope_tab << scope_tab << "Efl.Eo.Globals.efl_mono_thread_safe_native_dispose(h, gcHandlePtr);\n"
<< scope_tab << scope_tab << scope_tab << scope_tab << "}\n\n"
<< scope_tab << scope_tab << scope_tab << scope_tab << "Monitor.Exit(Efl.All.InitLock);\n"
<< scope_tab << scope_tab << scope_tab << "}\n"
<< scope_tab << scope_tab << "}\n\n"
<< scope_tab << "}\n\n"
<< scope_tab << "///<summary>Releases the underlying native instance.</summary>\n"
<< scope_tab << "public void Dispose()\n"
<< scope_tab << "{\n"
<< scope_tab << scope_tab << "Dispose(true);\n"
<< scope_tab << scope_tab << "GC.SuppressFinalize(this);\n"
<< scope_tab << "}\n\n"
).generate(sink, attributes::unused, context);
scope_tab << "/// <summary>Initializes a new instance of the <see cref=\"" << inherit_name << "\"/> class.\n"
<< scope_tab << "/// Internal usage: Constructor to forward the wrapper initialization to the root class that interfaces with native code. Should not be used directly.</summary>\n"
<< scope_tab << "/// <param name=\"baseKlass\">The pointer to the base native Eo class.</param>\n"
<< scope_tab << "/// <param name=\"managedType\">The managed type of the public constructor that originated this call.</param>\n"
<< scope_tab << "/// <param name=\"parent\">The Efl.Object parent of this instance.</param>\n"
<< scope_tab << "protected " << inherit_name << "(IntPtr baseKlass, System.Type managedType, Efl.Object parent) : base(baseKlass, managedType, parent)\n"
<< scope_tab << "{\n"
<< scope_tab << "}\n\n"
).generate(sink, attributes::unused, context);
}
template <typename OutputIterator, typename Context>
@ -730,80 +537,6 @@ struct klass
if (!has_events(cls))
return true;
std::string visibility = is_inherit_context(context) ? "protected " : "private ";
if (!helpers::has_regular_ancestor(cls))
{
// Callback registration functions
if (!as_generator(
scope_tab << "///<summary>Adds a new event handler, registering it to the native event. For internal use only.</summary>\n"
<< scope_tab << "///<param name=\"lib\">The name of the native library definining the event.</param>\n"
<< scope_tab << "///<param name=\"key\">The name of the native event.</param>\n"
<< scope_tab << "///<param name=\"evtCaller\">Delegate to be called by native code on event raising.</param>\n"
<< scope_tab << "///<param name=\"evtDelegate\">Managed delegate that will be called by evtCaller on event raising.</param>\n"
<< scope_tab << visibility << "void AddNativeEventHandler(string lib, string key, Efl.EventCb evtCaller, object evtDelegate)\n"
<< scope_tab << "{\n"
<< scope_tab << scope_tab << "IntPtr desc = Efl.EventDescription.GetNative(lib, key);\n"
<< scope_tab << scope_tab << "if (desc == IntPtr.Zero)\n"
<< scope_tab << scope_tab << "{\n"
<< scope_tab << scope_tab << scope_tab << "Eina.Log.Error($\"Failed to get native event {key}\");\n"
<< scope_tab << scope_tab << "}\n\n"
<< scope_tab << scope_tab << "if (eoEvents.ContainsKey((desc, evtDelegate)))\n"
<< scope_tab << scope_tab << "{\n"
<< scope_tab << scope_tab << scope_tab << "Eina.Log.Warning($\"Event proxy for event {key} already registered!\");\n"
<< scope_tab << scope_tab << scope_tab << "return;\n"
<< scope_tab << scope_tab << "}\n\n"
<< scope_tab << scope_tab << "IntPtr evtCallerPtr = Marshal.GetFunctionPointerForDelegate(evtCaller);\n"
<< scope_tab << scope_tab << "if (!Efl.Eo.Globals.efl_event_callback_priority_add(handle, desc, 0, evtCallerPtr, IntPtr.Zero))\n"
<< scope_tab << scope_tab << "{\n"
<< scope_tab << scope_tab << scope_tab << "Eina.Log.Error($\"Failed to add event proxy for event {key}\");\n"
<< scope_tab << scope_tab << scope_tab << "return;\n"
<< scope_tab << scope_tab << "}\n\n"
<< scope_tab << scope_tab << "eoEvents[(desc, evtDelegate)] = (evtCallerPtr, evtCaller);\n"
<< scope_tab << scope_tab << "Eina.Error.RaiseIfUnhandledException();\n"
<< scope_tab << "}\n\n"
<< scope_tab << "///<summary>Removes the given event handler for the given event. For internal use only.</summary>\n"
<< scope_tab << "///<param name=\"lib\">The name of the native library definining the event.</param>\n"
<< scope_tab << "///<param name=\"key\">The name of the native event.</param>\n"
<< scope_tab << "///<param name=\"evtDelegate\">The delegate to be removed.</param>\n"
<< scope_tab << visibility << "void RemoveNativeEventHandler(string lib, string key, object evtDelegate)\n"
<< scope_tab << "{\n"
<< scope_tab << scope_tab << "IntPtr desc = Efl.EventDescription.GetNative(lib, key);\n"
<< scope_tab << scope_tab << "if (desc == IntPtr.Zero)\n"
<< scope_tab << scope_tab << "{\n"
<< scope_tab << scope_tab << scope_tab << "Eina.Log.Error($\"Failed to get native event {key}\");\n"
<< scope_tab << scope_tab << scope_tab << "return;\n"
<< scope_tab << scope_tab << "}\n\n"
<< scope_tab << scope_tab << "var evtPair = (desc, evtDelegate);\n"
<< scope_tab << scope_tab << "if (eoEvents.TryGetValue(evtPair, out var caller))\n"
<< scope_tab << scope_tab << "{\n"
<< scope_tab << scope_tab << scope_tab << "if (!Efl.Eo.Globals.efl_event_callback_del(handle, desc, caller.evtCallerPtr, IntPtr.Zero))\n"
<< scope_tab << scope_tab << scope_tab << "{\n"
<< scope_tab << scope_tab << scope_tab << scope_tab << "Eina.Log.Error($\"Failed to remove event proxy for event {key}\");\n"
<< scope_tab << scope_tab << scope_tab << scope_tab << "return;\n"
<< scope_tab << scope_tab << scope_tab << "}\n\n"
<< scope_tab << scope_tab << scope_tab << "eoEvents.Remove(evtPair);\n"
<< scope_tab << scope_tab << scope_tab << "Eina.Error.RaiseIfUnhandledException();\n"
<< scope_tab << scope_tab << "}\n"
<< scope_tab << scope_tab << "else\n"
<< scope_tab << scope_tab << "{\n"
<< scope_tab << scope_tab << scope_tab << "Eina.Log.Error($\"Trying to remove proxy for event {key} when it is nothing registered.\");\n"
<< scope_tab << scope_tab << "}\n"
<< scope_tab << "}\n\n"
)
.generate(sink, NULL, context))
return false;
}
// Self events
if (!as_generator(*(event_definition(cls, cls))).generate(sink, cls.events, context))
return false;

View File

@ -72,8 +72,12 @@ public static class All
{
// Try to cleanup everything before actually shutting down.
Eina.Log.Debug("Calling GC before shutdown");
System.GC.Collect();
System.GC.WaitForPendingFinalizers();
for (int i = 0; i < 3; i++)
{
System.GC.Collect();
System.GC.WaitForPendingFinalizers();
Efl.App.AppMain.Iterate();
}
Monitor.Enter(InitLock);
MainLoopInitialized = false;

View File

@ -229,7 +229,7 @@ public class EflObjectElementTraits<T> : IBaseElementTraits<T>
{
if (nat != IntPtr.Zero)
{
Efl.Eo.Globals.efl_mono_thread_safe_efl_unref(nat);
Efl.Eo.Globals.efl_mono_thread_safe_efl_unref(nat);
}
}

View File

@ -0,0 +1,304 @@
using System;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Threading;
namespace Efl
{
namespace Eo
{
#if EFL_DEBUG
public class SafeIntPtr
{
private IntPtr handle;
private bool disposed;
private SafeIntPtr(IntPtr ptr)
{
handle = ptr;
}
public static implicit operator IntPtr(SafeIntPtr ptr)
{
if (ptr.disposed)
{
throw new ObjectDisposedException("Object has been disposed");
}
return ptr.handle;
}
public static implicit operator SafeIntPtr(IntPtr ptr)
{
return new SafeIntPtr(ptr);
}
public void Dispose()
{
disposed = true;
handle = IntPtr.Zero;
}
public long ToInt64()
{
return handle.ToInt64();
}
}
#endif
public abstract class EoWrapper : IWrapper, IDisposable
{
protected readonly object eventLock = new object();
protected bool inherited = false;
#if EFL_DEBUG
protected SafeIntPtr handle;
#else
protected IntPtr handle;
#endif
private static Efl.EventCb ownershipUniqueDelegate = new Efl.EventCb(OwnershipUniqueCallback);
private static Efl.EventCb ownershipSharedDelegate = new Efl.EventCb(OwnershipSharedCallback);
/// <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>
protected EoWrapper(System.IntPtr raw)
{
handle = raw;
AddWrapperSupervisor();
}
/// <summary>Initializes a new instance of the <see cref="Object"/> class.
/// Internal usage: Constructor to actually call the native library constructors. C# subclasses
/// must use the public constructor only.</summary>
/// <param name="baseKlass">The pointer to the base native Eo class.</param>
/// <param name="managedType">The managed type of the public constructor that originated this call.</param>
/// <param name="parent">The Efl.Object parent of this instance.</param>
/// <param name="file">Name of the file from where the constructor is called.</param>
/// <param name="line">Number of the line from where the constructor is called.</param>
protected EoWrapper(IntPtr baseKlass, System.Type managedType, Efl.Object parent,
[CallerFilePath] string file = null,
[CallerLineNumber] int line = 0)
{
inherited = ((object)this).GetType() != managedType;
IntPtr actual_klass = baseKlass;
if (inherited)
{
actual_klass = Efl.Eo.ClassRegister.GetInheritKlassOrRegister(baseKlass, ((object)this).GetType());
}
// Creation of the unfinalized Eo handle
Eina.Log.Debug($"Instantiating from klass 0x{actual_klass.ToInt64():x}");
System.IntPtr parent_ptr = System.IntPtr.Zero;
if (parent != null)
{
parent_ptr = parent.NativeHandle;
}
handle = Efl.Eo.Globals._efl_add_internal_start(file, line, actual_klass, parent_ptr, 1, 0);
if (handle == System.IntPtr.Zero)
{
throw new Exception("Instantiation failed");
}
Eina.Log.Debug($"Eo instance right after internal_start 0x{handle.ToInt64():x} with refcount {Efl.Eo.Globals.efl_ref_count(handle)}");
Eina.Log.Debug($"Parent was 0x{parent_ptr.ToInt64()}");
// Creation of wrapper supervisor
AddWrapperSupervisor();
}
/// <summary>Destructor.</summary>
~EoWrapper()
{
Dispose(false);
}
/// <summary>Pointer to the native instance.</summary>
public System.IntPtr NativeHandle
{
get { return handle; }
}
/// <summary>Pointer to the native class description.</summary>
public abstract System.IntPtr NativeClass
{
get;
}
/// <summary>Releases the underlying native instance.</summary>
protected virtual void Dispose(bool disposing)
{
if (disposing && handle != System.IntPtr.Zero)
{
IntPtr h = handle;
#if EFL_DEBUG
handle.Dispose();
#else
handle = IntPtr.Zero;
#endif
Efl.Eo.Globals.efl_mono_native_dispose(h);
}
else
{
Monitor.Enter(Efl.All.InitLock);
if (Efl.All.MainLoopInitialized)
{
Efl.Eo.Globals.efl_mono_thread_safe_native_dispose(handle);
}
Monitor.Exit(Efl.All.InitLock);
#if EFL_DEBUG
handle.Dispose();
#endif
}
}
/// <summary>Turns the native pointer into a string representation.</summary>
/// <returns>A string with the type and the native pointer for this object.</returns>
public override String ToString()
{
return $"{this.GetType().Name}@[0x{handle.ToInt64():x}]";
}
/// <summary>Releases the underlying native instance.</summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>Releases the underlying Eo object.
///
/// This method is a C# counterpart to the C `efl_del` function. It removes the parent of the object
/// and releases the Eo reference it was holding.
/// </summary>
public void Del()
{
// FIXME Implement this
((Efl.Object)this).SetParent(null);
Dispose();
}
/// <summary>Finishes instantiating this object.
/// Internal usage by generated code.</summary>
protected void FinishInstantiation()
{
Eina.Log.Debug("calling efl_add_internal_end");
var h = Efl.Eo.Globals._efl_add_end(handle, 1, 0);
Eina.Log.Debug($"efl_add_end returned eo 0x{handle.ToInt64():x}");
// if (h == IntPtr.Zero) // TODO
// {
// }
handle = h;
}
/// <summary>Adds a new event handler, registering it to the native event. For internal use only.</summary>
/// <param name="lib">The name of the native library definining the event.</param>
/// <param name="key">The name of the native event.</param>
/// <param name="evtCaller">Delegate to be called by native code on event raising.</param>
/// <param name="evtDelegate">Managed delegate that will be called by evtCaller on event raising.</param>
protected void AddNativeEventHandler(string lib, string key, Efl.EventCb evtCaller, object evtDelegate)
{
IntPtr desc = Efl.EventDescription.GetNative(lib, key);
if (desc == IntPtr.Zero)
{
Eina.Log.Error($"Failed to get native event {key}");
return;
}
var wsPtr = Efl.Eo.Globals.efl_mono_wrapper_supervisor_get(handle);
var ws = Efl.Eo.Globals.WrapperSupervisorPtrToManaged(wsPtr);
if (ws.EoEvents.ContainsKey((desc, evtDelegate)))
{
Eina.Log.Warning($"Event proxy for event {key} already registered!");
return;
}
IntPtr evtCallerPtr = Marshal.GetFunctionPointerForDelegate(evtCaller);
if (!Efl.Eo.Globals.efl_event_callback_priority_add(handle, desc, 0, evtCallerPtr, wsPtr))
{
Eina.Log.Error($"Failed to add event proxy for event {key}");
return;
}
ws.EoEvents[(desc, evtDelegate)] = (evtCallerPtr, evtCaller);
Eina.Error.RaiseIfUnhandledException();
}
/// <summary>Removes the given event handler for the given event. For internal use only.</summary>
/// <param name="lib">The name of the native library definining the event.</param>
/// <param name="key">The name of the native event.</param>
/// <param name="evtDelegate">The delegate to be removed.</param>
protected void RemoveNativeEventHandler(string lib, string key, object evtDelegate)
{
IntPtr desc = Efl.EventDescription.GetNative(lib, key);
if (desc == IntPtr.Zero)
{
Eina.Log.Error($"Failed to get native event {key}");
return;
}
var wsPtr = Efl.Eo.Globals.efl_mono_wrapper_supervisor_get(handle);
var ws = Efl.Eo.Globals.WrapperSupervisorPtrToManaged(wsPtr);
var evtPair = (desc, evtDelegate);
if (ws.EoEvents.TryGetValue(evtPair, out var caller))
{
if (!Efl.Eo.Globals.efl_event_callback_del(handle, desc, caller.evtCallerPtr, wsPtr))
{
Eina.Log.Error($"Failed to remove event proxy for event {key}");
return;
}
ws.EoEvents.Remove(evtPair);
Eina.Error.RaiseIfUnhandledException();
}
else
{
Eina.Log.Error($"Trying to remove proxy for event {key} when it is not registered.");
}
}
private static void OwnershipUniqueCallback(IntPtr data, ref Efl.Event.NativeStruct evt)
{
var ws = Efl.Eo.Globals.WrapperSupervisorPtrToManaged(data);
ws.MakeUnique();
}
private static void OwnershipSharedCallback(IntPtr data, ref Efl.Event.NativeStruct evt)
{
var ws = Efl.Eo.Globals.WrapperSupervisorPtrToManaged(data);
ws.MakeShared();
}
/// <sumary>Create and set to the internal native state a C# supervisor for this Eo wrapper. For internal use only.</sumary>
private void AddWrapperSupervisor()
{
var ws = new Efl.Eo.WrapperSupervisor(this);
Efl.Eo.Globals.SetWrapperSupervisor(handle, ws);
if (Efl.Eo.Globals.efl_ref_count(handle) > 1)
{
ws.MakeShared();
}
AddOwnershipEventHandlers();
}
/// <summary>Register handlers to ownership events, in order to control the object lifetime. For internal use only.</summary>
private void AddOwnershipEventHandlers()
{
AddNativeEventHandler("eo", "_EFL_EVENT_INVALIDATE", ownershipUniqueDelegate, ownershipUniqueDelegate);
AddNativeEventHandler("eo", "_EFL_EVENT_OWNERSHIP_UNIQUE", ownershipUniqueDelegate, ownershipUniqueDelegate);
AddNativeEventHandler("eo", "_EFL_EVENT_OWNERSHIP_SHARED", ownershipSharedDelegate, ownershipSharedDelegate);
Eina.Error.RaiseIfUnhandledException();
}
}
} // namespace Global
} // namespace Efl

View File

@ -0,0 +1,64 @@
using System;
using EventDictionary = System.Collections.Generic.Dictionary<(System.IntPtr desc, object evtDelegate), (System.IntPtr evtCallerPtr, Efl.EventCb evtCaller)>;
namespace Efl
{
namespace Eo
{
/// <summary>Observe the ownership state of an Eo wrapper and control its life-cycle.</summary>
public class WrapperSupervisor
{
private System.WeakReference weakRef;
#pragma warning disable CS0414
private Efl.Eo.IWrapper sharedRef;
#pragma warning restore CS0414
private EventDictionary eoEvents;
/// <summary>Create a new supervisor for the given.</summary>
/// <param name="obj">Efl object to be supervised.</param>
public WrapperSupervisor(Efl.Eo.IWrapper obj)
{
weakRef = new WeakReference(obj);
sharedRef = null;
eoEvents = new EventDictionary();
}
/// <summary>Efl object being supervised.</summary>
public Efl.Eo.IWrapper Target
{
get
{
return (Efl.Eo.IWrapper) weakRef.Target;
}
}
/// <summary>Dictionary that holds the events related with the supervised object.</summary>
public EventDictionary EoEvents
{
get
{
return eoEvents;
}
}
/// <summary>To be called when the object is uniquely owned by C#, removing its strong reference and making it available to garbage collection.</summary>
public void MakeUnique()
{
sharedRef = null;
}
/// <summary>To be called when the object is owned in the native library too, adding a strong reference to it and making it unavailable for garbage collection.</summary>
public void MakeShared()
{
if (this.Target == null)
throw new InvalidOperationException("Tried to make a null reference shared.");
sharedRef = this.Target;
}
}
}
}

View File

@ -48,6 +48,10 @@ public class Globals
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);
[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);
@ -68,11 +72,11 @@ public class Globals
[DllImport(efl.Libs.Eo)] public static extern int
efl_ref_count(IntPtr eo);
[DllImport(efl.Libs.CustomExports)] public static extern void
efl_mono_gchandle_callbacks_set(Efl.FreeGCHandleCb freeGCHandleCb, Efl.RemoveEventsCb removeEventsCb);
efl_mono_wrapper_supervisor_callbacks_set(Efl.FreeWrapperSupervisorCb freeWrapperSupervisorCb);
[DllImport(efl.Libs.CustomExports)] public static extern void
efl_mono_native_dispose(IntPtr eo, IntPtr gcHandle);
efl_mono_native_dispose(IntPtr eo);
[DllImport(efl.Libs.CustomExports)] public static extern void
efl_mono_thread_safe_native_dispose(IntPtr eo, IntPtr gcHandle);
efl_mono_thread_safe_native_dispose(IntPtr eo);
[DllImport(efl.Libs.CustomExports)] public static extern void
efl_mono_thread_safe_efl_unref(IntPtr eo);
@ -231,7 +235,7 @@ public class Globals
description.version = 2; // EO_VERSION
description.name = class_name;
description.class_type = 0; // REGULAR
description.data_size = (UIntPtr)8;
description.data_size = (UIntPtr)0;
description.class_initializer = IntPtr.Zero;
description.class_constructor = IntPtr.Zero;
description.class_destructor = IntPtr.Zero;
@ -246,6 +250,8 @@ public class Globals
IntPtr description_ptr = Eina.MemoryNative.Alloc(Marshal.SizeOf(description));
Marshal.StructureToPtr(description, description_ptr, false);
// FIXME: description_ptr seems to be leaking memory even after an eo_shutdown
var interface_list = EoG.get_efl_interfaces(type);
Eina.Log.Debug($"Going to register new class named {class_name}");
@ -442,60 +448,26 @@ public class Globals
}
}
public static IntPtr instantiate_start(IntPtr klass, Efl.Object parent,
[CallerFilePath] string file = null,
[CallerLineNumber] int line = 0)
public static Efl.Eo.WrapperSupervisor WrapperSupervisorPtrToManaged(IntPtr wsPtr)
{
Eina.Log.Debug($"Instantiating from klass 0x{klass.ToInt64():x}");
System.IntPtr parent_ptr = System.IntPtr.Zero;
if (parent != null)
{
parent_ptr = parent.NativeHandle;
}
System.IntPtr eo = Efl.Eo.Globals._efl_add_internal_start(file, line, klass, parent_ptr, 1, 0);
if (eo == System.IntPtr.Zero)
{
throw new Exception("Instantiation failed");
}
Eina.Log.Debug($"Eo instance right after internal_start 0x{eo.ToInt64():x} with refcount {Efl.Eo.Globals.efl_ref_count(eo)}");
Eina.Log.Debug($"Parent was 0x{parent_ptr.ToInt64()}");
return eo;
return (Efl.Eo.WrapperSupervisor) GCHandle.FromIntPtr(wsPtr).Target;
}
public static IntPtr instantiate_end(IntPtr eo)
public static Efl.Eo.WrapperSupervisor GetWrapperSupervisor(IntPtr eo)
{
Eina.Log.Debug("calling efl_add_internal_end");
eo = Efl.Eo.Globals._efl_add_end(eo, 1, 0);
Eina.Log.Debug($"efl_add_end returned eo 0x{eo.ToInt64():x}");
return eo;
}
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);
{
GCHandle gch = GCHandle.Alloc(obj);
EolianPD epd;
epd.pointer = GCHandle.ToIntPtr(gch);
Marshal.StructureToPtr(epd, pd, false);
}
}
public static Efl.Eo.IWrapper PrivateDataGet(IntPtr pd)
{
EolianPD epd = (EolianPD)Marshal.PtrToStructure(pd, typeof(EolianPD));
if (epd.pointer != IntPtr.Zero)
{
GCHandle gch = GCHandle.FromIntPtr(epd.pointer);
return (Efl.Eo.IWrapper)gch.Target;
}
else
var wsPtr = Efl.Eo.Globals.efl_mono_wrapper_supervisor_get(eo);
if (wsPtr == IntPtr.Zero)
{
return null;
}
return WrapperSupervisorPtrToManaged(wsPtr);
}
public static void SetWrapperSupervisor(IntPtr eo, Efl.Eo.WrapperSupervisor ws)
{
GCHandle gch = GCHandle.Alloc(ws);
Efl.Eo.Globals.efl_mono_wrapper_supervisor_set(eo, GCHandle.ToIntPtr(gch));
}
public static void free_dict_values(Dictionary<String, IntPtr> dict)
@ -601,93 +573,101 @@ public class Globals
return null;
}
IntPtr eoKlass = efl_class_get(handle);
if (eoKlass == IntPtr.Zero)
Efl.Eo.Globals.efl_ref(handle);
try
{
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)
var ws = Efl.Eo.Globals.GetWrapperSupervisor(handle);
if (ws != null && ws.Target != null)
{
instance = PrivateDataGet(pd);
if (!shouldIncRef)
{
Efl.Eo.Globals.efl_unref(handle);
}
return ws.Target;
}
return instance;
}
IntPtr eoKlass = efl_class_get(handle);
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;
}
private static Efl.FreeGCHandleCb FreeGCHandleCallbackDelegate = new Efl.FreeGCHandleCb(FreeGCHandleCallback);
public static void FreeGCHandleCallback(IntPtr gcHandlePtr)
{
try
{
GCHandle gcHandle = GCHandle.FromIntPtr(gcHandlePtr);
gcHandle.Free();
}
catch (Exception e)
{
Eina.Log.Error(e.ToString());
Eina.Error.Set(Eina.Error.UNHANDLED_EXCEPTION);
}
}
private static Efl.RemoveEventsCb RemoveEventsCallbackDelegate = new Efl.RemoveEventsCb(RemoveEventsCallback);
public static void RemoveEventsCallback(IntPtr obj, IntPtr gcHandlePtr)
{
try
{
GCHandle gcHandle = GCHandle.FromIntPtr(gcHandlePtr);
var eoEvents = gcHandle.Target as Dictionary<(IntPtr desc, object evtDelegate), (IntPtr evtCallerPtr, Efl.EventCb evtCaller)>;
if (eoEvents == null)
if (eoKlass == IntPtr.Zero)
{
Eina.Log.Error($"Invalid event dictionary [GCHandle pointer: {gcHandlePtr}]");
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}]");
}
Debug.Assert(IsGeneratedClass(managedType));
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 = (Efl.Eo.IWrapper) constructor.Invoke(new object[1] { handle });
if (ret == null)
{
throw new InvalidOperationException($"Can't construct type {managedType} from IntPtr handle");
}
if (shouldIncRef)
{
Efl.Eo.Globals.efl_ref(handle);
}
return ret;
}
finally
{
Efl.Eo.Globals.efl_unref(handle);
}
}
private static Efl.FreeWrapperSupervisorCb FreeWrapperSupervisorCallbackDelegate = new Efl.FreeWrapperSupervisorCb(FreeWrapperSupervisorCallback);
public static void FreeWrapperSupervisorCallback(IntPtr eo)
{
try
{
var wsPtr = Efl.Eo.Globals.efl_mono_wrapper_supervisor_get(eo);
if (wsPtr == IntPtr.Zero)
{
Eina.Log.Error($"Invalid wrapper supervisor [Eo pointer: {eo.ToInt64():x}]");
return;
}
foreach (var item in eoEvents)
Efl.Eo.Globals.efl_mono_wrapper_supervisor_set(eo, IntPtr.Zero);
GCHandle gch = GCHandle.FromIntPtr(wsPtr);
var ws = (Efl.Eo.WrapperSupervisor) gch.Target;
foreach (var item in ws.EoEvents)
{
if (!efl_event_callback_del(obj, item.Key.desc, item.Value.evtCallerPtr, IntPtr.Zero))
if (!efl_event_callback_del(eo, item.Key.desc, item.Value.evtCallerPtr, wsPtr))
{
Eina.Log.Error($"Failed to remove event proxy for event {item.Key.desc} [cb: {item.Value.evtCallerPtr}]");
Eina.Log.Error($"Failed to remove event proxy for event {item.Key.desc} [eo: {eo.ToInt64():x}; cb: {item.Value.evtCallerPtr.ToInt64():x}]");
}
}
// Free the native eo
Efl.Eo.Globals.efl_unref(eo);
// now the WrapperSupervisor can be collected, and so its member:
// - the event dictionary
// - and the EoWrapper if it is still pinned
gch.Free();
}
catch (Exception e)
{
@ -698,7 +678,7 @@ public class Globals
public static void SetNativeDisposeCallbacks()
{
efl_mono_gchandle_callbacks_set(FreeGCHandleCallbackDelegate, RemoveEventsCallbackDelegate);
efl_mono_wrapper_supervisor_callbacks_set(FreeWrapperSupervisorCallbackDelegate);
}
public static void ThreadSafeFreeCbExec(EinaFreeCb cbFreeCb, IntPtr cbData)
@ -800,8 +780,6 @@ public static class ClassRegister
string name = Eina.StringConversion.NativeUtf8ToManagedString(namePtr)
.Replace("_", ""); // Convert Efl C name to C# name
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))
@ -813,6 +791,7 @@ public static class ClassRegister
}
// When converting to managed, interfaces and mixins gets the 'I' prefix.
var klass_type = Efl.Eo.Globals.efl_class_type_get(klass);
if (klass_type == Efl.Eo.Globals.EflClassType.Interface || klass_type == Efl.Eo.Globals.EflClassType.Mixin)
{
var pos = name.LastIndexOf(".");

View File

@ -2,7 +2,9 @@ mono_files += files(
'iwrapper.cs',
'workaround.cs',
'FunctionWrapper.cs',
'NativeModule.cs'
'NativeModule.cs',
'EoWrapper.cs',
'WrapperSupervisor.cs'
)
if host_machine.system() == 'windows'

View File

@ -44,12 +44,6 @@ public struct Efl_Object_Ops
public UIntPtr count;
};
[StructLayout(LayoutKind.Sequential)]
public struct EolianPD
{
public IntPtr pointer;
}
#pragma warning disable 0169
public struct EvasObjectBoxLayout
@ -115,8 +109,7 @@ public struct EventDescription
};
public delegate void EventCb(System.IntPtr data, ref Event.NativeStruct evt);
public delegate void FreeGCHandleCb(System.IntPtr gcHandle);
public delegate void RemoveEventsCb(System.IntPtr obj, System.IntPtr gcHandle);
public delegate void FreeWrapperSupervisorCb(System.IntPtr obj);
[StructLayout(LayoutKind.Sequential)]
public struct TextCursorCursor

View File

@ -148,6 +148,10 @@ if get_option('mono-beta')
extra_cs_args += '-d:EFL_BETA'
endif
if get_option('buildtype') == 'debug'
extra_cs_args += '-d:EFL_DEBUG'
endif
efl_mono_install_dir = join_paths(dir_lib, 'efl-mono-'+version_major)
efl_mono_xml_doc = join_paths(meson.current_build_dir(), 'efl_mono.xml')

View File

@ -23,44 +23,39 @@
# endif
#endif /* ! _WIN32 */
typedef void (*Efl_Mono_Free_GCHandle_Cb)(void *gchandle);
typedef void (*Efl_Mono_Remove_Events_Cb)(Eo *obj, void *gchandle);
static Efl_Mono_Free_GCHandle_Cb _efl_mono_free_gchandle_call = NULL;
static Efl_Mono_Remove_Events_Cb _efl_mono_remove_events_call = NULL;
EAPI void efl_mono_gchandle_callbacks_set(Efl_Mono_Free_GCHandle_Cb free_gchandle_cb, Efl_Mono_Remove_Events_Cb remove_events_cb)
EAPI const char *efl_mono_wrapper_supervisor_key_get()
{
_efl_mono_free_gchandle_call = free_gchandle_cb;
_efl_mono_remove_events_call = remove_events_cb;
return "__c#_wrapper_supervisor";
}
EAPI void efl_mono_native_dispose(Eo *obj, void* gchandle)
EAPI void *efl_mono_wrapper_supervisor_get(Eo *eo)
{
if (gchandle) _efl_mono_remove_events_call(obj, gchandle);
efl_unref(obj);
if (gchandle) _efl_mono_free_gchandle_call(gchandle);
return efl_key_data_get(eo, efl_mono_wrapper_supervisor_key_get());
}
typedef struct _Efl_Mono_Native_Dispose_Data
EAPI void efl_mono_wrapper_supervisor_set(Eo *eo, void *ws)
{
Eo *obj;
void *gchandle;
} Efl_Mono_Native_Dispose_Data;
static void _efl_mono_native_dispose_cb(void *data)
{
Efl_Mono_Native_Dispose_Data *dd = data;
efl_mono_native_dispose(dd->obj, dd->gchandle);
free(dd);
efl_key_data_set(eo, efl_mono_wrapper_supervisor_key_get(), ws);
}
EAPI void efl_mono_thread_safe_native_dispose(Eo *obj, void* gchandle)
typedef void (*Efl_Mono_Free_Wrapper_Supervisor_Cb)(Eo *obj);
static Efl_Mono_Free_Wrapper_Supervisor_Cb _efl_mono_free_wrapper_supervisor_call = NULL;
EAPI void efl_mono_wrapper_supervisor_callbacks_set(Efl_Mono_Free_Wrapper_Supervisor_Cb free_wrapper_supervisor_cb)
{
Efl_Mono_Native_Dispose_Data *dd = malloc(sizeof(Efl_Mono_Native_Dispose_Data));
dd->obj = obj;
dd->gchandle = gchandle;
ecore_main_loop_thread_safe_call_async(_efl_mono_native_dispose_cb, dd);
_efl_mono_free_wrapper_supervisor_call = free_wrapper_supervisor_cb;
}
EAPI void efl_mono_native_dispose(Eo *obj)
{
_efl_mono_free_wrapper_supervisor_call(obj);
}
EAPI void efl_mono_thread_safe_native_dispose(Eo *obj)
{
ecore_main_loop_thread_safe_call_async((Ecore_Cb)efl_mono_native_dispose, obj);
}
static void _efl_mono_unref_cb(void *obj)

View File

@ -411,6 +411,11 @@ abstract Efl.Object
del @hot: void; [[Object is being deleted. See @.destructor.]]
invalidate @hot: void; [[Object is being invalidated and losing its parent. See @.invalidate.]]
noref @hot: void; [[Object has lost its last reference, only parent relationship is keeping it alive. Advanced usage.]]
ownership,unique @hot: void; [[Object has lost a reference and only one is left. It has just one owner now.
Triggered whenever the refcount goes from two to one.]]
ownership,shared @hot: void; [[Object has acquired a second reference. It has multiple owners now.
Triggered whenever increasing the refcount from one to two,
it will not trigger by further increasing the refcount beyond two.]]
destruct @hot: void; [[Object has been fully destroyed. It can not be used
beyond this point. This event should only serve to clean up any
reference you keep to the object.]]

View File

@ -1933,6 +1933,9 @@ efl_ref(const Eo *obj_id)
++(obj->user_refcount);
if (EINA_UNLIKELY(obj->user_refcount == 1))
_efl_ref(obj);
else if (EINA_UNLIKELY(obj->ownership_track && obj->user_refcount == 2))
efl_event_callback_call((Eo *) obj_id, EFL_EVENT_OWNERSHIP_SHARED, NULL);
#ifdef EO_DEBUG
_eo_log_obj_ref_op(obj, EO_REF_OP_REF);
#endif
@ -1991,6 +1994,10 @@ efl_unref(const Eo *obj_id)
}
_efl_unref(obj);
}
else if (EINA_UNLIKELY(obj->ownership_track && obj->user_refcount == 1))
{
efl_event_callback_call((Eo *) obj_id, EFL_EVENT_OWNERSHIP_UNIQUE, NULL);
}
_apply_auto_unref(obj, obj_id);

View File

@ -1179,6 +1179,12 @@ _special_event_count_inc(Eo *obj_id, Efl_Object_Data *pd, const Efl_Callback_Arr
}
else if (it->desc == EFL_EVENT_DESTRUCT)
pd->has_destroyed_event_cb = EINA_TRUE;
else if (it->desc == EFL_EVENT_OWNERSHIP_SHARED || it->desc == EFL_EVENT_OWNERSHIP_UNIQUE)
{
EO_OBJ_POINTER_RETURN(obj_id, obj);
obj->ownership_track = EINA_TRUE;
EO_OBJ_DONE(obj_id);
}
}
static inline void

View File

@ -126,6 +126,7 @@ struct _Eo_Object
Eina_Bool destructed:1;
Eina_Bool manual_free:1;
unsigned char auto_unref : 1; // unref after 1 call - hack for parts
Eina_Bool ownership_track:1;
};
/* How we search and store the implementations in classes. */

View File

@ -122,8 +122,8 @@ enum class variable_type
struct type_def;
bool operator==(type_def const& rhs, type_def const& lhs);
bool operator!=(type_def const& rhs, type_def const& lhs);
bool operator==(type_def const& lhs, type_def const& rhs);
bool operator!=(type_def const& lhs, type_def const& rhs);
enum class class_type
{
@ -377,8 +377,8 @@ struct type_def
bool is_beta;
type_def() = default;
type_def(variant_type original_type, std::string c_type, bool has_own)
: original_type(original_type), c_type(c_type), has_own(has_own) {}
type_def(variant_type original_type, std::string c_type, bool has_own, bool is_ptr, bool is_beta)
: original_type(original_type), c_type(c_type), has_own(has_own), is_ptr(is_ptr), is_beta(is_beta) {}
type_def(Eolian_Type const* eolian_type, Eolian_Unit const* unit, Eolian_C_Type_Type ctype)
{
@ -422,7 +422,7 @@ inline bool operator!=(type_def const& lhs, type_def const& rhs)
return !(lhs == rhs);
}
type_def const void_ {attributes::regular_type_def{"void", {qualifier_info::is_none, {}}, {}}, "void", false};
type_def const void_ {attributes::regular_type_def{"void", {qualifier_info::is_none, {}}, {}}, "void", false, false, false};
inline void type_def::set(Eolian_Type const* eolian_type, Eolian_Unit const* unit, Eolian_C_Type_Type ctype)
{
@ -699,6 +699,7 @@ struct function_def
function_type _type,
bool _is_beta = false,
bool _is_protected = false,
bool _is_static = false,
Eolian_Unit const* unit = nullptr)
: klass(_klass), return_type(_return_type), name(_name),
parameters(_parameters), c_name(_c_name), filename(_filename),
@ -708,6 +709,7 @@ struct function_def
property_documentation(_property_documentation),
type(_type),
is_beta(_is_beta), is_protected(_is_protected),
is_static(_is_static),
unit(unit) {}
function_def( ::Eolian_Function const* function, Eolian_Function_Type type, Eolian_Typedecl const* tp, Eolian_Unit const* unit)
@ -1191,7 +1193,7 @@ struct klass_def
{
return lhs.eolian_name == rhs.eolian_name
&& lhs.cxx_name == rhs.cxx_name
&& lhs.filename == lhs.filename
&& lhs.filename == rhs.filename
&& lhs.namespaces == rhs.namespaces
&& lhs.functions == rhs.functions
&& lhs.properties == rhs.properties
@ -1223,7 +1225,8 @@ struct klass_def
, class_type type
, std::set<klass_name, compare_klass_name_by_name> immediate_inherits
, std::string klass_get_name
, bool is_beta)
, bool is_beta
, Eolian_Unit const* unit)
: eolian_name(eolian_name), cxx_name(cxx_name), filename(filename)
, documentation(documentation)
, namespaces(namespaces)
@ -1231,6 +1234,7 @@ struct klass_def
, immediate_inherits(immediate_inherits)
, klass_get_name(klass_get_name)
, is_beta(is_beta)
, unit(unit)
{}
klass_def(std::string _eolian_name, std::string _cxx_name
, std::vector<std::string> _namespaces

View File

@ -110,6 +110,20 @@ class TestEoInherit
Efl.Object loop = new MyObject();
Test.Assert(loop.NativeHandle != System.IntPtr.Zero);
}
private static WeakReference CreateCollectableInherited()
{
var obj = new MyObject();
return new WeakReference(obj);
}
public static void inherited_collected()
{
var wref = CreateCollectableInherited();
Test.CollectAndIterate();
Test.AssertNull(wref.Target);
}
}
class TestEoNames
@ -496,4 +510,23 @@ class TestProvider
}
}
class TestObjectDeletion
{
public static void test_object_deletion()
{
var obj = new Dummy.PartHolder();
var part = obj.OnePart;
Test.AssertNotNull(part);
part.Del();
Test.AssertNull(obj.OnePart);
#if EFL_DEBUG
Test.AssertRaises<ObjectDisposedException>(() => part.SetParent(null));
#endif
}
}
}

View File

@ -274,4 +274,57 @@ class TestEventNaming
}
}
class TestEventWithDeadWrappers
{
private static WeakReference AttachToManager(Dummy.EventManager manager,
EventHandler<Dummy.TestObjectEvtWithIntEvt_Args> cb)
{
var obj = new Dummy.TestObject();
manager.Emitter = obj;
obj.EvtWithIntEvt += cb;
return new WeakReference(obj);
}
public static void test_event_from_c_owned_wrapper()
{
// Set upon object instantiation
WeakReference wref = null;
// Checks in the callback called
bool callbackCalled = false;
int received = -1;
// attach to evt with int
EventHandler<Dummy.TestObjectEvtWithIntEvt_Args> cb = (object sender, Dummy.TestObjectEvtWithIntEvt_Args args) => {
callbackCalled = true;
received = args.arg;
Test.Assert(Object.ReferenceEquals(sender, wref.Target));
};
Dummy.EventManager manager = new Dummy.EventManager();
wref = AttachToManager(manager, cb);
Test.CollectAndIterate();
manager.EmitWithInt(42);
Test.CollectAndIterate();
Test.Assert(callbackCalled, "Callback must have been called.");
Test.AssertEquals(42, received, "Wrong value received.");
// Cleanup checks
manager.Release();
// Make sure the released wrapper is collected and release the Eo object
Test.CollectAndIterate();
Test.AssertNull(wref.Target);
}
}
}

View File

@ -35,6 +35,46 @@ class TestInheritance
}
}
internal class Inherit3Parent : Dummy.TestObject
{
public bool disposed = false;
public bool childDisposed = false;
~Inherit3Parent()
{
Console.WriteLine("finalizer called for parent");
}
protected override void Dispose(bool disposing)
{
Console.WriteLine("Dispose parent");
base.Dispose(disposing);
}
}
internal class Inherit3Child : Dummy.TestObject
{
Inherit3Parent parent;
public Inherit3Child(Inherit3Parent parent) : base(parent)
{
// WARNING: Uncommenting the line below causes the parent-child cycle to leak.
// The GC won't be able to collect it.
// this.parent = parent;
}
~Inherit3Child()
{
Console.WriteLine("finalizer called for child");
}
protected override void Dispose(bool disposing)
{
/* parent.childDisposed = true; */
Console.WriteLine("Dispose parent");
base.Dispose(disposing);
}
}
public static void test_inherit_from_regular_class()
{
var obj = new Inherit1();
@ -50,6 +90,55 @@ class TestInheritance
string s = Dummy.InheritHelper.ReceiveDummyAndCallInStringshare(obj);
Test.AssertEquals ("Hello World", s);
}
private static void CreateAndCheckInheritedObjects(out WeakReference parentWRef, out WeakReference childWRef)
{
var parent = new Inherit3Parent();
var child = new Inherit3Child(parent);
parentWRef = new WeakReference(parent);
childWRef = new WeakReference(child);
Console.WriteLine($"Parent [{parent.ToString()}] has {Efl.Eo.Globals.efl_ref_count(parent.NativeHandle)} refs");
Console.WriteLine($"Child [{child.ToString()}] has {Efl.Eo.Globals.efl_ref_count(child.NativeHandle)} refs");
child = null;
System.GC.Collect(System.GC.MaxGeneration, GCCollectionMode.Forced, true, true);
System.GC.WaitForPendingFinalizers();
Efl.App.AppMain.Iterate();
child = (Inherit3Child) childWRef.Target;
Test.AssertNotNull(parent);
Test.AssertNotNull(child);
Test.AssertEquals(false, parent.disposed);
Test.AssertEquals(false, parent.childDisposed);
Console.WriteLine($"Parent [{parent.ToString()}] has {Efl.Eo.Globals.efl_ref_count(parent.NativeHandle)} refs");
Console.WriteLine($"Child [{child.ToString()}] has {Efl.Eo.Globals.efl_ref_count(child.NativeHandle)} refs");
parent = null;
child = null;
}
public static void test_inherit_lifetime()
{
WeakReference parentWRef;
WeakReference childWRef;
CreateAndCheckInheritedObjects(out parentWRef, out childWRef);
// Two invocations to iterate a the child wasn't being released with a single one
Test.CollectAndIterate();
Test.CollectAndIterate();
var parent = (Dummy.TestObject) parentWRef.Target;
var child = (Dummy.TestObject) childWRef.Target;
Test.AssertNull(parent);
Test.AssertNull(child);
}
}
}

View File

@ -42,6 +42,9 @@ class TestMain
if (localTestCase == setUp || localTestCase == tearDown)
continue;
// Cleanup garbage collector and job queue
Test.CollectAndIterate(1);
Console.WriteLine("[ RUN ] " + testCase.Name + "." + localTestCase.Name);
bool caseResult = true;

View File

@ -43,13 +43,14 @@ public static class Test
[CallerFilePath] string file = null,
[CallerMemberName] string member = null)
{
if (file == null)
file = "(unknown file)";
if (member == null)
member = "(unknown member)";
if (expected == null)
throw new AssertionException($"{file}:{line} ({member}) Null expected value. Use AssertNull.");
if (!expected.Equals(actual)) {
if (expected == null && actual == null)
return;
if (expected == null || !expected.Equals(actual))
{
if (file == null)
file = "(unknown file)";
if (member == null)
member = "(unknown member)";
if (msg == null || msg.Length == 0)
msg = $"Expected \"{expected}\", actual \"{actual}\"";
throw new AssertionException($"{file}:{line} ({member}) {msg}");
@ -62,13 +63,12 @@ public static class Test
[CallerFilePath] string file = null,
[CallerMemberName] string member = null)
{
if (file == null)
file = "(unknown file)";
if (member == null)
member = "(unknown member)";
if (expected == null)
throw new AssertionException($"{file}:{line} ({member}) Null expected value. Use AssertNull.");
if (expected.Equals(actual)) {
if (expected == null ? actual == null : expected.Equals(actual))
{
if (file == null)
file = "(unknown file)";
if (member == null)
member = "(unknown member)";
if (msg == null || msg.Length == 0)
msg = $"Expected \"{expected}\" shouldn't be equal to actual \"{actual}\"";
throw new AssertionException($"{file}:{line} ({member}) {msg}");
@ -196,6 +196,20 @@ public static class Test
if (reference == null)
throw new AssertionException($"Assertion failed: {file}:{line} ({member}) {msg}");
}
/// <summary>Runs a number of garbage collections and iterate the main loop.
/// The iteration is needed to make sure objects collected in the GC thread
/// are efl_unref'd in the main thread.</summary>
public static void CollectAndIterate(int iterations=1000)
{
for (int i = 0; i < iterations; i++)
{
System.GC.Collect();
}
System.GC.WaitForPendingFinalizers();
Efl.App.AppMain.Iterate();
}
}

View File

@ -0,0 +1,55 @@
#include "libefl_mono_native_test.h"
typedef struct Dummy_Event_Manager_Data
{
Eo* emitter;
} Dummy_Event_Manager_Data;
static Efl_Object*
_dummy_event_manager_efl_object_constructor(Eo *obj, EINA_UNUSED Dummy_Event_Manager_Data *pd)
{
efl_constructor(efl_super(obj, DUMMY_EVENT_MANAGER_CLASS));
return obj;
}
static void
_dummy_event_manager_efl_object_destructor(Eo *obj, Dummy_Event_Manager_Data *pd)
{
if (pd->emitter != 0)
efl_unref(pd->emitter);
efl_destructor(efl_super(obj, DUMMY_EVENT_MANAGER_CLASS));
}
static void
_dummy_event_manager_emitter_set(EINA_UNUSED Eo *obj, Dummy_Event_Manager_Data *pd, Eo *emitter)
{
pd->emitter = emitter;
}
static Eina_Bool
_dummy_event_manager_emit_with_int(EINA_UNUSED Eo *obj, Dummy_Event_Manager_Data *pd, int data)
{
if (pd->emitter)
efl_event_callback_call(pd->emitter, DUMMY_TEST_OBJECT_EVENT_EVT_WITH_INT, &data);
else
{
EINA_LOG_ERR("Trying to emit event without an emitter.");
return EINA_FALSE;
}
return EINA_TRUE;
}
static void
_dummy_event_manager_release(EINA_UNUSED Eo *obj, Dummy_Event_Manager_Data *pd)
{
if (!pd->emitter)
return;
efl_unref(pd->emitter);
}
#include "dummy_event_manager.eo.c"

View File

@ -0,0 +1,29 @@
import eina_types;
class @beta Dummy.Event_Manager extends Efl.Object {
methods {
@property emitter {
set {
}
values {
emitter: Efl.Object @owned;
}
}
emit_with_int {
params {
data: int;
}
return: bool;
}
release {
}
}
implements {
Efl.Object.constructor;
Efl.Object.destructor;
}
}

View File

@ -6,6 +6,16 @@ typedef struct Dummy_Part_Holder_Data
Eo *two;
} Dummy_Part_Holder_Data;
void part_deleted_cb(void *data, const Efl_Event *evt)
{
Dummy_Part_Holder_Data *pd = data;
if (evt->object == pd->one)
pd->one = NULL;
else if (evt->object == pd->two)
pd->two = NULL;
}
// Part holder
static Efl_Object*
_dummy_part_holder_efl_object_constructor(Eo *obj, Dummy_Part_Holder_Data *pd)
@ -16,12 +26,25 @@ _dummy_part_holder_efl_object_constructor(Eo *obj, Dummy_Part_Holder_Data *pd)
if (!efl_parent_get(obj))
{
pd->one = efl_add(DUMMY_TEST_OBJECT_CLASS, obj, efl_name_set(efl_added, "part_one"));
efl_event_callback_add(pd->one, EFL_EVENT_DEL, part_deleted_cb, pd);
pd->two = efl_add(DUMMY_TEST_OBJECT_CLASS, obj, efl_name_set(efl_added, "part_two"));
efl_event_callback_add(pd->two, EFL_EVENT_DEL, part_deleted_cb, pd);
}
return obj;
}
static void
_dummy_part_holder_efl_object_destructor(EINA_UNUSED Eo* obj, Dummy_Part_Holder_Data *pd)
{
if (pd->one)
efl_parent_set(pd->one, NULL);
if (pd->two)
efl_parent_set(pd->two, NULL);
}
Efl_Object *_dummy_part_holder_efl_part_part_get(EINA_UNUSED const Eo *obj, Dummy_Part_Holder_Data *pd, const char *name)
{
if (!strcmp(name, "one"))

View File

@ -9,5 +9,6 @@ class @beta Dummy.Part_Holder extends Dummy.Test_Object implements Efl.Part {
implements {
Efl.Part.part_get;
Efl.Object.constructor;
Efl.Object.destructor;
}
}

View File

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

View File

@ -1,4 +1,13 @@
eo_files = ['dummy_child.eo', 'dummy_numberwrapper.eo', 'dummy_test_object.eo', 'dummy_test_iface.eo', 'dummy_inherit_helper.eo', 'dummy_inherit_iface.eo', 'dummy_part_holder.eo']
eo_files = [
'dummy_child.eo',
'dummy_numberwrapper.eo',
'dummy_test_object.eo',
'dummy_test_iface.eo',
'dummy_inherit_helper.eo',
'dummy_inherit_iface.eo',
'dummy_part_holder.eo',
'dummy_event_manager.eo',
]
eo_file_targets = []
@ -23,6 +32,7 @@ efl_mono_native_test = library('efl_mono_native_test',
'dummy_numberwrapper.c',
'dummy_part_holder.c',
'dummy_test_object.c',
'dummy_event_manager.c',
],
dependencies : [ecore, eo, efl],
)
@ -100,7 +110,7 @@ custom_target('copy_efl_mono_lib_dll',
endif
config_libs = ['eina', 'ecore', 'eo', 'efl', 'evas', 'eldbus', 'elementary']
load_lib = ''
load_lib = efl_mono_test_suite_path + ':'
foreach config : config_libs
lib = get_variable(config+'_lib')

View File

@ -162,6 +162,238 @@ EFL_START_TEST(eo_test_unref_noref)
}
EFL_END_TEST
typedef struct {
int shared, unique, invalidate;
} OwnershipEventsCounter;
static void
_ownership_shared_event(void *data, const Efl_Event *ev EINA_UNUSED)
{
OwnershipEventsCounter *counter = data;
++(counter->shared);
}
static void
_ownership_unique_event(void *data, const Efl_Event *ev EINA_UNUSED)
{
OwnershipEventsCounter *counter = data;
++(counter->unique);
}
static void
_invalidate_ownership_event(void *data, const Efl_Event *ev EINA_UNUSED)
{
OwnershipEventsCounter *counter = data;
++(counter->invalidate);
}
EFL_START_TEST(eo_test_ownership_events)
{
OwnershipEventsCounter counter = {0,};
Eo *obj = efl_add_ref(SIMPLE_CLASS, NULL);
efl_event_callback_add(obj, EFL_EVENT_OWNERSHIP_SHARED, _ownership_shared_event, &counter);
efl_event_callback_add(obj, EFL_EVENT_OWNERSHIP_UNIQUE, _ownership_unique_event, &counter);
efl_event_callback_add(obj, EFL_EVENT_INVALIDATE, _invalidate_ownership_event, &counter);
efl_ref(obj);
ck_assert_int_eq(counter.shared, 1);
ck_assert_int_eq(counter.unique, 0);
efl_unref(obj);
ck_assert_int_eq(counter.shared, 1);
ck_assert_int_eq(counter.unique, 1);
efl_ref(obj);
ck_assert_int_eq(counter.shared, 2);
ck_assert_int_eq(counter.unique, 1);
efl_ref(obj);
ck_assert_int_eq(counter.shared, 2);
ck_assert_int_eq(counter.unique, 1);
efl_ref(obj);
ck_assert_int_eq(counter.shared, 2);
ck_assert_int_eq(counter.unique, 1);
efl_unref(obj);
ck_assert_int_eq(counter.shared, 2);
ck_assert_int_eq(counter.unique, 1);
efl_unref(obj);
ck_assert_int_eq(counter.shared, 2);
ck_assert_int_eq(counter.unique, 1);
efl_unref(obj);
ck_assert_int_eq(counter.shared, 2);
ck_assert_int_eq(counter.unique, 2);
ck_assert_int_eq(counter.invalidate, 0);
efl_unref(obj);
ck_assert_int_eq(counter.shared, 2);
ck_assert_int_eq(counter.unique, 2);
ck_assert_int_eq(counter.invalidate, 1);
}
EFL_END_TEST
EFL_START_TEST(eo_test_ownership_events_with_parent)
{
OwnershipEventsCounter counter = {0,};
Eo *par = efl_add_ref(SIMPLE_CLASS, NULL);
Eo *obj = efl_add(SIMPLE_CLASS, par);
efl_event_callback_add(obj, EFL_EVENT_OWNERSHIP_SHARED, _ownership_shared_event, &counter);
efl_event_callback_add(obj, EFL_EVENT_OWNERSHIP_UNIQUE, _ownership_unique_event, &counter);
efl_event_callback_add(obj, EFL_EVENT_INVALIDATE, _invalidate_ownership_event, &counter);
efl_ref(obj);
ck_assert_int_eq(counter.shared, 1);
ck_assert_int_eq(counter.unique, 0);
efl_unref(obj);
ck_assert_int_eq(counter.shared, 1);
ck_assert_int_eq(counter.unique, 1);
efl_ref(obj);
ck_assert_int_eq(counter.shared, 2);
ck_assert_int_eq(counter.unique, 1);
efl_ref(obj);
ck_assert_int_eq(counter.shared, 2);
ck_assert_int_eq(counter.unique, 1);
efl_unref(obj);
ck_assert_int_eq(counter.shared, 2);
ck_assert_int_eq(counter.unique, 1);
efl_unref(obj);
ck_assert_int_eq(counter.shared, 2);
ck_assert_int_eq(counter.unique, 2);
ck_assert_int_eq(counter.invalidate, 0);
efl_del(obj);
ck_assert_int_eq(counter.shared, 2);
ck_assert_int_eq(counter.unique, 2);
ck_assert_int_eq(counter.invalidate, 1);
efl_unref(par);
ck_assert_int_eq(counter.shared, 2);
ck_assert_int_eq(counter.unique, 2);
}
EFL_END_TEST
EFL_START_TEST(eo_test_ownership_events_with_parent_invalidate)
{
OwnershipEventsCounter counter = {0,};
Eo *par = efl_add_ref(SIMPLE_CLASS, NULL);
Eo *obj = efl_add(SIMPLE_CLASS, par);
efl_event_callback_add(obj, EFL_EVENT_OWNERSHIP_SHARED, _ownership_shared_event, &counter);
efl_event_callback_add(obj, EFL_EVENT_OWNERSHIP_UNIQUE, _ownership_unique_event, &counter);
efl_event_callback_add(obj, EFL_EVENT_INVALIDATE, _invalidate_ownership_event, &counter);
/* Kill parent */
efl_unref(par);
ck_assert_int_eq(counter.shared, 0);
ck_assert_int_eq(counter.unique, 0);
ck_assert_int_eq(counter.invalidate, 1);
}
EFL_END_TEST
EFL_START_TEST(eo_test_ownership_events_with_parent_invalidate2)
{
OwnershipEventsCounter counter = {0,};
Eo *par = efl_add_ref(SIMPLE_CLASS, NULL);
Eo *obj = efl_add(SIMPLE_CLASS, par);
efl_event_callback_add(obj, EFL_EVENT_OWNERSHIP_SHARED, _ownership_shared_event, &counter);
efl_event_callback_add(obj, EFL_EVENT_OWNERSHIP_UNIQUE, _ownership_unique_event, &counter);
efl_event_callback_add(obj, EFL_EVENT_INVALIDATE, _invalidate_ownership_event, &counter);
efl_ref(obj);
ck_assert_int_eq(counter.shared, 1);
ck_assert_int_eq(counter.unique, 0);
ck_assert_int_eq(counter.invalidate, 0);
/* Kill parent */
efl_unref(par);
ck_assert_int_eq(counter.shared, 1);
ck_assert_int_eq(counter.unique, 1);
ck_assert_int_eq(counter.invalidate, 1);
efl_unref(obj);
ck_assert_int_eq(counter.shared, 1);
ck_assert_int_eq(counter.unique, 1);
ck_assert_int_eq(counter.invalidate, 1);
}
EFL_END_TEST
EFL_START_TEST(eo_test_ownership_events_with_parent_invalidate3)
{
OwnershipEventsCounter counter = {0,};
Eo *par = efl_add_ref(SIMPLE_CLASS, NULL);
Eo *obj = efl_add(SIMPLE_CLASS, par);
efl_event_callback_add(obj, EFL_EVENT_OWNERSHIP_SHARED, _ownership_shared_event, &counter);
efl_event_callback_add(obj, EFL_EVENT_OWNERSHIP_UNIQUE, _ownership_unique_event, &counter);
efl_event_callback_add(obj, EFL_EVENT_INVALIDATE, _invalidate_ownership_event, &counter);
efl_ref(obj);
ck_assert_int_eq(counter.shared, 1);
ck_assert_int_eq(counter.unique, 0);
efl_unref(obj);
ck_assert_int_eq(counter.shared, 1);
ck_assert_int_eq(counter.unique, 1);
efl_ref(obj);
ck_assert_int_eq(counter.shared, 2);
ck_assert_int_eq(counter.unique, 1);
efl_ref(obj);
ck_assert_int_eq(counter.shared, 2);
ck_assert_int_eq(counter.unique, 1);
ck_assert_int_eq(counter.invalidate, 0);
/* Kill parent */
efl_unref(par);
ck_assert_int_eq(counter.shared, 2);
ck_assert_int_eq(counter.unique, 1);
ck_assert_int_eq(counter.invalidate, 1);
}
EFL_END_TEST
EFL_START_TEST(eo_test_ownership_events_self_invalidate)
{
OwnershipEventsCounter counter = {0,};
Eo *par = efl_add_ref(SIMPLE_CLASS, NULL);
Eo *obj = efl_add(SIMPLE_CLASS, par);
efl_event_callback_add(obj, EFL_EVENT_OWNERSHIP_SHARED, _ownership_shared_event, &counter);
efl_event_callback_add(obj, EFL_EVENT_OWNERSHIP_UNIQUE, _ownership_unique_event, &counter);
efl_event_callback_add(obj, EFL_EVENT_INVALIDATE, _invalidate_ownership_event, &counter);
ck_assert_int_eq(counter.shared, 0);
ck_assert_int_eq(counter.unique, 0);
ck_assert_int_eq(counter.invalidate, 0);
efl_ref(obj);
ck_assert_int_eq(counter.shared, 1);
ck_assert_int_eq(counter.unique, 0);
ck_assert_int_eq(counter.invalidate, 0);
efl_del(obj);
ck_assert_int_eq(counter.shared, 1);
ck_assert_int_eq(counter.unique, 1);
ck_assert_int_eq(counter.invalidate, 1);
/* Kill parent */
efl_unref(par);
ck_assert_int_eq(counter.shared, 1);
ck_assert_int_eq(counter.unique, 1);
ck_assert_int_eq(counter.invalidate, 1);
efl_unref(obj);
ck_assert_int_eq(counter.shared, 1);
ck_assert_int_eq(counter.unique, 1);
ck_assert_int_eq(counter.invalidate, 1);
}
EFL_END_TEST
typedef struct {
Eo *par;
Eina_Bool called;
@ -216,6 +448,12 @@ void eo_test_lifecycle(TCase *tc)
tcase_add_test(tc, eo_test_shutdown_eventting);
tcase_add_test(tc, eo_test_del_in_noref);
tcase_add_test(tc, eo_test_unref_noref);
tcase_add_test(tc, eo_test_ownership_events);
tcase_add_test(tc, eo_test_ownership_events_with_parent);
tcase_add_test(tc, eo_test_ownership_events_with_parent_invalidate);
tcase_add_test(tc, eo_test_ownership_events_with_parent_invalidate2);
tcase_add_test(tc, eo_test_ownership_events_with_parent_invalidate3);
tcase_add_test(tc, eo_test_ownership_events_self_invalidate);
tcase_add_test(tc, eo_test_invalidating_get);
tcase_add_test(tc, eo_test_alive_get);
}