Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

A Fast Way to Get at the File's Version

3.80/5 (4 votes)
25 Jan 2008CPOL 1  
Alternative to Microsoft's FileVersionInfo class

Introduction

Microsoft's FileVersionInfo class is very convenient for getting at the VERSION resource of an executable. However the performance of this class is quite slow. If you have a need to get file versions of a lot of files, you need a much faster way.

Using the Code

The code uses a technique from Matt Pietrek's PEDUMP program to map the executable into memory. However instead of walking the directory structure of the PE format, the code uses standard WinAPI Resource functions to get at the VERSION resource. An interesting (dirty) technique has been used to increment the starting address of the file by one byte to get at the same memory pointer returned by LoadLibraryEx which is the function that should be used to work with resources. The problem with LoadLibraryEx is that it is still slow compared to mapping the file into memory (event with the LOAD_LIBRARY_AS_DATAFILE flag). The code is also useful as a demonstration of various Interop techniques.

C#
class FastFileVersionInfo
{
  [StructLayout(LayoutKind.Sequential)]
  struct IMAGE_DOS_HEADER
  {
    public UInt16 e_magic;         // Magic number
    /*
    public UInt16 e_cblp;          // Bytes on last page of file
    public UInt16 e_cp;            // Pages in file
    public UInt16 e_crlc;          // Relocations
    public UInt16 e_cparhdr;       // Size of header in paragraphs
    public UInt16 e_minalloc;      // Minimum extra paragraphs needed
    public UInt16 e_maxalloc;      // Maximum extra paragraphs needed
    public UInt16 e_ss;            // Initial (relative) SS value
    public UInt16 e_sp;            // Initial SP value
    public UInt16 e_csum;          // Checksum
    public UInt16 e_ip;            // Initial IP value
    public UInt16 e_cs;            // Initial (relative) CS value
    public UInt16 e_lfarlc;        // File address of relocation table
    public UInt16 e_ovno;          // Overlay number
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
    public UInt16[] e_res1;        // Reserved words
    public UInt16 e_oemid;         // OEM identifier (for e_oeminfo)
    public UInt16 e_oeminfo;       // OEM information; e_oemid specific
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
    public UInt16[] e_res2;        // Reserved words
    public Int32 e_lfanew;         // File address of new EXE header
    */
  }

