c# plugins dll assemblies native

c# - ¿Cómo determinar si un archivo DLL es un ensamblado administrado o nativo(no permite cargar un dll nativo)?



plugins assemblies (7)

Título original: ¿Cómo puedo evitar cargar un dll nativo desde una aplicación .NET?

Fondo:

La aplicación My C # incluye un plugin framework y un genérico cargador de plugins.

El cargador de complementos enumera el directorio de la aplicación para identificar los archivos DLL del complemento (esencialmente busca * .dll en este momento).

Dentro del mismo directorio de la aplicación se encuentra una dll nativa (Windows, non-.net), de la cual, indirectamente, depende uno de los dlls del complemento.

El cargador de complementos asume ciegamente que native.dll es un dll Assembly Assembly, simplemente porque solo comprueba la extensión del archivo. Cuando intenta cargar el dll nativo, se lanza una excepción:

"No se pudo cargar el archivo o ensamblado ''native.dll'' o una de sus dependencias. Se esperaba que el módulo incluyera un manifiesto de ensamblaje".

Básicamente, creo un informe de diagnóstico si falla la carga del complemento, por lo que estoy tratando de evitar que este registro se llene con mensajes sobre la imposibilidad de cargar el dll nativo (que ni siquiera quiero intentar).

La pregunta:

¿Hay alguna llamada a la API .NET que pueda usar para determinar si un archivo binario resulta ser un ensamblado de .NET para que no intente cargar el dll nativo en absoluto?

Tal vez a más largo plazo moveré mis complementos a un subdirectorio, pero por ahora, solo quiero un trabajo que no implique codificar el nombre "native.dll" dentro de mi cargador de complementos.

Supongo que estoy buscando algún tipo de llamada estática de Assembly.IsManaged () que he pasado por alto ... ¿presumiblemente no existe esa API?


Ampliando la respuesta de Kirill, la traduje a VB, sintonicé la lógica Boolean para facilitar su lectura y la convertí en un método de extensión para System.IO.FileInfo . Con suerte, puede ayudar a alguien.

Public Module FileSystem <Extension> Public Function IsManagedAssembly(File As FileInfo) As Boolean Dim _ uHeaderSignature, uHeaderPointer As UInteger Dim _ uFormat, u64, u32 As UShort u64 = &H20B u32 = &H10B IsManagedAssembly = False If File.Exists AndAlso File.Length.IsAtLeast(64) Then Using oStream As New FileStream(File.FullName, FileMode.Open, FileAccess.Read) Using oReader As New BinaryReader(oStream) ''PE Header starts @ 0x3C (60). Its a 4 byte header. oStream.Position = &H3C uHeaderPointer = oReader.ReadUInt32 If uHeaderPointer = 0 Then uHeaderPointer = &H80 End If '' Ensure there is at least enough room for the following structures: '' 24 byte PE Signature & Header '' 28 byte Standard Fields (24 bytes for PE32+) '' 68 byte NT Fields (88 bytes for PE32+) '' >= 128 byte Data Dictionary Table If uHeaderPointer < oStream.Length - 257 Then '' Check the PE signature. Should equal ''PE/0/0''. oStream.Position = uHeaderPointer uHeaderSignature = oReader.ReadUInt32 If uHeaderSignature = &H4550 Then '' skip over the PEHeader fields oStream.Position += 20 '' Read PE magic number from Standard Fields to determine format. uFormat = oReader.ReadUInt16 If uFormat = u32 OrElse uFormat = u64 Then '' Read the 15th Data Dictionary RVA field which contains the CLI header RVA. '' When this is non-zero then the file contains CLI data, otherwise not. Select Case uFormat Case u32 : oStream.Position = uHeaderPointer + &HE8 Case u64 : oStream.Position = uHeaderPointer + &HF8 End Select IsManagedAssembly = oReader.ReadUInt32 > 0 End If End If End If End Using End Using End If End Function End Module


Como Orip sugirió, querrá envolverlo en un bloque try {} catch {}; en particular, quiere estar BadImageFormatException

