|
1 | 1 | using System;
|
2 | 2 | using System.Collections.Generic;
|
3 | 3 | using System.Reflection;
|
| 4 | +using System.Runtime.InteropServices; |
4 | 5 |
|
5 | 6 | namespace ClrLoader
|
6 | 7 | {
|
7 | 8 | using static ClrLoader;
|
8 | 9 |
|
9 |
| - class DomainData : IDisposable |
| 10 | + public static class DomainSetup |
10 | 11 | {
|
11 | 12 | public delegate int EntryPoint(IntPtr buffer, int size);
|
12 | 13 |
|
| 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 | + { |
13 | 38 | bool _disposed = false;
|
14 | 39 |
|
15 | 40 | 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; |
17 | 43 |
|
18 | 44 | public DomainData(AppDomain domain)
|
19 | 45 | {
|
20 | 46 | Domain = domain;
|
21 |
| - _delegates = new Dictionary<(string, string, string), EntryPoint>(); |
| 47 | + _functors = new Dictionary<(string, string, string), IntPtr>(); |
| 48 | + _resolvedAssemblies = new HashSet<string>(); |
22 | 49 | }
|
23 | 50 |
|
24 |
| - public EntryPoint GetEntryPoint(string assemblyPath, string typeName, string function) |
| 51 | + private void installResolver(string assemblyPath) |
25 | 52 | {
|
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); |
32 | 57 |
|
33 |
| - if (!_delegates.TryGetValue(key, out result)) |
| 58 | + AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => |
34 | 59 | {
|
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 | + } |
37 | 65 |
|
38 |
| - Print($"Loaded type {type}"); |
39 |
| - result = (EntryPoint)Delegate.CreateDelegate(typeof(EntryPoint), type, function); |
| 66 | + private static readonly object _lockObj = new object(); |
40 | 67 |
|
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"); |
43 | 72 |
|
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 | + } |
45 | 106 | }
|
46 | 107 |
|
47 | 108 | public void Dispose()
|
48 | 109 | {
|
49 | 110 | if (!_disposed)
|
50 | 111 | {
|
51 |
| - _delegates.Clear(); |
| 112 | + _functors.Clear(); |
52 | 113 |
|
53 | 114 | if (Domain != AppDomain.CurrentDomain)
|
54 | 115 | AppDomain.Unload(Domain);
|
|
0 commit comments