Skip to content

Commit 5dadf4c

Browse files
koubaaMohamed Koubaa
andauthored
Support pythonnet for AppDomain (#78)
* load the assembly and get function address inside the domain When using domain.Load for an assembly, the assembly resolution rules are awkward Even if the full path is given to the AssemblyName, when the domain tries to load the assembly, it does not use that context and tries to resolve the assembly using normal domain resolution rules, which would require an assembly resolver to be installed. However, the assembly resolver that is actually used at runtime is the one installed to the main appdomain. This prevents a library like Python.Runtime.dll (used by pythonnet) which is not installed to the application base directory to be loaded by clr_loader. To fix this issue, the assembly resolver of the main appdomain is lazily extending to include paths needed for libraries passed into GetFunction, and GetFunction internally uses AppDomain.DoCallBack() to marshal the function pointer inside the target app domain, using global domain data to access the function pointer and return it to the user of clr_loader. * Add comment * PR review feedback --------- Co-authored-by: Mohamed Koubaa <[email protected]>
1 parent f9e0544 commit 5dadf4c

File tree

2 files changed

+100
-24
lines changed

2 files changed

+100
-24
lines changed

netfx_loader/ClrLoader.cs

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.IO;
4+
using System.Reflection;
45
using System.Runtime.InteropServices;
56
using NXPorts.Attributes;
67

@@ -21,23 +22,37 @@ public static void Initialize()
2122
}
2223
}
2324