foreach (string aDll in dllCollection) { try { Assembly anAssembly = Assembly.LoadFrom(aDll); } catch (BadImageFormatException ex) { //Handle this here } catch (Exception ex) { //Other exceptions (i/o, security etc.) } }

Consulte las otras excepciones aquí http://msdn.microsoft.com/en-us/library/1009fa28.aspx


El uso de la excepción BadImageFormatException es una mala forma de hacerlo, por ej. si su aplicación apunta a .NET 3.5, no reconocerá los ensamblados compilados contra .NET Core, aunque se administre el ensamblado.

Así que creo que analizar el encabezado PE es mucho mejor.


La respuesta citada por lubos hasko es buena, pero no funciona para ensamblajes de 64 bits. Aquí hay una versión corregida (inspirada en http://apichange.codeplex.com/SourceControl/changeset/view/76c98b8c7311#ApiChange.Api/src/Introspection/CorFlagsReader.cs )

public static bool IsManagedAssembly(string fileName) { using (Stream fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read)) using (BinaryReader binaryReader = new BinaryReader(fileStream)) { if (fileStream.Length < 64) { return false; } //PE Header starts @ 0x3C (60). Its a 4 byte header. fileStream.Position = 0x3C; uint peHeaderPointer = binaryReader.ReadUInt32(); if (peHeaderPointer == 0) { peHeaderPointer = 0x80; } // Ensure there is at least enough room for the following structures: // 24 byte PE Signature & Header // 28 byte Standard Fields (24 bytes for PE32+) // 68 byte NT Fields (88 bytes for PE32+) // >= 128 byte Data Dictionary Table if (peHeaderPointer > fileStream.Length - 256) { return false; } // Check the PE signature. Should equal ''PE/0/0''. fileStream.Position = peHeaderPointer; uint peHeaderSignature = binaryReader.ReadUInt32(); if (peHeaderSignature != 0x00004550) { return false; } // skip over the PEHeader fields fileStream.Position += 20; const ushort PE32 = 0x10b; const ushort PE32Plus = 0x20b; // Read PE magic number from Standard Fields to determine format. var peFormat = binaryReader.ReadUInt16(); if (peFormat != PE32 && peFormat != PE32Plus) { return false; } // Read the 15th Data Dictionary RVA field which contains the CLI header RVA. // When this is non-zero then the file contains CLI data otherwise not. ushort dataDictionaryStart = (ushort)(peHeaderPointer + (peFormat == PE32 ? 232 : 248)); fileStream.Position = dataDictionaryStart; uint cliHeaderRva = binaryReader.ReadUInt32(); if (cliHeaderRva == 0) { return false; } return true; } }

La pieza que faltaba era compensar el inicio del diccionario de datos de manera diferente dependiendo de si somos PE32 o PE32Plus:

// Read PE magic number from Standard Fields to determine format. var peFormat = binaryReader.ReadUInt16(); if (peFormat != PE32 && peFormat != PE32Plus) { return false; } // Read the 15th Data Dictionary RVA field which contains the CLI header RVA. // When this is non-zero then the file contains CLI data otherwise not. ushort dataDictionaryStart = (ushort)(peHeaderPointer + (peFormat == PE32 ? 232 : 248));


Me temo que la única forma real de hacerlo es llamar a System.Reflection.AssemblyName.GetAssemblyName pasar la ruta completa al archivo que desea verificar. Esto intentará extraer el nombre del manifiesto sin cargar el ensamblaje completo en el dominio. Si el archivo es un ensamblaje administrado, devolverá el nombre del ensamblaje como una cadena, de lo contrario arrojará una BadImageFormatException que puede capturar e ignorar antes de omitir el ensamblaje y pasar a los otros complementos.


Siempre puedes ajustar la carga DLL con un bloque try / except ...


¿Cómo determinar si un archivo es un ensamblado de .NET o no?

public static bool IsManagedAssembly(string fileName) { uint peHeader; uint peHeaderSignature; ushort machine; ushort sections; uint timestamp; uint pSymbolTable; uint noOfSymbol; ushort optionalHeaderSize; ushort characteristics; ushort dataDictionaryStart; uint[] dataDictionaryRVA = new uint[16]; uint[] dataDictionarySize = new uint[16]; Stream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read); BinaryReader reader = new BinaryReader(fs); //PE Header starts @ 0x3C (60). Its a 4 byte header. fs.Position = 0x3C; peHeader = reader.ReadUInt32(); //Moving to PE Header start location... fs.Position = peHeader; peHeaderSignature = reader.ReadUInt32(); //We can also show all these value, but we will be //limiting to the CLI header test. machine = reader.ReadUInt16(); sections = reader.ReadUInt16(); timestamp = reader.ReadUInt32(); pSymbolTable = reader.ReadUInt32(); noOfSymbol = reader.ReadUInt32(); optionalHeaderSize = reader.ReadUInt16(); characteristics = reader.ReadUInt16(); // Now we are at the end of the PE Header and from here, the PE Optional Headers starts... To go directly to the datadictionary, we''ll increase the stream’s current position to with 96 (0x60). 96 because, 28 for Standard fields 68 for NT-specific fields From here DataDictionary starts...and its of total 128 bytes. DataDictionay has 16 directories in total, doing simple maths 128/16 = 8. So each directory is of 8 bytes. In this 8 bytes, 4 bytes is of RVA and 4 bytes of Size. btw, the 15th directory consist of CLR header! if its 0, its not a CLR file :) dataDictionaryStart = Convert.ToUInt16(Convert.ToUInt16(fs.Position) + 0x60); fs.Position = dataDictionaryStart; for (int i = 0; i < 15; i++) { dataDictionaryRVA[i] = reader.ReadUInt32(); dataDictionarySize[i] = reader.ReadUInt32(); } fs.Close(); if (dataDictionaryRVA[14] == 0) return false; else return true; }