342

I've got an arbitrary list of .NET assemblies.

I need to programmatically check if each DLL was built for x86 (as opposed to x64 or Any CPU). Is this possible?

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Judah Gabriel Himango
  • 55,559
  • 37
  • 152
  • 206
  • possible duplicate of [How can I determine for which platform an executable is compiled?](http://stackoverflow.com/questions/197951/how-can-i-determine-for-which-platform-an-executable-is-compiled) – nawfal Feb 25 '13 at 19:26
  • 2
    You might also want to check out this one: [check-if-unmanaged-dll-is-32-bit-or-64-bit](http://stackoverflow.com/questions/1001404/check-if-unmanaged-dll-is-32-bit-or-64-bit). – Matt May 11 '15 at 10:16
  • 2
    In later version of CorFlags, corresponding to .NET 4.5, ["32BIT" was replaced by "32BITREQ" and "32BITPREF".](http://stackoverflow.com/questions/18608785/how-to-interpret-the-corflags-flags/23614024#23614024). – Peter Mortensen Jun 28 '15 at 13:35
  • [How to Determine if a .NET Assembly was Built for x86 or x64?](https://www.codeproject.com/tips/759340/how-to-determine-if-a-net-assembly-was-built-for-x) – Olivier Rogier Jul 06 '20 at 17:37

16 Answers16

309

Look at System.Reflection.AssemblyName.GetAssemblyName(string assemblyFile)

You can examine assembly metadata from the returned AssemblyName instance:

Using PowerShell:

[36] C:\> [reflection.assemblyname]::GetAssemblyName("${pwd}\Microsoft.GLEE.dll") | fl

Name                  : Microsoft.GLEE
Version               : 1.0.0.0
CultureInfo           :
CodeBase              : file:///C:/projects/powershell/BuildAnalyzer/...
EscapedCodeBase       : file:///C:/projects/powershell/BuildAnalyzer/...
ProcessorArchitecture : MSIL
Flags                 : PublicKey
HashAlgorithm         : SHA1
VersionCompatibility  : SameMachine
KeyPair               :
FullName              : Microsoft.GLEE, Version=1.0.0.0, Culture=neut... 

Here, ProcessorArchitecture identifies target platform.

  • Amd64: A 64-bit processor based on the x64 architecture.
  • Arm: An ARM processor.
  • IA64: A 64-bit Intel Itanium processor only.
  • MSIL: Neutral with respect to processor and bits-per-word.
  • X86: A 32-bit Intel processor, either native or in the Windows on Windows environment on a 64-bit platform (WOW64).
  • None: An unknown or unspecified combination of processor and bits-per-word.

I'm using PowerShell in this example to call the method.

x0n
  • 47,695
  • 5
  • 84
  • 110
  • 61
    Forgive the stupid question - but what in this tells you that it's x86? – George Mauer Dec 13 '12 at 16:31
  • 54
    The ProcessorArchitecture field is an enumeration; in the above example it is set to MSIL, which means "Neutral with respect to processor and bits-per-word." Other values include X86, IA64, Amd64. See http://msdn.microsoft.com/en-us/library/system.reflection.processorarchitecture.aspx for more details. – Brian Gillespie Dec 13 '12 at 19:45
  • 1
    I get the following error trying to use 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."` (Yes, I've spelt it correctly). – PeterX Oct 29 '13 at 09:14
  • 4
    Try with `[reflection.assemblyname]::GetAssemblyName("${pwd}\name.dll")` as sometimes the process's current directory is not the same as the current provider's (which is where I assume the DLL is for you) – x0n Oct 29 '13 at 14:36
  • 2
    Another caveat to look out for is forgetting to "unblock" the DLL if you downloaded it from the internets. Use unblock-file, or right click/properties/unblock from explorer. You will need to restart the shell for it to recognize the unblocked status if you've already failed once in the current session (blame internet explorer for that - yes, really.) – x0n Jan 22 '14 at 20:16
  • What's is better: AssemblyName.ProcessorArchitecture, CorFlags, or PE reader http://stackoverflow.com/a/9767750/206730 ? – Kiquenet Mar 25 '14 at 14:47
  • 1
    In ASP.NET MVC code, there is a comment `// DevDiv 216459: This code originally used Assembly.GetName(), but that requires FileIOPermission, which isn't granted in medium trust. However, Assembly.FullName *is* accessible in medium trust.` Sadly, there's no way to read ProcessorArchitecture without using the `GetName instance method`; using `AssemblyName constructor`, the field is always set to `None`. – metadings Aug 13 '14 at 10:36
  • How does this work when the exe/com/dll file is not built using .NET framework ? An example would be `write.exe` from Windows 95, or `win386.dll` from Windows 3.1, or `himem.sys` from DOS 6.x . – Kraang Prime Aug 06 '16 at 02:42
  • 1
    @SamuelJackson The question clearly qualifies the input as being a .NET assembly. If it's not, that's a different question to solve. The code will fail to load the DLL, and will throw an exception. – x0n Aug 08 '16 at 14:41
227

You can use the CorFlags CLI tool (for instance, C:\Program Files\Microsoft SDKs\Windows\v7.0\Bin\CorFlags.exe) to determine the status of an assembly, based on its output and opening an assembly as a binary asset you should be able to determine where you need to seek to determine if the 32BIT flag is set to 1 (x86) or 0 (Any CPU or x64, depending on PE):

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

The blog post x64 Development with .NET has some information about corflags.

Even better, you can use Module.GetPEKind to determine whether an assembly is PortableExecutableKinds value PE32Plus (64-bit), Required32Bit (32-bit and WOW), or ILOnly (any CPU) along with other attributes.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
cfeduke
  • 22,322
  • 10
  • 59
  • 64
  • 1
    After seeing your update, using the GetPEKind seems to be the proper way to do this. I've marked yours as the answer. – Judah Gabriel Himango Nov 07 '08 at 05:38
  • Excellent tip about Module.GetPEKind, never knew about that until now. I've always used the `corflags` tool. – Scott Dorman Aug 15 '09 at 13:42
  • 9
    GetPEKind fails in a 64 bit process when checking 32 bit assemblies – jjxtra Apr 19 '10 at 16:25
  • 2
    You have to call GetPEKind from 32bit process – Ludwo Feb 18 '13 at 12:21
  • 2
    I install VS 2008, VS 2010, VS 2012 and VS 2013. I have 8 files CorFlags.exe in subfolders in C:\Program Files (x86)\Microsoft SDKs\Windows\. Which I should be use ? – Kiquenet Mar 25 '14 at 15:06
  • `md5sum` each executable to see if any of them are different (or just check the file size and modified date); I suspect any one of them will work just fine. – cfeduke Mar 25 '14 at 15:22
  • @cfeduke file size are differents for both version – Kiquenet Mar 25 '14 at 18:19
  • any "parser" for using corflags.exe programatically in C# (Process.Start and redirect output) ? Or better use http://stackoverflow.com/a/14936673/206730 ? – Kiquenet Mar 25 '14 at 20:27
  • @Ludwo any full source code sample using GetPEKind from 32 bit process?. PsychoDad says GetPEKind fails in a 64 bit process when checking 32 bit assemblies – Kiquenet Mar 25 '14 at 20:28
  • Check my answer below – Ludwo Mar 26 '14 at 11:42
  • 5
    As pointed out in [this answer](http://stackoverflow.com/a/23614024/134761), in .NET 4.5 there is 32BITREQ and 32BITPREF instead of the 32BIT flag. PE32/0/0 and PE32/0/1 are AnyCPU and AnyCPU 32-bit preferred, respectively. – angularsen Jan 30 '15 at 11:36
  • This just shows "Error: is not a valid Win32 application." – O. R. Mapper Mar 10 '15 at 14:09
146

Just for clarification, CorFlags.exe is part of the .NET Framework SDK. I have the development tools on my machine, and the simplest way for me determine whether a DLL is 32-bit only is to:

  1. Open the Visual Studio Command Prompt (In Windows: menu Start/Programs/Microsoft Visual Studio/Visual Studio Tools/Visual Studio 2008 Command Prompt)

  2. CD to the directory containing the DLL in question

  3. Run corflags like this: corflags MyAssembly.dll

You will get output something like this:

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

As per comments the flags above are to be read as following:

  • Any CPU: PE = PE32 and 32BIT = 0
  • x86: PE = PE32 and 32BIT = 1
  • 64-bit: PE = PE32+ and 32BIT = 0
Uwe Keim
  • 36,867
  • 50
  • 163
  • 268
JoshL
  • 10,168
  • 11
  • 52
  • 60
  • 12
    This seems to have changed meanwhile; corflags now displays `32BITREQ` and `32BITPREF` rather than a single `32BIT` value. – O. R. Mapper Mar 10 '15 at 14:11
  • 1
    Microsoft .NET 4.5 introduced a new option, Any CPU 32-bit Preferred. [Here](http://stackoverflow.com/a/23614024/465053) are the details. – RBT Apr 10 '17 at 12:02
  • The "Visual Studio Command Prompt" is nowadays called "[Visual Studio 2019 Developer Command Prompt](https://docs.microsoft.com/en-us/dotnet/framework/tools/developer-command-prompt-for-vs)". – Uwe Keim Oct 27 '19 at 08:31
22

How about you just write you own? The core of the PE architecture hasn't been seriously changed since its implementation in Windows 95. Here's a C# example:

    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))
                {
                    if (bReader.ReadUInt16() == 23117) //check the MZ signature
                    {
                        fStream.Seek(0x3A, System.IO.SeekOrigin.Current); //seek to e_lfanew.
                        fStream.Seek(bReader.ReadUInt32(), System.IO.SeekOrigin.Begin); //seek to the start of the NT header.
                        if (bReader.ReadUInt32() == 17744) //check the PE\0\0 signature.
                        {
                            fStream.Seek(20, System.IO.SeekOrigin.Current); //seek past the file header,
                            architecture = bReader.ReadUInt16(); //read the magic number of the optional header.
                        }
                    }
                }
            }
        }
        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;
    }
}

