Incrustar una DLL dentro de otra como recurso incrustado y luego llamarla desde mi código
Pregunta
Tengo una situación en la que estoy creando una DLL que utiliza otra DLL de terceros, pero preferiría poder crear la DLL de terceros en mi DLL en lugar de tener que mantener ambas juntas si es posible.
Esto es C# y .NET 3.5.
La forma en que me gustaría hacer esto es almacenando la DLL de terceros como un recurso integrado que luego coloco en el lugar apropiado durante la ejecución de la primera DLL.
La forma en que originalmente planeé hacer esto es escribiendo código para colocar la DLL de terceros en la ubicación especificada por System.Reflection.Assembly.GetExecutingAssembly().Location.ToString()
menos el último /nameOfMyAssembly.dll
.Puedo salvar con éxito al tercero. .DLL
en esta ubicación (que termina siendo
C: Documentos y configuraciones MyUSername Local Settings Application Data Assembly DL3 KXPPAX6Y.ZCY A1MZ1499.1TR E0115D44 91BB86EB_FE18C901
), pero cuando llego a la parte de mi código que requiere esta DLL, no puedo encontrarla.
¿Alguien tiene alguna idea de lo que debo hacer de manera diferente?
Solución
Una vez que haya integrado el ensamblado de terceros como recurso, agregue código para suscribirse al AppDomain.AssemblyResolve
evento del dominio actual durante el inicio de la aplicación.Este evento se activa cada vez que el subsistema Fusion de CLR no logra ubicar un ensamblaje de acuerdo con las políticas de sondeo vigentes.En el controlador de eventos para AppDomain.AssemblyResolve
, carga el recurso usando Assembly.GetManifestResourceStream
e introducir su contenido como una matriz de bytes en el correspondiente Assembly.Load
sobrecarga.A continuación se muestra cómo podría verse una implementación de este tipo en C#:
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
{
var resName = args.Name + ".dll";
var thisAssembly = Assembly.GetExecutingAssembly();
using (var input = thisAssembly.GetManifestResourceStream(resName))
{
return input != null
? Assembly.Load(StreamToBytes(input))
: null;
}
};
dónde StreamToBytes
podría definirse como:
static byte[] StreamToBytes(Stream input)
{
var capacity = input.CanSeek ? (int) input.Length : 0;
using (var output = new MemoryStream(capacity))
{
int readLength;
var buffer = new byte[4096];
do
{
readLength = input.Read(buffer, 0, buffer.Length);
output.Write(buffer, 0, readLength);
}
while (readLength != 0);
return output.ToArray();
}
}
Finalmente, como algunos ya han mencionado, ILFusionar Puede ser otra opción a considerar, aunque algo más complicada.
Otros consejos
Al final lo hice casi exactamente como sugirió raboof (y similar a lo que sugirió dgvid), excepto con algunos cambios menores y algunas omisiones corregidas.Elegí este método porque era el más cercano a lo que estaba buscando en primer lugar y no requería el uso de ejecutables de terceros ni nada parecido.¡Funciona muy bien!
Así es como terminó luciendo mi código:
EDITAR:Decidí mover esta función a otro ensamblado para poder reutilizarla en varios archivos (simplemente paso Assembly.GetExecutingAssembly()).
Esta es la versión actualizada que le permite pasar el ensamblaje con las DLL integradas.
embedResourcePrefix es la ruta de cadena al recurso incrustado; normalmente será el nombre del ensamblado seguido de cualquier estructura de carpetas que contenga el recurso (p. ej."MyComapny.MyProduct.MyAssembly.Resources" si el dll está en una carpeta llamada Recursos en el proyecto).También se supone que el dll tiene una extensión .dll.resource.
public static void EnableDynamicLoadingForDlls(Assembly assemblyToLoadFrom, string embeddedResourcePrefix) {
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => { // had to add =>
try {
string resName = embeddedResourcePrefix + "." + args.Name.Split(',')[0] + ".dll.resource";
using (Stream input = assemblyToLoadFrom.GetManifestResourceStream(resName)) {
return input != null
? Assembly.Load(StreamToBytes(input))
: null;
}
} catch (Exception ex) {
_log.Error("Error dynamically loading dll: " + args.Name, ex);
return null;
}
}; // Had to add colon
}
private static byte[] StreamToBytes(Stream input) {
int capacity = input.CanSeek ? (int)input.Length : 0;
using (MemoryStream output = new MemoryStream(capacity)) {
int readLength;
byte[] buffer = new byte[4096];
do {
readLength = input.Read(buffer, 0, buffer.Length); // had to change to buffer.Length
output.Write(buffer, 0, readLength);
}
while (readLength != 0);
return output.ToArray();
}
}
Existe una herramienta llamada IlMerge que puede lograr esto: http://research.microsoft.com/~mbarnett/ILMerge.aspx
Luego puedes crear un evento de compilación similar al siguiente.
Establecer ruta="C:\Program Files\Microsoft\ILMerge"
ilmerge /out:$(ProjectDir)\Deploy\LevelEditor.exe $(ProjectDir)\bin elease elease.exe $(ProjectDir)\bin elease\InteractLib.dll $(ProjectDir)\bin elease\SpriteLib.dll $(ProjectDir)\bin elease\LevelLibrary.dll
Tuve éxito al hacer lo que usted describe, pero debido a que la DLL de terceros también es un ensamblado .NET, nunca la escribo en el disco, simplemente la cargo desde la memoria.
Obtengo el ensamblaje de recursos integrado como una matriz de bytes como esta:
Assembly resAssembly = Assembly.LoadFile(assemblyPathName);
byte[] assemblyData;
using (Stream stream = resAssembly.GetManifestResourceStream(resourceName))
{
assemblyData = ReadBytesFromStream(stream);
stream.Close();
}
Luego cargo los datos con Assembly.Load().
Finalmente, agrego un controlador a AppDomain.CurrentDomain.AssemblyResolve para devolver mi ensamblado cargado cuando el cargador de tipos lo mira.
Ver el Taller de fusión .NET para detalles adicionales.
Puedes lograr esto muy fácilmente usando netz, un compresor y empaquetador de ejecutables .net NET.
En lugar de escribir el ensamblaje en el disco, puede intentar hacer Assembly.Load(byte[] rawAssembly) donde crea rawAssembly a partir del recurso incrustado.