diff --git a/netfx_loader/ClrLoader.cs b/netfx_loader/ClrLoader.cs index 32b4c01..2a065bb 100644 --- a/netfx_loader/ClrLoader.cs +++ b/netfx_loader/ClrLoader.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Reflection; using System.Runtime.InteropServices; using NXPorts.Attributes; @@ -21,6 +22,19 @@ public static void Initialize() } } + private static string AssemblyDirectory + { + get + { + // This is needed in case the DLL was shadow-copied + // (Otherwise .Location would work) + string codeBase = Assembly.GetExecutingAssembly().CodeBase; + UriBuilder uri = new UriBuilder(codeBase); + string path = Uri.UnescapeDataString(uri.Path); + return Path.GetDirectoryName(path); + } + } + [DllExport("pyclr_create_appdomain", CallingConvention.Cdecl)] public static IntPtr CreateAppDomain( [MarshalAs(UnmanagedType.LPUTF8Str)] string name, @@ -28,16 +42,17 @@ public static IntPtr CreateAppDomain( ) { Print($"Creating AppDomain {name} with {configFile}"); + + var clrLoaderDir = AssemblyDirectory; if (!string.IsNullOrEmpty(name)) { var setup = new AppDomainSetup { - ApplicationBase = AppDomain.CurrentDomain.BaseDirectory, + ApplicationBase = clrLoaderDir, ConfigurationFile = configFile }; - Print($"Base: {AppDomain.CurrentDomain.BaseDirectory}"); + Print($"Base: {clrLoaderDir}"); var domain = AppDomain.CreateDomain(name, null, setup); - Print($"Located domain {domain}"); var domainData = new DomainData(domain); @@ -61,8 +76,8 @@ public static IntPtr GetFunction( try { var domainData = _domains[(int)domain]; - var deleg = domainData.GetEntryPoint(assemblyPath, typeName, function); - return Marshal.GetFunctionPointerForDelegate(deleg); + Print($"Getting functor for function {function} of type {typeName} in assembly {assemblyPath}"); + return domainData.GetFunctor(assemblyPath, typeName, function); } catch (Exception exc) { diff --git a/netfx_loader/DomainData.cs b/netfx_loader/DomainData.cs index 3a17d7a..35c3363 100644 --- a/netfx_loader/DomainData.cs +++ b/netfx_loader/DomainData.cs @@ -1,54 +1,115 @@ using System; using System.Collections.Generic; using System.Reflection; +using System.Runtime.InteropServices; namespace ClrLoader { using static ClrLoader; - class DomainData : IDisposable + public static class DomainSetup { public delegate int EntryPoint(IntPtr buffer, int size); + public static void StoreFunctorFromDomainData() + { + var domain = AppDomain.CurrentDomain; + var assemblyPath = (string)domain.GetData("_assemblyPath"); + var typeName = (string)domain.GetData("_typeName"); + var function = (string)domain.GetData("_function"); + var deleg = GetDelegate(domain, assemblyPath, typeName, function); + var functor = Marshal.GetFunctionPointerForDelegate(deleg); + domain.SetData("_thisDelegate", deleg); + domain.SetData("_thisFunctor", functor); + } + + private static Delegate GetDelegate(AppDomain domain, string assemblyPath, string typeName, string function) + { + var assemblyName = AssemblyName.GetAssemblyName(assemblyPath); + var assembly = domain.Load(assemblyName); + var type = assembly.GetType(typeName, throwOnError: true); + var deleg = Delegate.CreateDelegate(typeof(EntryPoint), type, function); + return deleg; + } + } + + class DomainData : IDisposable + { bool _disposed = false; public AppDomain Domain { get; } - public Dictionary<(string, string, string), EntryPoint> _delegates; + public Dictionary<(string, string, string), IntPtr> _functors; + public HashSet _resolvedAssemblies; public DomainData(AppDomain domain) { Domain = domain; - _delegates = new Dictionary<(string, string, string), EntryPoint>(); + _functors = new Dictionary<(string, string, string), IntPtr>(); + _resolvedAssemblies = new HashSet(); } - public EntryPoint GetEntryPoint(string assemblyPath, string typeName, string function) + private void installResolver(string assemblyPath) { - if (_disposed) - throw new InvalidOperationException("Domain is already disposed"); - - var key = (assemblyPath, typeName, function); - - EntryPoint result; + var assemblyName = AssemblyName.GetAssemblyName(assemblyPath).Name; + if (_resolvedAssemblies.Contains(assemblyName)) + return; + _resolvedAssemblies.Add(assemblyName); - if (!_delegates.TryGetValue(key, out result)) + AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => { - var assembly = Domain.Load(AssemblyName.GetAssemblyName(assemblyPath)); - var type = assembly.GetType(typeName, throwOnError: true); + if (args.Name.Contains(assemblyName)) + return Assembly.LoadFrom(assemblyPath); + return null; + }; + } - Print($"Loaded type {type}"); - result = (EntryPoint)Delegate.CreateDelegate(typeof(EntryPoint), type, function); + private static readonly object _lockObj = new object(); - _delegates[key] = result; - } + public IntPtr GetFunctor(string assemblyPath, string typeName, string function) + { + if (_disposed) + throw new InvalidOperationException("Domain is already disposed"); - return result; + // neither the domain data nor the _functors dictionary is threadsafe + lock (_lockObj) + { + installResolver(assemblyPath); + var assemblyName = AssemblyName.GetAssemblyName(assemblyPath).Name; + + var key = (assemblyName, typeName, function); + + IntPtr result; + if (!_functors.TryGetValue(key, out result)) + { + Domain.SetData("_assemblyPath", assemblyPath); + Domain.SetData("_typeName", typeName); + Domain.SetData("_function", function); + + Domain.DoCallBack(new CrossAppDomainDelegate(DomainSetup.StoreFunctorFromDomainData)); + result = (IntPtr)Domain.GetData("_thisFunctor"); + if (result == IntPtr.Zero) + throw new Exception($"Unable to get functor for {assemblyName}, {typeName}, {function}"); + + // set inputs to StoreFunctorFromDomainData to null. + // (There's no method to explicitly clear domain data) + Domain.SetData("_assemblyPath", null); + Domain.SetData("_typeName", null); + Domain.SetData("_function", null); + + // the result has to remain in the domain data because we don't know when the + // client of pyclr_get_function will actually invoke the functor, and if we + // remove it from the domain data after returning it may get collected too early. + _functors[key] = result; + } + return result; + } } public void Dispose() { if (!_disposed) { - _delegates.Clear(); + _functors.Clear(); if (Domain != AppDomain.CurrentDomain) AppDomain.Unload(Domain);