Now the current constants are:

0x10B - PE32  format.
0x20B - PE32+ format.

But with this method it allows for the possibilities of new constants, just validate the return as you see fit.

Jason
  • 221
  • 2
  • 2
  • 1
    Interesting, thanks for the code with explanation. Module.GetPEKind is probably the easiest path. But this is helpful for learning's sake. Thanks. – Judah Gabriel Himango Mar 19 '12 at 17:47
  • 3
    Very interesting but when I have an application compiled with Any CPU, the result is 0x10B. This is wrong because my application is run in a x64 system. Is there any other flag to check? – Samuel Jul 24 '12 at 17:41
  • GetPEArchitecture works for assemblies compiled using .net 3.5, 4.0, 4.5 and 4.5.1 ? Anyways, I think, Module.GetPEKind fails in a 64 bit process when checking 32 bit assemblies. – Kiquenet Mar 25 '14 at 20:51
10

DotPeek from JetBrains provides quick and easy way to see msil(anycpu), x86, x64 dotPeek

neminem
  • 2,582
  • 5
  • 25
  • 35
9

Try to use CorFlagsReader from this project at CodePlex. It has no references to other assemblies and it can be used as is.

Ludwo
  • 5,783
  • 4
  • 29
  • 48
6
[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);
    }
}
Morgan Mellor
  • 61
  • 1
  • 2
  • Thanks for this, One of our applications has to be built as x86, adding a unit test ensures that the build server's build libraries will be 32bit and avoids those mistakes from happening :) – Mido Mar 05 '15 at 09:00
