如何确定一个.NET程序集是为x86还是x64构建的?

372

我有一个任意的.NET程序集列表。

我需要编程方式检查每个DLL是否是为x86构建的(而不是x64或Any CPU)。这是否可能?


2
你可能也想查看这个链接:check-if-unmanaged-dll-is-32-bit-or-64-bit - Matt
2
在 CorFlags 的后续版本中,对应于 .NET 4.5,"32BIT" 被 "32BITREQ" 和 "32BITPREF" 取代。 - Peter Mortensen
如何确定.NET程序集是为x86还是x64构建的? - user12031933
16个回答

355

看一下System.Reflection.AssemblyName.GetAssemblyName(string assemblyFile)

你可以从返回的AssemblyName实例中查看程序集元数据:

使用PowerShell:

[36] C:\> [reflection.assemblyname]::GetAssemblyName("${pwd}\Microsoft.GLEE.dll") | fl
名称 : Microsoft.GLEE 版本 : 1.0.0.0 CultureInfo : CodeBase : file:///C:/projects/powershell/BuildAnalyzer/... EscapedCodeBase : file:///C:/projects/powershell/BuildAnalyzer/... 处理器架构 : MSIL 标志 : PublicKey 哈希算法 : SHA1 版本兼容性 : SameMachine KeyPair : 完整名称 : Microsoft.GLEE, Version=1.0.0.0, Culture=neut...

在这里,ProcessorArchitecture标识目标平台。

  • Amd64: 基于x64架构的64位处理器。
  • Arm: ARM处理器。
  • IA64: 仅支持64位Intel Itanium处理器。
  • MSIL: 对处理器和字长中立。
  • X86: 32位Intel处理器,可以是本地的,也可以是在64位平台上的Windows on Windows环境中(WoW64)。
  • None: 处理器和字长的组合未知或未指定。

我在这个例子中使用PowerShell调用了该方法。


65
抱歉,这是一个愚蠢的问题,但这个东西怎么能告诉你它是x86架构的呢? - George Mauer
58
ProcessorArchitecture 字段是一个枚举类型;在上面的示例中,它设置为 MSIL,表示“与处理器和每字位数无关的中立状态”,其他可用的值包括 X86、IA64 和 Amd64。请参阅 http://msdn.microsoft.com/en-us/library/system.reflection.processorarchitecture.aspx 了解更多细节。 - Brian Gillespie
3
我在尝试使用PowerShell时遇到了以下错误:Exception calling "GetAssemblyName" with "1" argument(s): "Could not load file or assembly '[DLLName].dll' or one of its dependencies. The system cannot find the file specified."(是的,我已经正确拼写了它)。 - PeterX
5
有时候进程的当前目录与当前提供程序的目录不同(这就是我假设 DLL 存在的地方),因此建议尝试使用 [reflection.assemblyname]::GetAssemblyName("${pwd}\name.dll") - x0n
3
还有一个需要注意的问题是,如果你从互联网上下载了DLL文件,可能会忘记“解除阻止”。使用unblock-file命令,或者在资源管理器中右键点击/属性/解除阻止。如果你当前的会话中已经失败了一次,那么你需要重新启动shell才能识别未被阻止的状态(这要归咎于Internet Explorer - 是的,真的)。 - x0n
显示剩余5条评论

237

您可以使用CorFlags CLI工具(例如,C:\ Program Files \ Microsoft SDKs \ Windows \ v7.0 \ Bin \ CorFlags.exe)来确定程序集的状态,根据其输出和将程序集作为二进制资产打开,您应该能够确定需要查找的位置,以确定32BIT标志是否设置为1(x86)或0(任何CPUx64,取决于PE):

Option    | PE    | 32BIT
----------|-------|---------
x86       | PE32  | 1
Any CPU   | PE32  | 0
x64       | PE32+ | 0

博客文章使用.NET进行x64开发提供了一些关于corflags的信息。

更好的是,您可以使用Module.GetPEKind确定程序集是否为PortableExecutableKindsPE32Plus(64位)、Required32Bit(32位和WoW)或ILOnly(任何CPU),以及其他属性。


1
看到您的更新后,使用GetPEKind似乎是正确的方法。我已将您的回答标记为答案。 - Judah Gabriel Himango
9
在检查32位程序集时,GetPEKind在64位进程中失败。 - jjxtra
2
你必须从32位进程中调用 GetPEKind 函数。 - Ludwo
2
我安装了VS 2008、VS 2010、VS 2012和VS 2013。在C:\Program Files (x86)\Microsoft SDKs\Windows\的子文件夹中,我有8个CorFlags.exe文件。我应该使用哪一个? - Kiquenet
7
此答案所指出,在.NET 4.5中,32BIT标志已经被32BITREQ和32BITPREF替代。PE32/0/0和PE32/0/1分别表示AnyCPU和AnyCPU首选32位。 - angularsen
显示剩余7条评论

