在C#中检查DLL文件是否为CLR程序集的最佳方法

20

如何最好地检查一个DLL文件是Win32 DLL还是CLR程序集。目前我使用的代码是:

    try
    {
        this.currentWorkingDirectory = Path.GetDirectoryName(assemblyPath);

        //Try to load the assembly.
        assembly = Assembly.LoadFile(assemblyPath);

        return assembly != null;
    }
    catch (FileLoadException ex)
    {
        exception = ex;
    }
    catch (BadImageFormatException ex)
    {
        exception = ex;
    }
    catch (ArgumentException ex)
    {
        exception = ex;
    }
    catch (Exception ex)
    {
        exception = ex;
    }

    if (exception is BadImageFormatException)
    {
        return false;
    }

但我喜欢在加载之前进行检查,因为我不想出现异常(时间)。

有没有更好的方法?


这个回答解决了你的问题吗?如何确定一个DLL是托管程序集还是本机程序集(防止加载本机dll) - MSDN.WhiteKnight
8个回答

23

检查 PE 头:

DOS 头从 0x0 开始,0x3c 处的 DWORD 包含指向 PE 签名的指针(通常为 0x80),该签名长度为 4 字节,接下来的 20 字节是 COFF 头,然后是 PE 头部(位于 0x9)。PE 头部长 224 字节,并包含数据目录(位于 PE 头部之后 96 字节 = 0xf)。第 15 个条目(位于 0x16)是 CLR 头描述符(有时称为 COM 描述符,但这与 COM 无关)。如果此条目为空(即在 0x168 到 0x16f 的 8 个字节中为 0),则该文件不是 .NET 程序集。如果要检查它是否为 COM DLL,则应查看其是否导出 GetClassObject。

参考资料

更新:有一种更“ .NET” 的实现方法:

使用 Module.GetPEKind 方法并检查PortableExecutableKinds 枚举:

NotAPortableExecutableImage 文件不是可移植可执行文件 (PE) 格式。

ILOnly 可执行文件仅包含 Microsoft 中间语言 (MSIL),因此对于 32 位或 64 位平台来说是中性的。

Required32Bit 可执行文件可以在 32 位平台上运行,也可以在 64 位平台上以 WoW64 模式(32 位模拟模式)运行。

PE32Plus 可执行文件只能在 64 位平台上运行。

Unmanaged32Bit 可执行文件是未托管代码,并且可以在 32 位和 64 位平台上运行。

Unmanaged32Bit 可执行文件是未托管代码,并且可以在 32 位和 64 位平台上运行。

32位Windows on Windows (WOW)环境在64位平台上运行。

PE32Plus这个可执行文件需要一个64位的平台。

Unmanaged32Bit这个可执行文件包含纯非托管代码。


12
我会尝试类似原帖的做法。这个解决方案听起来很不错,但是如何在不调用Assembly.LoadFile(对于非CLR dlls,它会在您调用GetPEKind之前抛出BadImageFormatException)的情况下获取Module实例呢? - Paul A Jungwirth
1
@Simon:当然如果你感兴趣的话,你可以写一些代码作为回答。 - Mitch Wheat
3
@Mitch:好的,也许我的投票速度有点太快了。我试图撤销它,但超时已经过去了。抱歉。请随意对我的回答进行投票 :)。我确实尝试让这个工作起来。尽管我认为Paul的问题是合理的。Module.GetPEKind是实例。所以要获取一个模块,您需要调用程序集Assembly.Load。这会引发异常。由于问题明确说明“没有异常”,因此这个答案似乎不完整或不正确。 - Simon

13

如果加载了一个程序集,例如 Assembly.LoadFile(dotNetDllorExe) 并且没有抛出任何异常,则这是一个有效的 .NET 程序集。如果不是,则会抛出 "BadImageFormatException" 异常。

通过加载文件并检查是否抛出异常来检查文件是否为程序集并不是太清洁的方法。毕竟,异常应该只在特殊情况下使用。

.NET 程序集是常规 Win32 PE 文件,操作系统不区分 .NET 程序集和 Win32 可执行二进制文件,它们都是同样的 PE 文件。那么系统如何确定 DLL 或 EXE 是否为托管程序集以加载 CLR?

