From 969f2372231e62dc201207c9301a37a8f07be41f Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 10 Feb 2021 17:26:30 +0100 Subject: [PATCH 1/2] Refactor ownership in .NET Framework loader --- netfx_loader/ClrLoader.cs | 74 ++++++++++++++++++++++---------------- netfx_loader/DomainData.cs | 61 +++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 30 deletions(-) create mode 100644 netfx_loader/DomainData.cs diff --git a/netfx_loader/ClrLoader.cs b/netfx_loader/ClrLoader.cs index 2ba4e74..e8b2767 100644 --- a/netfx_loader/ClrLoader.cs +++ b/netfx_loader/ClrLoader.cs @@ -1,8 +1,6 @@ using System; -using System.Diagnostics; -using System.Globalization; +using System.Collections.Generic; using System.IO; -using System.Reflection; using System.Runtime.InteropServices; using NXPorts.Attributes; @@ -10,7 +8,18 @@ namespace ClrLoader { public static class ClrLoader { - delegate int EntryPoint(IntPtr buffer, int size); + static bool _initialized = false; + static List _domains = new List(); + + [DllExport("pyclr_initialize", CallingConvention.Cdecl)] + public static void Initialize() + { + if (!_initialized) + { + _domains.Add(new DomainData(AppDomain.CurrentDomain)); + _initialized = true; + } + } [DllExport("pyclr_create_appdomain", CallingConvention.Cdecl)] public static IntPtr CreateAppDomain( @@ -29,11 +38,9 @@ public static IntPtr CreateAppDomain( Print($"Located domain {domain}"); - var handle = GCHandle.Alloc(domain, GCHandleType.Pinned); - - Print($"Created handle {handle}"); - - return handle.AddrOfPinnedObject(); + var domainData = new DomainData(domain); + _domains.Add(domainData); + return new IntPtr(_domains.Count - 1); } else { @@ -51,18 +58,8 @@ public static IntPtr GetFunction( { try { - var domainObj = AppDomain.CurrentDomain; - if (domain != IntPtr.Zero) - { - var handle = GCHandle.FromIntPtr(domain); - domainObj = (AppDomain)handle.Target; - } - - var assembly = domainObj.Load(AssemblyName.GetAssemblyName(assemblyPath)); - var type = assembly.GetType(typeName, throwOnError: true); - Print($"Loaded type {type}"); - var deleg = Delegate.CreateDelegate(typeof(EntryPoint), type, function); - + var domainData = _domains[(int)domain]; + var deleg = domainData.GetEntryPoint(assemblyPath, typeName, function); return Marshal.GetFunctionPointerForDelegate(deleg); } catch (Exception exc) @@ -77,21 +74,38 @@ public static void CloseAppDomain(IntPtr domain) { if (domain != IntPtr.Zero) { - var handle = GCHandle.FromIntPtr(domain); - var domainObj = (AppDomain)handle.Target; - AppDomain.Unload(domainObj); - handle.Free(); + try + { + var domainData = _domains[(int)domain]; + domainData.Dispose(); + } + catch (Exception exc) + { + Print($"Exception in {nameof(CloseAppDomain)}: {exc.GetType().Name} {exc.Message}\n{exc.StackTrace}"); + } + } + } + + [DllExport("pyclr_finalize", CallingConvention.Cdecl)] + public static void Close() + { + foreach (var domainData in _domains) + { + domainData.Dispose(); } + + _domains.Clear(); + _initialized = false; } - #if DEBUG - static void Print(string s) +#if DEBUG + internal static void Print(string s) { Console.WriteLine(s); } - #else - static void Print(string s) {} - #endif +#else + internal static void Print(string s) { } +#endif } } diff --git a/netfx_loader/DomainData.cs b/netfx_loader/DomainData.cs new file mode 100644 index 0000000..3a17d7a --- /dev/null +++ b/netfx_loader/DomainData.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace ClrLoader +{ + using static ClrLoader; + + class DomainData : IDisposable + { + public delegate int EntryPoint(IntPtr buffer, int size); + + bool _disposed = false; + + public AppDomain Domain { get; } + public Dictionary<(string, string, string), EntryPoint> _delegates; + + public DomainData(AppDomain domain) + { + Domain = domain; + _delegates = new Dictionary<(string, string, string), EntryPoint>(); + } + + public EntryPoint GetEntryPoint(string assemblyPath, string typeName, string function) + { + if (_disposed) + throw new InvalidOperationException("Domain is already disposed"); + + var key = (assemblyPath, typeName, function); + + EntryPoint result; + + if (!_delegates.TryGetValue(key, out result)) + { + var assembly = Domain.Load(AssemblyName.GetAssemblyName(assemblyPath)); + var type = assembly.GetType(typeName, throwOnError: true); + + Print($"Loaded type {type}"); + result = (EntryPoint)Delegate.CreateDelegate(typeof(EntryPoint), type, function); + + _delegates[key] = result; + } + + return result; + } + + public void Dispose() + { + if (!_disposed) + { + _delegates.Clear(); + + if (Domain != AppDomain.CurrentDomain) + AppDomain.Unload(Domain); + + _disposed = true; + } + } + + } +} \ No newline at end of file From 367d70823998a9d1e697569dae1962012c456984 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 10 Feb 2021 17:31:25 +0100 Subject: [PATCH 2/2] Call initialize and finalize from Python --- clr_loader/ffi/netfx.py | 2 ++ clr_loader/netfx.py | 25 ++++++++++++++++++++----- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/clr_loader/ffi/netfx.py b/clr_loader/ffi/netfx.py index 02a7737..2ebc862 100644 --- a/clr_loader/ffi/netfx.py +++ b/clr_loader/ffi/netfx.py @@ -5,8 +5,10 @@ typedef void* pyclr_domain; typedef int (*entry_point)(void* buffer, int size); +void pyclr_initialize(); void* pyclr_create_appdomain(const char* name, const char* config_file); entry_point pyclr_get_function(pyclr_domain domain, const char* assembly_path, const char* class_name, const char* function); void pyclr_close_appdomain(pyclr_domain domain); +void pyclr_finalize(); """ ] diff --git a/clr_loader/netfx.py b/clr_loader/netfx.py index 5c6e794..4ca0d62 100644 --- a/clr_loader/netfx.py +++ b/clr_loader/netfx.py @@ -1,15 +1,12 @@ +import atexit from .ffi import ffi, load_netfx - _FW = None class NetFx: def __init__(self, name=None, config_file=None): - global _FW - if _FW is None: - _FW = load_netfx() - + initialize() self._domain = _FW.pyclr_create_appdomain( name or ffi.NULL, config_file or ffi.NULL ) @@ -27,3 +24,21 @@ def get_callable(self, assembly_path, typename, function): def __del__(self): if self._domain and _FW: _FW.pyclr_close_appdomain(self._domain) + + +def initialize(): + global _FW + if _FW is not None: + return + + _FW = load_netfx() + _FW.pyclr_initialize() + + atexit.register(_release) + + +def _release(): + global _FW + if _FW is not None: + _FW.pyclr_finalize() + _FW = None