154

仅作澄清,CorFlags.exe是.NET Framework SDK的一部分。我在我的机器上安装了开发工具,最简单的方法来确定一个DLL是否只支持32位是:

  1. 打开Visual Studio命令提示符(在Windows中:开始菜单/程序/Microsoft Visual Studio/Visual Studio Tools/Visual Studio 2008命令提示符)

  2. CD到包含所询问的DLL的目录

  3. 像这样运行corflags:corflags MyAssembly.dll

你将会得到类似于以下的输出:

Microsoft (R) .NET Framework CorFlags Conversion Tool.  Version  3.5.21022.8
Copyright (c) Microsoft Corporation.  All rights reserved.

Version   : v2.0.50727
CLR Header: 2.5
PE        : PE32
CorFlags  : 3
ILONLY    : 1
32BIT     : 1
Signed    : 0

根据评论,上面的标志应该被解读如下:

  • 任何CPU:PE = PE32和32BIT = 0
  • x86:PE = PE32和32BIT = 1
  • 64位:PE = PE32+和32BIT = 0

13
这似乎已经有所改变;现在corflags显示32BITREQ32BITPREF,而不是单独的32BIT值。 - O. R. Mapper
1
微软.NET 4.5引入了一个新选项,即“任何CPU 32位首选项”。这里有详细信息。 - RBT
2
“Visual Studio命令提示符”现在被称为“Visual Studio 2019开发人员命令提示符”。 - Uwe Keim

24

请自己编写。PE体系结构的核心自 Windows 95 实施以来并没有发生过重大改变。

以下是C#示例:

    public static ushort GetPEArchitecture(string pFilePath)
    {
        ushort architecture = 0;
        try
        {
            using (System.IO.FileStream fStream = new System.IO.FileStream(pFilePath, System.IO.FileMode.Open, System.IO.FileAccess.Read))
            {
                using (System.IO.BinaryReader bReader = new System.IO.BinaryReader(fStream))
                {
                    // Check the MZ signature
                    if (bReader.ReadUInt16() == 23117)
                    {
                        // Seek to e_lfanew.
                        fStream.Seek(0x3A, System.IO.SeekOrigin.Current);

                        // Seek to the start of the NT header.
                        fStream.Seek(bReader.ReadUInt32(), System.IO.SeekOrigin.Begin);

                        if (bReader.ReadUInt32() == 17744) // Check the PE\0\0 signature.
                        {
                            // Seek past the file header,
                            fStream.Seek(20, System.IO.SeekOrigin.Current);

                            // Read the magic number of the optional header.
                            architecture = bReader.ReadUInt16();
                        }
                    }
                }
            }
        }
        catch (Exception) { /* TODO: Any exception handling you want
                                     to do, personally I just take 0
                                     as a sign of failure */
        }

        // If architecture returns 0, there has been an error.
        return architecture;
    }
}

现在的常量为:
0x10B - PE32  format.
0x20B - PE32+ format.

但是使用这种方法可以允许新常量的可能性。只需根据需要验证返回值即可。


1
有趣,谢谢你附带解释的代码。Module.GetPEKind 可能是最简单的方法。但出于学习目的这很有帮助。谢谢。 - Judah Gabriel Himango
3
非常有趣,但当我使用 Any CPU 编译应用程序时,结果为 0x10B。这是错误的,因为我的应用程序在 x64 系统上运行。是否还有其他标志可以检查? - Samuel
GetPEArchitecture适用于使用.net 3.5、4.0、4.5和4.5.1编译的程序集吗?无论如何,我认为,在检查32位程序集时,Module.GetPEKind在64位进程中会失败。 - Kiquenet

23

来自JetBrains的DotPeek提供了快速轻松地查看msil(Any CPU)、x86和x64的方法:

DotPeek


9

1
这是最准确和有用的答案。 - Kirill Osenkov
截至目前,该链接仍然有效,但由于CodePlex即将关闭,最好在为时已晚之前采取适当的行动。 - Peter Mortensen
链接现在已经完全失效了:“嗯,我们无法找到该网站。我们无法连接到apichange.codeplex.com上的服务器。” - Peter Mortensen

5
以下是一个批处理文件,它将对当前工作目录和所有子目录中的所有DLL文件和EXE文件运行corflags.exe,解析结果并显示每个目标架构。
根据使用的corflags.exe版本,输出中的行项目将包括32BIT32BITREQ(和32BITPREF)。这两个中包含在输出中的关键行项目必须进行检查,以区分Any CPUx86。如果您使用的是旧版的corflags.exeWindows SDK v8.0A之前),则输出中只会出现32BIT行项目,正如过去的答案所指出的那样。否则,32BITREQ32BITPREF将替换它。
这里假设 corflags.exe 已在 %PATH% 中。最简单的方法是使用 开发人员命令提示符 来确保此项已完成。或者,您可以将其从 默认位置 复制过来。
如果对未管理的 DLL 或 EXE 文件运行以下批处理文件,则会错误地将其显示为 x86,因为实际从 Corflags.exe 输出的信息将是类似于以下错误消息:

