C# 如何确定 DLL 是托管程序集还是本机程序集(防止加载本机 dll)?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/367761/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to determine whether a DLL is a managed assembly or native (prevent loading a native dll)?
提问by Mr. Wilby
Original title: How can I prevent loading a native dll from a .NET app?
原标题:如何防止从 .NET 应用程序加载本机 dll?
Background:
背景:
My C# application includes a plugin framework and generic plugin loader.
我的 C# 应用程序包括一个插件框架和通用插件加载器。
The plugin loader enumerates the application directory in order to identify plugin dlls (essentially it searches for *.dll at this time).
插件加载器枚举应用程序目录以识别插件dll(本质上它此时搜索*.dll)。
Within the same application directory is a native (Windows, non-.net) dll, which, indirectly, one of the plugin dlls depends upon.
在同一应用程序目录中是本机(Windows,非 .net)dll,间接依赖于插件 dll 之一。
The plugin loader blindly assumes that the native.dll is a .NET Assembly dll, simply because it only checks the file extension. When it attempts to load the native dll, an exception is thrown:
插件加载器盲目地假设 native.dll 是一个 .NET Assembly dll,仅仅因为它只检查文件扩展名。当它尝试加载本机 dll 时,会引发异常:
"Could not load file or assembly 'native.dll' or one of its dependencies. The module was expected to contain an assembly manifest."
“无法加载文件或程序集‘native.dll’或其依赖项之一。该模块应包含程序集清单。”
I basically create a diagnostic report if plugin loading fails, so I'm trying to avoid having this log filled up with messages about not being able to load the native dll (which I don't even want to attempt).
如果插件加载失败,我基本上会创建一个诊断报告,所以我试图避免让这个日志充满关于无法加载本机 dll 的消息(我什至不想尝试)。
The question:
问题:
Is there some .NET API call that I can use to determine whether a binary happens to be a .NET assembly so that I don't attempt to load the native dll at all?
是否有一些 .NET API 调用可用于确定二进制文件是否恰好是 .NET 程序集,以便我根本不尝试加载本机 dll?
Perhaps longer term I will move my plugins to a subdirectory, but for now, I just want a work around that doesn't involve hard-coding the "native.dll" name inside my plugin loader.
也许从长远来看,我会将我的插件移动到一个子目录,但现在,我只想解决一个不涉及在我的插件加载器中硬编码“native.dll”名称的方法。
I guess I'm looking for some kind of static Assembly.IsManaged() API call that I've overlooked.... presumably no such API exists?
我想我正在寻找某种我忽略的静态 Assembly.IsManaged() API 调用......大概不存在这样的 API?
回答by orip
You could always wrap the DLL loading with a try/except block...
您始终可以使用 try/except 块包装 DLL 加载...
回答by Ian
As orip suggested, you will want to wrap it in a try {} catch {} block - in particular, you want to be watching out for the BadImageFormatException
正如 orip 建议的那样,您需要将其包装在 try {} catch {} 块中 - 特别是,您需要注意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.)
}
}
Check out the other exceptions here http://msdn.microsoft.com/en-us/library/1009fa28.aspx
在此处查看其他例外情况http://msdn.microsoft.com/en-us/library/1009fa28.aspx
回答by lubos hasko
How to determine whether a file is a .NET Assembly or not?
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;
}
回答by Wolfwyrd
I'm afraid the only real way of doing this is to call System.Reflection.AssemblyName.GetAssemblyName
passing the full path to the file you want to check. This will attempt to pull the name from the manifest without loading the full assembly into the domain. If the file is a managed assembly then it will return the name of the assembly as a string otherwise it will throw a BadImageFormatException
which you can catch and ignore before skipping over the assembly and moving onto your other plugins.
恐怕唯一真正的方法是调用System.Reflection.AssemblyName.GetAssemblyName
将完整路径传递给要检查的文件。这将尝试从清单中提取名称,而不将完整程序集加载到域中。如果文件是托管程序集,那么它将以字符串形式返回程序集的名称,否则它会抛出一个BadImageFormatException
您可以在跳过程序集并移动到其他插件之前捕获和忽略的。
回答by Kirill Osenkov
Answer quoted by lubos hasko is good but it doesn't work for 64-bit assemblies. Here's a corrected version (inspired by http://apichange.codeplex.com/SourceControl/changeset/view/76c98b8c7311#ApiChange.Api/src/Introspection/CorFlagsReader.cs)
lubos hasko 引用的答案很好,但不适用于 64 位程序集。这是一个更正的版本(灵感来自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 // 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));
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##代码####代码##'.
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
'.
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;
}
}
The missing piece was to offset to the data dictionary start differently depending on whether we are PE32 or PE32Plus:
缺失的部分是根据我们是 PE32 还是 PE32Plus 以不同的方式偏移到数据字典的开头:
##代码##回答by user2842155
Using BadImageFormatException exception is a bad way to go, for ex. if your application targets .NET 3.5, it will not recognize let's say assemblies compiled against .NET Core, though the assembly is managed.
例如,使用 BadImageFormatException 异常是一个糟糕的方法。如果您的应用程序以 .NET 3.5 为目标,它不会识别假设针对 .NET Core 编译的程序集,尽管程序集是托管的。
So I think parsing PE header is much better.
所以我认为解析 PE 头要好得多。
回答by InteXX
Extending on Kirill's answer, I've translated it to VB, tuned the Boolean
logic slightly for readability and turned it into an extension method for System.IO.FileInfo
. Hopefully it can help someone.
扩展 Kirill 的回答,我已将其翻译为 VB,Boolean
稍微调整了逻辑以提高可读性,并将其转换为System.IO.FileInfo
. 希望它可以帮助某人。