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.
class FastFileVersionInfo
{
[StructLayout(LayoutKind.Sequential)]
struct IMAGE_DOS_HEADER
{
public UInt16 e_magic;
}
[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