And how to determine whether it’s managed or unmanaged.

With proliferation of libraries around the .NET world, sometimes you need to determine how the DLL was compiled or even whether it’s managed or not.

Just give me the TL;DR

Download the Bitness command line utility, or the source code from which it was built.  Usage: the app takes a single command line parameter.  If the parameter is a file, it’ll interrogate that file.  if the parameter is a folder, it’ll loop through all the DLLs in that folder.  In the absense of any command line parameters, it’ll process all files in the current directory.  Requirements: .NET 4.0 Framework.

Is it managed or not?

This one is easy enough.  The PE (Portable Executable) header present in all Windows executables reveals whether the DLL is managed or not.  I am not going to go over the PE format. 

   1:  private static bool IsManagedAssembly(string fileName)
   2:  {
   3:      Stream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
   4:   
   5:      try
   6:      {
   7:          BinaryReader reader = new BinaryReader(fs);
   8:          //PE Header starts @ 0x3C (60). Its a 4 byte header.
   9:          fs.Position = 0x3C;
  10:          uint peHeader = reader.ReadUInt32();
  11:          //Moving to PE Header start location...
  12:          fs.Position = peHeader;
  13:          uint peHeaderSignature = reader.ReadUInt32();
  14:          ushort machine = reader.ReadUInt16();
  15:          ushort sections = reader.ReadUInt16();
  16:          uint timestamp = reader.ReadUInt32();
  17:          uint pSymbolTable = reader.ReadUInt32();
  18:          uint noOfSymbol = reader.ReadUInt32();
  19:          ushort optionalHeaderSize = reader.ReadUInt16();
  20:          ushort characteristics = reader.ReadUInt16();
  21:   
  22:          long posEndOfHeader = fs.Position;
  23:          ushort magic = reader.ReadUInt16();
  24:   
  25:          int off = 0x60; // Offset to data directories for 32Bit PE images
  26:          // See section 3.4 of the PE format specification.
  27:          if (magic == 0x20b) //0x20b == PE32+ (64Bit), 0x10b == PE32 (32Bit)
  28:          {
  29:              off = 0x70;  // Offset to data directories for 64Bit PE images
  30:          }
  31:          fs.Position = posEndOfHeader;
  32:   
  33:          uint[] dataDictionaryRVA = new uint[16];
  34:          uint[] dataDictionarySize = new uint[16];
  35:          ushort dataDictionaryStart = Convert.ToUInt16(Convert.ToUInt16(fs.Position) + off);
  36:   
  37:          fs.Position = dataDictionaryStart;
  38:   
  39:          for (int i = 0; i < 15; i++)
  40:          {
  41:              dataDictionaryRVA[i] = reader.ReadUInt32();
  42:              dataDictionarySize[i] = reader.ReadUInt32();
  43:          }
  44:          if (dataDictionaryRVA[14] == 0)
  45:          {
  46:              fs.Close();
  47:              return false;
  48:          }
  49:          else
  50:          {
  51:              fs.Close();
  52:              return true;
  53:          }
  54:      }
  55:      catch (Exception)
  56:      {
  57:          return false;
  58:      }
  59:      finally
  60:      {
  61:          fs.Close();
  62:      }
  63:  }

Great, it’s Managed.  How was it compiled?

If the DLL is managed, we now need to determine whether the assembly is compiled for x86, x64 or AnyCPU.  For that will load the assembly into memory and interrogate its PE type.

   1:  private static PortableExecutableKinds GetManagedType(string fileName)
   2:  {
   3:      Assembly assembly = Assembly.ReflectionOnlyLoadFrom(fileName);
   4:      PortableExecutableKinds peKind;
   5:      ImageFileMachine imageFileMachine;
   6:      assembly.ManifestModule.GetPEKind(out peKind, out imageFileMachine);
   7:      return peKind;
   8:  }

Note that instead of using Assembly.Load I am using Assembly.ReflectionOnlyLoadFrom.  The reason for this is that if you attempt to Assembly.Load a 64 bit assembly from a 32 bit process, you’ll get a BadFormatException error.  Instead we use Assembly.ReflectionOnlyLoadFrom which only interrogates its PE header.  From there it’s a simple matter of using the switch statement.

   1:  switch (peKind)
   2:  {
   3:      case PortableExecutableKinds.ILOnly:
   4:          Console.WriteLine("Managed AnyCPU");
   5:          break;
   6:      case PortableExecutableKinds.Required32Bit:
   7:          Console.WriteLine("Managed x86");
   8:          break;
   9:      case PortableExecutableKinds.PE32Plus:
  10:          Console.WriteLine("Managed x64");
  11:          break;
  12:      case PortableExecutableKinds.Unmanaged32Bit:
  13:          Console.WriteLine("Managed but states that it's unmanaged???");
  14:          break;
  15:      case PortableExecutableKinds.NotAPortableExecutableImage:
  16:          Console.WriteLine("Managed but cannot determine anything else");
  17:          break;
  18:      default:
  19:          throw new ArgumentOutOfRangeException();
  20:  }


The last two enum members should be extremely rare and indicate that the assembly is somewhat corrupted.

It’s Native!  64-bit or 32?

If the DLL happens to be native, we can determine whether it’s compiled for 64 bit or 32.  (There is no such thing as AnyCPU in the native world). 

   1:  private static MachineType GetDllMachineType(string dllPath)
   2:  {
   3:      //see http://www.microsoft.com/whdc/system/platform/firmware/PECOFF.mspx
   4:      //offset to PE header is always at 0x3C
   5:      //PE header starts with "PE\0\0" =  0x50 0x45 0x00 0x00
   6:      //followed by 2-byte machine type field (see document above for enum)
   7:      FileStream fs = new FileStream(dllPath, FileMode.Open, FileAccess.Read);
   8:      BinaryReader br = new BinaryReader(fs);
   9:      fs.Seek(0x3c, SeekOrigin.Begin);
  10:      Int32 peOffset = br.ReadInt32();
  11:      fs.Seek(peOffset, SeekOrigin.Begin);
  12:      UInt32 peHead = br.ReadUInt32();
  13:      if (peHead != 0x00004550) // "PE\0\0", little-endian
  14:          throw new Exception("Can't find PE header");
  15:      MachineType machineType = (MachineType)br.ReadUInt16();
  16:      br.Close();
  17:      fs.Close();
  18:      return machineType;
  19:  }
  20:   
  21:  private static bool? UnmanagedDllIs64Bit(string dllPath)
  22:  {
  23:      switch (GetDllMachineType(dllPath))
  24:      {
  25:          case MachineType.IMAGE_FILE_MACHINE_AMD64:
  26:          case MachineType.IMAGE_FILE_MACHINE_IA64:
  27:              return true;
  28:          case MachineType.IMAGE_FILE_MACHINE_I386:
  29:              return false;
  30:          default:
  31:              return null;
  32:      }
  33:  }
The UnmanagedDllIs64Bit method tells us whether the DLL is 64 bit or not.  Keep in mind that in addition to x86 and x64, Windows through its history supported many other architectures, for which you can interrogate the return value of GetDllMachineType method.  What other ones?  PowerPC, MIPS, DEC/Compaq Alpha, the upcoming ARM port of windows and many others.