25+
private static string AssemblyDirectory
26+
{
27+
get
28+
{
29+
// This is needed in case the DLL was shadow-copied
30+
// (Otherwise .Location would work)
31+
string codeBase = Assembly.GetExecutingAssembly().CodeBase;
32+
UriBuilder uri = new UriBuilder(codeBase);
33+
string path = Uri.UnescapeDataString(uri.Path);
34+
return Path.GetDirectoryName(path);
35+
}
36+
}
37+
2438
[DllExport("pyclr_create_appdomain", CallingConvention.Cdecl)]
2539
public static IntPtr CreateAppDomain(
2640
[MarshalAs(UnmanagedType.LPUTF8Str)] string name,
2741
[MarshalAs(UnmanagedType.LPUTF8Str)] string configFile
2842
)
2943
{
3044
Print($"Creating AppDomain {name} with {configFile}");
45+
46+
var clrLoaderDir = AssemblyDirectory;
3147
if (!string.IsNullOrEmpty(name))
3248
{
3349
var setup = new AppDomainSetup
3450
{
35-
ApplicationBase = AppDomain.CurrentDomain.BaseDirectory,
51+
ApplicationBase = clrLoaderDir,
3652
ConfigurationFile = configFile
3753
};
38-
Print($"Base: {AppDomain.CurrentDomain.BaseDirectory}");
54+
Print($"Base: {clrLoaderDir}");
3955
var domain = AppDomain.CreateDomain(name, null, setup);
40-
4156
Print($"Located domain {domain}");
4257

4358
var domainData = new DomainData(domain);
@@ -61,8 +76,8 @@ public static IntPtr GetFunction(
6176
try
6277
{
6378
var domainData = _domains[(int)domain];
64-
var deleg = domainData.GetEntryPoint(assemblyPath, typeName, function);
65-
return Marshal.GetFunctionPointerForDelegate(deleg);
79+
Print($"Getting functor for function {function} of type {typeName} in assembly {assemblyPath}");
80+
return domainData.GetFunctor(assemblyPath, typeName, function);
6681
}
6782
catch (Exception exc)
6883
{

netfx_loader/DomainData.cs

Lines changed: 80 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,115 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Reflection;
4+
using System.Runtime.InteropServices;
45

56
namespace ClrLoader
67
{
78
using static ClrLoader;
89

9-
class DomainData : IDisposable
10+
public static class DomainSetup
1011
{
1112
public delegate int EntryPoint(IntPtr buffer, int size);
1213

14+
public static void StoreFunctorFromDomainData()
15+
{
16+
var domain = AppDomain.CurrentDomain;
17+
var assemblyPath = (string)domain.GetData("_assemblyPath");
18+
var typeName = (string)domain.GetData("_typeName");
19+
var function = (string)domain.GetData("_function");
20+
var deleg = GetDelegate(domain, assemblyPath, typeName, function);
21+
var functor = Marshal.GetFunctionPointerForDelegate(deleg);
22+
domain.SetData("_thisDelegate", deleg);
23+
domain.SetData("_thisFunctor", functor);
24+
}
25+
26+
private static Delegate GetDelegate(AppDomain domain, string assemblyPath, string typeName, string function)
27+
{
28+
var assemblyName = AssemblyName.GetAssemblyName(assemblyPath);
29+
var assembly = domain.Load(assemblyName);
30+
var type = assembly.GetType(typeName, throwOnError: true);
31+
var deleg = Delegate.CreateDelegate(typeof(EntryPoint), type, function);
32+
return deleg;
33+
}
34+
}
35+
36+
class DomainData : IDisposable
37+
{
1338
bool _disposed = false;
1439

1540
public AppDomain Domain { get; }
16-
public Dictionary<(string, string, string), EntryPoint> _delegates;
41+
public Dictionary<(string, string, string), IntPtr> _functors;
42+
public HashSet<string> _resolvedAssemblies;
1743

1844
public DomainData(AppDomain domain)
1945
{
2046
Domain = domain;
21-
_delegates = new Dictionary<(string, string, string), EntryPoint>();
47+
_functors = new Dictionary<(string, string, string), IntPtr>();
48+
_resolvedAssemblies = new HashSet<string>();
2249
}
2350

24-
public EntryPoint GetEntryPoint(string assemblyPath, string typeName, string function)
51+
private void installResolver(string assemblyPath)
2552
{
26-
if (_disposed)
27-
throw new InvalidOperationException("Domain is already disposed");
28-
29-
var key = (assemblyPath, typeName, function);
30-
31-
EntryPoint result;
53+
var assemblyName = AssemblyName.GetAssemblyName(assemblyPath).Name;
54+
if (_resolvedAssemblies.Contains(assemblyName))
55+
return;
56+
_resolvedAssemblies.Add(assemblyName);
3257

33-
if (!_delegates.TryGetValue(key, out result))
58+
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
3459
{
35-
var assembly = Domain.Load(AssemblyName.GetAssemblyName(assemblyPath));
36-
var type = assembly.GetType(typeName, throwOnError: true);
60+
if (args.Name.Contains(assemblyName))
61+
return Assembly.LoadFrom(assemblyPath);
62+
return null;
63+
};
64+
}
3765

38-
Print($"Loaded type {type}");
39-
result = (EntryPoint)Delegate.CreateDelegate(typeof(EntryPoint), type, function);
66+
private static readonly object _lockObj = new object();
4067

41-
_delegates[key] = result;
42-
}
68+
public IntPtr GetFunctor(string assemblyPath, string typeName, string function)
69+
{
70+
if (_disposed)
71+
throw new InvalidOperationException("Domain is already disposed");
4372

44-
return result;
73+
// neither the domain data nor the _functors dictionary is threadsafe
74+
lock (_lockObj)
75+
{
76+
installResolver(assemblyPath);
77+
var assemblyName = AssemblyName.GetAssemblyName(assemblyPath).Name;
78+
79+
var key = (assemblyName, typeName, function);
80+
81+
IntPtr result;
82+
if (!_functors.TryGetValue(key, out result))
83+
{
84+
Domain.SetData("_assemblyPath", assemblyPath);
85+
Domain.SetData("_typeName", typeName);
86+
Domain.SetData("_function", function);
87+
88+
Domain.DoCallBack(new CrossAppDomainDelegate(DomainSetup.StoreFunctorFromDomainData));
89+
result = (IntPtr)Domain.GetData("_thisFunctor");
90+
if (result == IntPtr.Zero)
91+
throw new Exception($"Unable to get functor for {assemblyName}, {typeName}, {function}");
92+
93+
// set inputs to StoreFunctorFromDomainData to null.
94+
// (There's no method to explicitly clear domain data)
95+
Domain.SetData("_assemblyPath", null);
96+
Domain.SetData("_typeName", null);
97+
Domain.SetData("_function", null);
98+
99+
// the result has to remain in the domain data because we don't know when the
100+
// client of pyclr_get_function will actually invoke the functor, and if we
101+
// remove it from the domain data after returning it may get collected too early.
102+
_functors[key] = result;
103+
}
104+
return result;
105+
}
45106
}
46107

47108
public void Dispose()
48109
{
49110
if (!_disposed)
50111
{
51-
_delegates.Clear();
112+
_functors.Clear();
52113

53114
if (Domain != AppDomain.CurrentDomain)
54115
AppDomain.Unload(Domain);

0 commit comments

Comments
 (0)