它验证文件头以检查它是否为托管程序集。在随 .NET SDK 一起提供的 ECMA 规范第 II 部分-元数据中,您会看到 PE 格式中的单独 CLI Header。它是 PE 可选头中的第15个数据目录。因此,简而言之,如果此数据目录中有值,则意味着这是一个有效的 .NET 程序集,否则不是。

internal static class PortableExecutableHelper
{
    internal static bool IsDotNetAssembly(string peFile)
    {
        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(peFile, 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();
        }
        if (dataDictionaryRVA[14] == 0)
        {
            Console.WriteLine("This is NOT a valid CLR File!!");
            return false;
        }
        else
        {
            Console.WriteLine("This is a valid CLR File..");
            return true;
        }
        fs.Close();
    }
}

ECMA RefBlog Ref


不幸的是,我仍然有大量的BadImageFormatExceptions问题,这似乎经常报告错误的阳性结果,例如对于C ++ Boost库。 - Jonas
请查看Calum Heanney的答案,以更新此解决方案以适用于64位dll。https://dev59.com/cHM_5IYBdhLWcg3wdy_z#29643803 - edwabr123
检查Calum Heanney的答案,以更新这个解决方案,使其适用于64位的dll。https://stackoverflow.com/a/29643803/2151722 - undefined

4

我的声望不足以直接评论Jeremy的答案,这个答案适用于32位DLL。但是我必须为64位DLL进行改进,因为它们似乎有更多基于Windows的COFF字段。

Microsoft所述,COFF“魔数”指示格式是PE32(32位)还是PE32+(64位)。如果是后者,则数据目录的偏移量为112字节,而不是96字节。

// After the COFF header, the first COFF field is a Magic Number.
UInt16 coffMagic = reader.ReadUInt16();

// Skip remaining fields to reach data directories.  See:
// https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#optional-header-image-only
if (coffMagic == 0x010B)
{
    // It's a 32-bit DLL.  96 bytes of COFF fields.
    // Subtract 2 for the magic number we just read.
    fs.Position += (96 - 2);
}
else if (coffMagic == 0x020B)
{
    // It's a 64-bit DLL.  112 bytes of COFF fields.
    // Subtract 2 for the magic number we just read.
    fs.Position += (112 - 2);
}

2
面对过去相同的问题,我采用了你的反射方法,因为另一种选择是手动读取PE头文件,就像这样。对于我的情况来说,这似乎有点过度,但它可能对你有用。

0

您没有说明您是需要在代码中实现这个功能,还是只是想知道您系统上查看的文件是否为 .NET 程序集(您可能认为需要自己编写代码才能判断)。如果是后者,您可以使用 Dependency Walker 工具来查看文件是否依赖 MSCOREE.dll,这是 .Net 运行时引擎。


谢谢,但我需要这个来避免反射异常。 - schoetbi

0
你可以使用类似以下的代码:
        AssemblyName assemblyName = null;

        try
        {
            assemblyName = AssemblyName.GetAssemblyName(filename);
        }
        catch (System.IO.FileNotFoundException ex)
        {
            throw new Exception("File not found!", ex);
        }
        catch (System.BadImageFormatException ex)
        {
            throw new Exception("File is not an .Net Assembly.", ex);
        }

请也查看一下:https://msdn.microsoft.com/zh-cn/library/ms173100.aspx

0

更新2020:

这应该是最简单的方法来检测一个DLL是否是.NET库:

public bool IsNetAssembly(string fileName)
{
    try
    {
        AssemblyName.GetAssemblyName(fileName);
    }
    catch (BadImageFormatException)
    {
        // not a .Net Assembly
         return false;
    }
    
    return true;
}

-2
你可以从文件中读取前两个字节,如果这两个字节是"MZ",那么尝试读取程序集名称来确定程序集的有效性(microsoft slow way)。
    public static bool isValidAssembly (string sFileName)
    {
        try
        {
            using (FileStream fs = File.OpenRead(sFileName))
            {
                if ((fs.ReadByte() != 'M') || (fs.ReadByte() != 'Z'))
                {
                    fs.Close();
                    return false;
                }
                fs.Close();
            }

            // http://msdn.microsoft.com/en-us/library/ms173100.aspx
            object foo = SR.AssemblyName.GetAssemblyName(sFileName);
            return true;
        }
        catch 
        {
            return false;
        }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接