-
-
Notifications
You must be signed in to change notification settings - Fork 269
/
AssemblyManager.cs
274 lines (248 loc) · 10.8 KB
/
AssemblyManager.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
// Copyright (c) Govert van Drimmelen. All rights reserved.
// Excel-DNA is licensed under the zlib license. See LICENSE.txt for details.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using ExcelDna.Loader.Logging;
using SevenZip.Compression.LZMA;
namespace ExcelDna.Loader
{
// TODO: Lots more to make a flexible loader.
internal static class AssemblyManager
{
static string pathXll;
static IntPtr hModule;
static Dictionary<string, Assembly> loadedAssemblies = new Dictionary<string,Assembly>();
internal static void Initialize(IntPtr hModule, string pathXll)
{
AssemblyManager.pathXll = pathXll;
AssemblyManager.hModule = hModule;
loadedAssemblies.Add(Assembly.GetExecutingAssembly().FullName, Assembly.GetExecutingAssembly());
// TODO: Load up the DnaFile and Assembly names ?
AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolve;
}
[MethodImpl(MethodImplOptions.Synchronized)]
private static Assembly AssemblyResolve(object sender, ResolveEventArgs args)
{
string name;
byte[] assemblyBytes;
Assembly loadedAssembly = null;
AssemblyName assemblyName = new AssemblyName(args.Name);
CultureInfo cultureInfo = assemblyName.CultureInfo;
name = assemblyName.Name.ToUpperInvariant();
if (name == "EXCELDNA") /* Special case for pre-0.14 versions of ExcelDna */
{
name = "EXCELDNA.INTEGRATION";
}
if (name == "EXCELDNA.LOADER")
{
// Loader must have been loaded from bytes.
// But I have seen the Loader, and it is us.
return Assembly.GetExecutingAssembly();
}
bool isResourceAssembly = name.EndsWith(".RESOURCES");
// This check and mapping must match that done when packing (in ResourceHelper.cs : ResourceUpdate.AddAssembly)
if (isResourceAssembly && cultureInfo != null && !string.IsNullOrEmpty(cultureInfo.Name))
{
name += "." + cultureInfo.Name.ToUpperInvariant();
}
// Check our AssemblyResolve cache
if (loadedAssemblies.TryGetValue(name, out loadedAssembly))
return loadedAssembly;
// Check if it is loaded in the AppDomain already,
// e.g. from resources as an ExternalLibrary
loadedAssembly = GetAssemblyIfLoaded(assemblyName);
if (loadedAssembly != null)
{
Logger.Initialization.Info("Assembly {0} was found to already be loaded into the AppDomain.", name);
loadedAssemblies[name] = loadedAssembly;
return loadedAssembly;
}
// Now check in resources ...
// We expect failures when loading .resources assemblies, so only log at the Verbose level.
// From: http://blogs.msdn.com/b/suzcook/archive/2003/05/29/57120.aspx
// "Note: Unless you are explicitly debugging the failure of a resource to load,
// you will likely want to ignore failures to find assemblies with the ".resources" extension
// with the culture set to something other than "neutral". Those are expected failures when the
// ResourceManager is probing for satellite assemblies."
if (isResourceAssembly)
Logger.Initialization.Verbose("Attempting to load {0} from resources.", name);
else
Logger.Initialization.Info("Attempting to load {0} from resources.", name);
assemblyBytes = GetResourceBytes(name, 0);
if (assemblyBytes == null)
{
if (isResourceAssembly)
Logger.Initialization.Verbose("Assembly {0} could not be loaded from resources (ResourceManager probing for satellite assemblies).", name);
else
Logger.Initialization.Warn("Assembly {0} could not be loaded from resources.", name);
return null;
}
byte[] pdbBytes = GetResourceBytes(name, 4);
if (pdbBytes != null)
Logger.Initialization.Info("Trying Assembly.Load for {0} (from {1} bytes, with {2} bytes of pdb).", name, assemblyBytes.Length, pdbBytes.Length);
else
Logger.Initialization.Info("Trying Assembly.Load for {0} (from {1} bytes, without pdb).", name, assemblyBytes.Length);
try
{
loadedAssembly = pdbBytes == null ? Assembly.Load(assemblyBytes) : Assembly.Load(assemblyBytes, pdbBytes);
loadedAssemblies[name] = loadedAssembly;
return loadedAssembly;
}
catch (Exception e)
{
Logger.Initialization.Error(e, "Error during Assembly Load from bytes");
}
return null;
}
// TODO: This method probably should not be here.
internal static byte[] GetResourceBytes(string resourceName, int type) // types: 0 - Assembly, 1 - Dna file, 2 - Image
{
// CAREFUL: Can't log here yet as this method is called during Integration.Initialize()
// Logger.Initialization.Info("GetResourceBytes for resource {0} of type {1}", resourceName, type);
string typeName;
if (type == 0)
{
typeName = "ASSEMBLY";
}
else if (type == 1)
{
typeName = "DNA";
}
else if (type == 2)
{
typeName = "IMAGE";
}
else if (type == 3)
{
typeName = "SOURCE";
}
else if (type == 4)
{
typeName = "PDB";
}
else
{
throw new ArgumentOutOfRangeException("type", "Unknown resource type. Only types 0 (Assembly), 1 (Dna file), 2 (Image) or 3 (Source) are valid.");
}
return ResourceHelper.LoadResourceBytes(hModule, typeName, resourceName);
}
// A copy of this method lives in ExcelDna.Integration - ExternalLibrary.cs
private static Assembly GetAssemblyIfLoaded(AssemblyName assemblyName)
{
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly loadedAssembly in assemblies)
{
AssemblyName loadedName = loadedAssembly.GetName();
if (loadedName.Name.Equals(assemblyName.Name, StringComparison.OrdinalIgnoreCase))
{
// For resources, also check the culture
if (loadedName.Name.EndsWith(".RESOURCES", StringComparison.OrdinalIgnoreCase))
{
if ((loadedName.CultureInfo == null) && (assemblyName.CultureInfo != null) ||
(loadedName.CultureInfo != null) && (assemblyName.CultureInfo == null) ||
!string.Equals(loadedName.CultureInfo.Name, assemblyName.CultureInfo.Name))
{
continue; // next loadedAssembly
}
}
return loadedAssembly;
}
}
return null;
}
}
internal unsafe static class ResourceHelper
{
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr FindResource(
IntPtr hModule,
string lpName,
string lpType);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr LoadResource(
IntPtr hModule,
IntPtr hResInfo);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr LockResource(
IntPtr hResData);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern uint SizeofResource(
IntPtr hModule,
IntPtr hResInfo);
[DllImport("kernel32.dll")]
private static extern uint GetLastError();
// Load the resource, trying also as compressed if no uncompressed version is found.
// If the resource type ends with "_LZMA", we decompress from the LZMA format.
internal static byte[] LoadResourceBytes(IntPtr hModule, string typeName, string resourceName)
{
// CAREFUL: Can't log here yet as this method is called during Integration.Initialize()
// Logger.Initialization.Info("LoadResourceBytes for resource {0} of type {1}", resourceName, typeName);
IntPtr hResInfo = FindResource(hModule, resourceName, typeName);
if (hResInfo == IntPtr.Zero)
{
// We expect this null result value when the resource does not exists.
if (!typeName.EndsWith("_LZMA"))
{
// Try the compressed name.
typeName += "_LZMA";
hResInfo = FindResource(hModule, resourceName, typeName);
}
if (hResInfo == IntPtr.Zero)
{
// CAREFUL: Can't log here yet as this method is called during Integration.Initialize()
// Logger.Initialization.Info("Resource not found - resource {0} of type {1}", resourceName, typeName);
Debug.Print("ResourceHelper.LoadResourceBytes - Resource not found - resource {0} of type {1}", resourceName, typeName);
// Return null to indicate that the resource was not found.
return null;
}
}
IntPtr hResData = LoadResource(hModule, hResInfo);
if (hResData == IntPtr.Zero)
{
// Unexpected error - this should not happen
// CAREFUL: Can't log here yet as this method is called during Integration.Initialize()
//Logger.Initialization.Error("Unexpected errror loading resource {0} of type {1}", resourceName, typeName);
Debug.Print("ResourceHelper.LoadResourceBytes - Unexpected errror loading resource {0} of type {1}", resourceName, typeName);
throw new Win32Exception();
}
uint size = SizeofResource(hModule, hResInfo);
IntPtr pResourceBytes = LockResource(hResData);
byte[] resourceBytes = new byte[size];
Marshal.Copy(pResourceBytes, resourceBytes, 0, (int)size);
if (typeName.EndsWith("_LZMA"))
return Decompress(resourceBytes);
else
return resourceBytes;
}
private static byte[] Decompress(byte[] inputBytes)
{
MemoryStream newInStream = new MemoryStream(inputBytes);
Decoder decoder = new Decoder();
newInStream.Seek(0, 0);
MemoryStream newOutStream = new MemoryStream();
byte[] properties2 = new byte[5];
if (newInStream.Read(properties2, 0, 5) != 5)
throw (new Exception("input .lzma is too short"));
long outSize = 0;
for (int i = 0; i < 8; i++)
{
int v = newInStream.ReadByte();
if (v < 0)
throw (new Exception("Can't Read 1"));
outSize |= ((long)(byte)v) << (8 * i);
}
decoder.SetDecoderProperties(properties2);
long compressedSize = newInStream.Length - newInStream.Position;
decoder.Code(newInStream, newOutStream, compressedSize, outSize, null);
byte[] b = newOutStream.ToArray();
return b;
}
}
}