forked from enlightenment/efl
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. Depends on D8678 Test Plan: `ninja test` and `make test` Reviewers: lauromoura, felipealmeida, woohyun, segfaultxavi Reviewed By: lauromoura Subscribers: cedric, #reviewers, #committers Tags: #efl Differential Revision: https://phab.enlightenment.org/D9014devs/vitorsousa/constructible_eflsharp
parent
937da0b12c
commit
fcf5f1d2e2
16 changed files with 599 additions and 451 deletions
@ -0,0 +1,253 @@ |
||||
using System; |
||||
using System.Runtime.InteropServices; |
||||
using System.Runtime.CompilerServices; |
||||
using System.Threading; |
||||
|
||||
namespace Efl |
||||
{ |
||||
|
||||
namespace Eo |
||||
{ |
||||
|
||||
public abstract class EoWrapper : IWrapper, IDisposable |
||||
{ |
||||
protected readonly object eventLock = new object(); |
||||
protected bool inherited = false; |
||||
protected System.IntPtr handle = IntPtr.Zero; |
||||
|
||||
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; |
||||
handle = IntPtr.Zero; |
||||
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); |
||||
} |
||||
} |
||||
|
||||
/// <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{(UInt64)handle: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 |
@ -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; |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|