5

Below is a batch file that will run corflags.exe against all dlls and exes in the current working directory and all sub-directories, parse the results and display the target architecture of each.

Depending on the version of corflags.exe that is used, the line items in the output will either include 32BIT, or 32BITREQ (and 32BITPREF). Whichever of these two is included in the output is the critical line item that must be checked to differentiate between Any CPU and x86. If you are using an older version of corflags.exe (pre Windows SDK v8.0A), then only the 32BIT line item will be present in the output, as others have indicated in past answers. Otherwise 32BITREQ and 32BITPREF replace it.

This assumes corflags.exe is in the %PATH%. The simplest way to ensure this is to use a Developer Command Prompt. Alternatively you could copy it from it's default location.

If the batch file below is run against an unmanaged dll or exe, it will incorrectly display it as x86, since the actual output from Corflags.exe will be an error message similar to:

corflags : error CF008 : The specified file does not have a valid managed header

@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.
Community
  • 1
  • 1
Eric Lease
  • 3,894
  • 1
  • 24
  • 43
3

I've cloned a super handy tool that adds a context menu entry for assemblies in the windows explorer to show all available info:

Download here: https://github.com/tebjan/AssemblyInformation/releases

enter image description here

thalm
  • 2,449
  • 1
  • 27
  • 45
  • The "source code (zip)" download has a project file containing hard-coded paths to files on your computer and not included in the download :( – Ben Voigt Jul 31 '20 at 17:21
2

One more way would be to use dumpbin from the Visual Studio tools on DLL and look for the appropriate output

dumpbin.exe /HEADERS <your dll path>
    FILE HEADER VALUE
                 14C machine (x86)
                   4 number of sections
            5885AC36 time date stamp Mon Jan 23 12:39:42 2017
                   0 file pointer to symbol table
                   0 number of symbols
                  E0 size of optional header
                2102 characteristics
                       Executable
                       32 bit word machine
                       DLL

Note: Above o/p is for 32bit dll

One more useful option with dumpbin.exe is /EXPORTS, It will show you the function exposed by the dll

dumpbin.exe /EXPORTS <PATH OF THE DLL>
Ayush joshi
  • 297
  • 1
  • 4
  • 14
2

More generic way - use file structure to determine bitness and image type:

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);
    }
}