  [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
  struct VS_VERSIONINFO
  {
    public UInt16 wLength;
    public UInt16 wValueLength;
    public UInt16 wType;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 15)]
    public string szKey;
    public UInt16 Padding1;
  }

  static Int32 nPaddingOffset = 
        Marshal.OffsetOf(typeof(VS_VERSIONINFO), "Padding1").ToInt32();

  [StructLayout(LayoutKind.Sequential)]
  struct VS_FIXEDFILEINFO
  {
    public UInt32 dwSignature;
    public UInt32 dwStrucVersion;
    public UInt32 dwFileVersionMS;
    public UInt32 dwFileVersionLS;
    public UInt32 dwProductVersionMS;
    public UInt32 dwProductVersionLS;
    public UInt32 dwFileFlagsMask;
    public UInt32 dwFileFlags;
    public UInt32 dwFileOS;
    public UInt32 dwFileType;
    public UInt32 dwFileSubtype;
    public UInt32 dwFileDateMS;
    public UInt32 dwFileDateLS;
  };

  [DllImport("Kernel32.dll", SetLastError = true)]
  static extern IntPtr CreateFile(string fileName, UInt32 fileAccess, 
        UInt32 fileShare, IntPtr securityAttributes, UInt32 creationDisposition, 
        UInt32 flags, IntPtr template);

  [DllImport("kernel32.dll", SetLastError = true)]
  static extern IntPtr CreateFileMapping
        (IntPtr hFile, IntPtr lpFileMappingAttributes, UInt32 flProtect, 
        UInt32 dwMaximumSizeHigh, UInt32 dwMaximumSizeLow, string lpName);

  [DllImport("kernel32.dll", SetLastError = true)]
  static extern IntPtr MapViewOfFile
        (IntPtr hFileMappingObject, UInt32 dwDesiredAccess, UInt32 dwFileOffsetHigh, 
        UInt32 dwFileOffsetLow, IntPtr dwNumberOfBytesToMap);

  [DllImport("kernel32.dll", SetLastError = true)]
  [return: MarshalAs(UnmanagedType.Bool)]
  static extern bool CloseHandle(IntPtr hObject);

  [DllImport("kernel32.dll", SetLastError = true)]
  static extern bool UnmapViewOfFile(IntPtr lpBaseAddress);

  [DllImport("kernel32.dll", SetLastError = true)]
  static extern IntPtr FindResource(IntPtr hModule, string lpName, Int32 lpType);

  [DllImport("kernel32.dll", SetLastError = true)]
  static extern IntPtr LoadResource(IntPtr hModule, IntPtr hResInfo);

  [DllImport("kernel32.dll")]
  static extern IntPtr LockResource(IntPtr hResData);

  const UInt32 GENERIC_READ = 0x80000000;
  const UInt32 FILE_SHARE_READ = 0x00000001;
  const UInt32 OPEN_EXISTING = 3;
  const UInt32 FILE_ATTRIBUTE_NORMAL = 0x00000080;
  const Int32  INVALID_HANDLE_VALUE = -1;
  const UInt32 PAGE_READONLY = 0x02;
  const UInt32 FILE_MAP_READ = 0x0004;
  const UInt16 IMAGE_DOS_SIGNATURE = 0x5A4D;
  const Int32  RT_VERSION = 16;

  public static string GetVersion(string sFile)
  {
    String sVersion = null;

    IntPtr hFile = CreateFile(sFile, GENERIC_READ, FILE_SHARE_READ, IntPtr.Zero, 
        OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, IntPtr.Zero);

    if (hFile.ToInt32() == INVALID_HANDLE_VALUE)
      throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());

    IntPtr hFileMapping = 
        CreateFileMapping(hFile, IntPtr.Zero, PAGE_READONLY, 0, 0, null);

    if (hFileMapping.ToInt32() == 0)
    {
      int returnCode = Marshal.GetLastWin32Error();

      CloseHandle(hFile);

      throw new System.ComponentModel.Win32Exception(returnCode);
    }

    IntPtr pMappedFileBase = 
        MapViewOfFile(hFileMapping, FILE_MAP_READ, 0, 0, IntPtr.Zero);

    if (pMappedFileBase.ToInt32() == 0)
    {
      int returnCode = Marshal.GetLastWin32Error();

      CloseHandle(hFileMapping);
      CloseHandle(hFile);

      throw new System.ComponentModel.Win32Exception(returnCode);
    }

    IMAGE_DOS_HEADER DOSHeader = 
        (IMAGE_DOS_HEADER) Marshal.PtrToStructure
        (pMappedFileBase, typeof(IMAGE_DOS_HEADER));

    if (DOSHeader.e_magic == IMAGE_DOS_SIGNATURE)
    {
      IntPtr hModule = new IntPtr(pMappedFileBase.ToInt32() + 1);

      IntPtr hRes = FindResource(hModule, "#1", RT_VERSION);

      if (hRes.ToInt32() != 0)
      {
        IntPtr hGlobal = LoadResource(hModule, hRes);

        if (hGlobal.ToInt32() != 0)
        {
          IntPtr lpRes = LockResource(hGlobal);

          if (lpRes.ToInt32() != 0)
          {
            VS_VERSIONINFO VersionInfo = 
                (VS_VERSIONINFO) Marshal.PtrToStructure(lpRes, typeof(VS_VERSIONINFO));

            if (VersionInfo.wValueLength > 0)
            {
              IntPtr pFI = new IntPtr(lpRes.ToInt32() + nPaddingOffset);

              while ((pFI.ToInt32() & 0x04) != 0)
                pFI = new IntPtr(pFI.ToInt32() + 2);

              VS_FIXEDFILEINFO FileInfo = (VS_FIXEDFILEINFO) Marshal.PtrToStructure
                        (pFI, typeof(VS_FIXEDFILEINFO));

              UInt32 v1 = (FileInfo.dwFileVersionMS & 0xffff0000) >> 16;
              UInt32 v2 = FileInfo.dwFileVersionMS & 0x0000ffff;
              UInt32 v3 = (FileInfo.dwFileVersionLS & 0xffff0000) >> 16;
              UInt32 v4 = FileInfo.dwFileVersionLS & 0x0000ffff;

              sVersion = v1.ToString() + '.' + v2.ToString() + '.' + 
                    v3.ToString() + '.' + v4.ToString();
            }
          }
        }
      }
    }

    UnmapViewOfFile(pMappedFileBase);
    CloseHandle(hFileMapping);
    CloseHandle(hFile);

    return sVersion;
  }
}

History

  • 24th January, 2008: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)