corflags : error CF008 : 指定的文件没有有效的托管标头

@echo off

echo.
echo Target architecture for all exes and dlls:
echo.

REM For each exe and dll in this directory and all subdirectories...
for %%a in (.exe, .dll) do forfiles /s /m *%%a /c "cmd /c echo @relpath" > testfiles.txt

for /f %%b in (testfiles.txt) do (
    REM Dump corflags results to a text file
    corflags /nologo %%b > corflagsdeets.txt

   REM Parse the corflags results to look for key markers
   findstr /C:"PE32+">nul .\corflagsdeets.txt && (
      REM `PE32+` indicates x64
        echo %%~b = x64
    ) || (
      REM pre-v8 Windows SDK listed only "32BIT" line item,
      REM newer versions list "32BITREQ" and "32BITPREF" line items
        findstr /C:"32BITREQ  : 0">nul /C:"32BIT     : 0" .\corflagsdeets.txt && (
            REM `PE32` and NOT 32bit required indicates Any CPU
            echo %%~b = Any CPU
        ) || (
            REM `PE32` and 32bit required indicates x86
            echo %%~b = x86
        )
    )

    del corflagsdeets.txt
)

del testfiles.txt
echo.

5
[TestMethod]
public void EnsureKWLLibrariesAreAll64Bit()
{
    var assemblies = Assembly.GetExecutingAssembly().GetReferencedAssemblies().Where(x => x.FullName.StartsWith("YourCommonProjectName")).ToArray();
    foreach (var assembly in assemblies)
    {
        var myAssemblyName = AssemblyName.GetAssemblyName(assembly.FullName.Split(',')[0] + ".dll");
        Assert.AreEqual(ProcessorArchitecture.MSIL, myAssemblyName.ProcessorArchitecture);
    }
}

谢谢您,我们的一个应用程序必须构建为x86架构,添加单元测试可以确保构建服务器的构建库是32位的,并避免发生这些错误 :) - Mido

4

“源代码(zip)”下载包含一个项目文件,其中包含硬编码的路径,指向您计算机上未包含在下载中的文件 :( - Ben Voigt

2

一种更通用的方法是使用文件结构来确定位数和图像类型:

public static CompilationMode GetCompilationMode(this FileInfo info)
{
    if (!info.Exists)
        throw new ArgumentException($"{info.FullName} does not exist");

    var intPtr = IntPtr.Zero;
    try
    {
        uint unmanagedBufferSize = 4096;
        intPtr = Marshal.AllocHGlobal((int)unmanagedBufferSize);

        using (var stream = File.Open(info.FullName, FileMode.Open, FileAccess.Read))
        {
            var bytes = new byte[unmanagedBufferSize];
            stream.Read(bytes, 0, bytes.Length);
            Marshal.Copy(bytes, 0, intPtr, bytes.Length);
        }

        // Check DOS header magic number
        if (Marshal.ReadInt16(intPtr) != 0x5a4d)
            return CompilationMode.Invalid;

        // This will get the address for the WinNT header
        var ntHeaderAddressOffset = Marshal.ReadInt32(intPtr + 60);

        // Check WinNT header signature
        var signature = Marshal.ReadInt32(intPtr + ntHeaderAddressOffset);
        if (signature != 0x4550)
            return CompilationMode.Invalid;

        // Determine file bitness by reading magic from IMAGE_OPTIONAL_HEADER
        var magic = Marshal.ReadInt16(intPtr + ntHeaderAddressOffset + 24);

        var result = CompilationMode.Invalid;
        uint clrHeaderSize;
        if (magic == 0x10b)
        {
            clrHeaderSize = (uint)Marshal.ReadInt32(intPtr + ntHeaderAddressOffset + 24 + 208 + 4);
            result |= CompilationMode.Bit32;
        }
        else if (magic == 0x20b)
        {
            clrHeaderSize = (uint)Marshal.ReadInt32(intPtr + ntHeaderAddressOffset + 24 + 224 + 4);
            result |= CompilationMode.Bit64;
        }
        else return CompilationMode.Invalid;

        result |= clrHeaderSize != 0
            ? CompilationMode.CLR
            : CompilationMode.Native;

        return result;
    }
    finally
    {
        if (intPtr != IntPtr.Zero)
            Marshal.FreeHGlobal(intPtr);
    }
}

编译模式枚举
[Flags]
public enum CompilationMode
{
    Invalid = 0,
    Native = 0x1,
    CLR = Native << 1,
    Bit32 = CLR << 1,
    Bit64 = Bit32 << 1
}

源代码及其解释,请查看GitHub


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