Compilation mode enumeration

[Flags]
public enum CompilationMode
{
    Invalid = 0,
    Native = 0x1,
    CLR = Native << 1,
    Bit32 = CLR << 1,
    Bit64 = Bit32 << 1
}

Source code with explanation at GitHub

BlackGad
  • 51
  • 4
1

cfeduke notes the possibility of calling GetPEKind. It's potentially interesting to do this from PowerShell.

Here, for example, is code for a cmdlet that could be used: https://stackoverflow.com/a/16181743/64257

Alternatively, at https://stackoverflow.com/a/4719567/64257 it is noted that "there's also the Get-PEHeader cmdlet in the PowerShell Community Extensions that can be used to test for executable images."

Community
  • 1
  • 1
Chris
  • 9,496
  • 6
  • 43
  • 55
1

A more advanced application for that you can find here: CodePlex - ApiChange

Examples:

C:\Downloads\ApiChange>ApiChange.exe -CorFlags c:\Windows\winhlp32.exe
File Name; Type; Size; Processor; IL Only; Signed
winhlp32.exe; Unmanaged; 296960; X86

C:\Downloads\ApiChange>ApiChange.exe -CorFlags c:\Windows\HelpPane.exe
File Name; Type; Size; Processor; IL Only; Signed
HelpPane.exe; Unmanaged; 733696; Amd64
Wernfried Domscheit
  • 38,841
  • 5
  • 50
  • 81
1

Another way to check the target platform of a .NET assembly is inspecting the assembly with .NET Reflector...

@#~#€~! I've just realized that the new version is not free! So, correction, if you have a free version of .NET reflector, you can use it to check the target platform.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
sakito
  • 1,051
  • 12
  • 17
  • 9
    Use [ILSpy](http://wiki.sharpdevelop.net/ILSpy.ashx), it's a basic open source app that does much the same things as Reflector – Binary Worrier Aug 05 '11 at 11:01
0

An alternative to already mentioned tools is Telerik JustDecompile (free tool) which will display the information next to the assembly name:

Any or x86 or x64 information in Telerik

Alexei - check Codidact
  • 17,850
  • 12
  • 118
  • 126
0

I like ILSpy tool guys. It shows not only architecture, but target framework as well:

// linq2db, Version=3.0.0.0, Culture=neutral, PublicKeyToken=e41013125f9e410a
// Global type: <Module>
// Architecture: AnyCPU (64-bit preferred)
// Runtime: v4.0.30319
// This assembly is signed with a strong name key.
// This assembly was compiled using the /deterministic option.
// Hash algorithm: SHA1

So it is possible to determine if it is .Net Core 2.1, .Net Framework 4.6 or any other one:

target framework

Maxim
  • 10,077
  • 5
  • 26
  • 43