开发者

Retrieving dll version info via Win32 - VerQueryValue(...) crashes under Win7 x64

The respected open source .NET wrapper implementation (SharpBITS) of Windows BITS services fails identifying the underlying BITS version under Win7 x64.

Here is the source code that fails. NativeMethods are native Win32 calls wrapped by .NET methods and decorated via DllImport attribute.

private static BitsVersion GetBitsVersion()
    {
        try
        {
            string fileName = Path.Combine(
                     System.Environment.SystemDirectory, "qmgr.dll");
            int handle = 0;
            int size = NativeMethods.GetFileVersionInfoSize(fileName, 
                                                            out handle);
            if (size == 0) return BitsVersion.Bits0_0;
            byte[] buffer = new byte[size];
            if (!NativeMethods.GetFileVersionInfo(fileName, 
                                                  handle, 
                                                  size, 
                                                  buffer))
            {
                return BitsVersion.Bits0_0;
            }
            IntPtr su开发者_StackOverflow中文版bBlock = IntPtr.Zero;
            uint len = 0;
            if (!NativeMethods.VerQueryValue(buffer,
                              @"\VarFileInfo\Translation", 
                              out subBlock, 
                              out len))
            {
                return BitsVersion.Bits0_0;
            }

            int block1 = Marshal.ReadInt16(subBlock);
            int block2 = Marshal.ReadInt16((IntPtr)((int)subBlock + 2 ));
            string spv = string.Format(
                 @"\StringFileInfo\{0:X4}{1:X4}\ProductVersion", 
                 block1, 
                 block2);

            string versionInfo;
            if (!NativeMethods.VerQueryValue(buffer, 
                                             spv, 
                                             out versionInfo, 
                                             out len))
            {
                return BitsVersion.Bits0_0;
            }
...

The implementation follows the MSDN instructions by the letter. Still during the second VerQueryValue(...) call, the application crashes and kills the debug session without hesitation. Just a little more debug info right before the crash:

  • spv => "\StringFileInfo\040904B0\ProductVersion"
  • buffer => byte[1900] - full with binary data
  • block1 => 1033
  • block2 => 1200

I looked at the targeted "C:\Windows\System32\qmgr.dll" file (The implementation of BITS) via Windows. It says that the Product Version is 7.5.7600.16385. Instead of crashing, this value should return in the verionInfo string. Any advice?


The answer of Nobugz does point out a very valid issue (big thanx for that!), but the final killer was a .NET bug: marshaling strings in this scenario fails under the x64 .NET implementation. The bug is fixed in .NET4.0. Here is the issue reported, as well as Microsoft's answer.

The recommended workaround is retrieve an IntPtr instead of the string as output and marshaling the string manually. So the final code (including the fix of Nobugz):

int block1 = Marshal.ReadInt16(subBlock);
int block2 = Marshal.ReadInt16(subBlock, 2);
string spv = string.Format(@"\StringFileInfo\{0:X4}{1:X4}\ProductVersion", 
                             block1, block2);

IntPtr versionInfoPtr;
if (!NativeMethods.VerQueryValue(buffer, spv, out versionInfoPtr, out len))
{
     return BitsVersion.Bits0_0;
}
string versionInfo = Marshal.PtrToStringAuto(versionInfoPtr);


I don't know if something's wrong with your code, but have you considered using the FileVersionInfo class rather than P/Invoke API calls ? It's easier to use and less error prone...


I looked at SharpBITS about a year ago. I remember seeing a Big Bug back then which would make it unsuitable on 64-bit operating systems. Can't remember the exact bug, probably a bad P/Invoke declaration. This code snippet is definitely wrong:

int block2 = Marshal.ReadInt16((IntPtr)((int)subBlock + 2 ));

An IntPtr cannot be cast to an int in x64 mode. It must look like this:

int block2 = Marshal.ReadInt16((IntPtr)((long)subBlock + 2 ));

Or much better:

int block2 = Marshal.ReadInt16(subBlock, 2); 

I'd strongly recommend you force your app to run in 32-bit mode if you use this library to avoid these problems. Project + Properties, Build tab, Platform Target = x86. You can notify the author here.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