37

I have a version resource in my resources in a C++ project which contains version number, copyright and build details. Is there an easy way to access this at run-time to populate my help/about dialog as I am currently maintaining seperate const values of this information. Ideally, the solution should work for Windows/CE mobile and earlier versions of Visual C++ (6.0 upwards).

SmacL
  • 22,555
  • 12
  • 95
  • 149
  • You should note that using `_get_pgmptr()` or `_get_wpgmptr()` is better than `GetModuleFileName(NULL, szFilename, MAX_PATH)`; saves you from unnecessary allocation and clarify your intention. – q12 Jan 08 '17 at 13:00

7 Answers7

38

This is an edited version of my original answer.

bool GetProductAndVersion(CStringA & strProductName, CStringA & strProductVersion)
{
    // get the filename of the executable containing the version resource
    TCHAR szFilename[MAX_PATH + 1] = {0};
    if (GetModuleFileName(NULL, szFilename, MAX_PATH) == 0)
    {
        TRACE("GetModuleFileName failed with error %d\n", GetLastError());
        return false;
    }

    // allocate a block of memory for the version info
    DWORD dummy;
    DWORD dwSize = GetFileVersionInfoSize(szFilename, &dummy);
    if (dwSize == 0)
    {
        TRACE("GetFileVersionInfoSize failed with error %d\n", GetLastError());
        return false;
    }
    std::vector<BYTE> data(dwSize);

    // load the version info
    if (!GetFileVersionInfo(szFilename, NULL, dwSize, &data[0]))
    {
        TRACE("GetFileVersionInfo failed with error %d\n", GetLastError());
        return false;
    }

    // get the name and version strings
    LPVOID pvProductName = NULL;
    unsigned int iProductNameLen = 0;
    LPVOID pvProductVersion = NULL;
    unsigned int iProductVersionLen = 0;

    // replace "040904e4" with the language ID of your resources
    if (!VerQueryValue(&data[0], _T("\\StringFileInfo\\040904e4\\ProductName"), &pvProductName, &iProductNameLen) ||
        !VerQueryValue(&data[0], _T("\\StringFileInfo\\040904e4\\ProductVersion"), &pvProductVersion, &iProductVersionLen))
    {
        TRACE("Can't obtain ProductName and ProductVersion from resources\n");
        return false;
    }

    strProductName.SetString((LPCSTR)pvProductName, iProductNameLen);
    strProductVersion.SetString((LPCSTR)pvProductVersion, iProductVersionLen);

    return true;
}
Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • Where do you find the language ID? – Jeff B Mar 23 '16 at 18:56
  • @JeffB I'd start at the MSDN page [Language Identifier Constants and Strings](https://msdn.microsoft.com/en-us/library/windows/desktop/dd318693(v=vs.85).aspx). – Mark Ransom Mar 23 '16 at 20:05
  • Both your last two lines gives me a `'void ATL::CSimpleStringT::SetString(const char *,int)' : cannot convert argument 1 from 'LPCTSTR' to 'const char *'.`. I've `#include ` – Patrizio Bertoni Jun 01 '16 at 09:26
  • 1
    @PatrizioBertoni use `LPCSTR` instead, sorry. – Mark Ransom Jun 01 '16 at 13:56
  • The example in the [`VerQueryValue` documentation](https://msdn.microsoft.com/en-us/library/ms647464(v=vs.85).aspx) uses `VerQueryValue` with `lpSubBlock = _T("\\VarFileInfo\\Translation")` to get the translation information. – Bondolin Jul 14 '16 at 13:55
  • Complete language identifier table can be found [here](https://msdn.microsoft.com/pl-pl/library/windows/desktop/aa381049(v=vs.85).aspx). 040904e4 is equivalent to 0409=US and 04e4=multilingual charset (decimal 1252) – ANTARA Nov 07 '16 at 14:57
  • This Code has an error. `VerQueryValue` returns the length of the string **including** the terminating zero. `SetString` appends the terminating zero to the string. This leads to unexpected behaviour. If you append sth. to say `strProductName` it looks like nothing happened, because there now is a zero in the middle of the string. Functions that take a zero-terminated string will take only the part of the original product name. It should be either: `strProductName.SetString((LPCSTR)pvProductName, iProductNameLen -1 );` or `strProductName.SetString((LPCSTR)pvProductName);`. – Holger Böhnke Apr 10 '17 at 17:22
  • @Holger that's not at all obvious from reading the documentation, and the code is no longer available to me for testing. I wouldn't presume to make that correction without testing. – Mark Ransom Apr 10 '17 at 17:52
  • 2
    @Mark: There you are perfectly right. It's not very well documented. I was trying to find, why my string concatenation did not work. It was at a completely different place in the code. Finally I traced it down to the `VerQueryValue` function. There it dawned to me that there might be an additional zero in the string data. Trimmed that off and voila it worked. Maybe you just put a hint in your solution to ask people to look at my comment. If you just use the string by itself you would not notice the difference. As soon as you `+=` another string or use the `+` operator the symptom shows. – Holger Böhnke Apr 12 '17 at 19:48
  • works under WinCE (Windows Compact Embedded) 2013 perfectly. Thank you. – flexo Sep 19 '17 at 11:54
  • When I tried it, I am able to get only the first character. I tried debugging using Visual studio. – Snippy Valson Jul 02 '19 at 07:00
  • If using TCHAR (wchar) instead of single-byte characters, use `std::vector data(dwSize);` instead of `std::vector data(dwSize);` and `LPCTSTR` instead of `LPCSTR`. – AlainD Jul 18 '22 at 15:26
  • @AlainD the return value from `GetFileVersionInfoSize` is in bytes, so the vector should be bytes as well or it will be the wrong size. You might be right about needing better casting to pull out the results though. – Mark Ransom Jul 18 '22 at 16:33
  • @MarkRansom: Ah yes, possibly `...data(dwSize / sizeof(TCHAR))` to be absolutely correct. Most of the returned data is blank so I hadn't spot that bug. Good spot. Using `TCHAR` and casting to `LPCTSTR` worked for me with VS2019 using C++14. My project `Character Set` is set (by default) to `Use Unicode Character Set`. – AlainD Jul 19 '22 at 08:42
17

To get a language independent result to Mark's answer change :

   // replace "040904e4" with the language ID of your resources
    !VerQueryValue(&data[0], _T("\\StringFileInfo\\040904e4\\ProductVersion"), &pvProductVersion, &iProductVersionLen))
{
    TRACE("Can't obtain ProductName and ProductVersion from resources\n");
    return false;
}

To

UINT                uiVerLen = 0;
VS_FIXEDFILEINFO*   pFixedInfo = 0;     // pointer to fixed file info structure
// get the fixed file info (language-independent) 
if(VerQueryValue(&data[0], TEXT("\\"), (void**)&pFixedInfo, (UINT *)&uiVerLen) == 0)
{
    return false;
}

 strProductVersion.Format("%u.%u.%u.%u", 
    HIWORD (pFixedInfo->dwProductVersionMS),
    LOWORD (pFixedInfo->dwProductVersionMS),
    HIWORD (pFixedInfo->dwProductVersionLS),
    LOWORD (pFixedInfo->dwProductVersionLS));
Ajay
  • 18,086
  • 12
  • 59
  • 105
EdM
  • 459
  • 5
  • 12
  • 4
    This gets different information from Mark Ransom's answer. The values you retrieve are four integers from the PRODUCTVERSION top-level line in the resource file; however Marks' answer retrieves the string "ProductVersion" from under the "StringFileInfo" block. – M.M Feb 02 '18 at 03:50
7

Something like might get you started, perhaps:

TCHAR moduleName[MAX_PATH+1];
(void)GetModuleFileName(AfxGetInstanceHandle(), moduleName, MAX_PATH);
DWORD dummyZero;
DWORD versionSize = GetFileVersionInfoSize(moduleName, &dummyZero);
if(versionSize == 0)
{
    return NULL;
}
void* pVersion = malloc(versionSize);
if(pVersion == NULL)
{
    return NULL;
}
if(!GetFileVersionInfo(moduleName, NULL, versionSize, pVersion))
{
    free(pVersion);
    return NULL;
}

UINT length;
VS_FIXEDFILEINFO* pFixInfo;
VERIFY(VerQueryValue(pVersionInfo, const_cast<LPTSTR>("\\"), (LPVOID*)&pFixInfo, &length));
Will Dean
  • 39,055
  • 11
  • 90
  • 118
6

Beware! Using FindResource..LockResource is not correct. It will sometimes work (as it did in my small demo program) and sometimes cause access violations (example: the production code I was making the demo for).

The VerQueryValue() documentation states that you should call GetFileVersionInfoSize and GetFileVersionInfo instead. Raymond Chen explains, see http://blogs.msdn.com/oldnewthing/archive/2006/12/26/1365215.aspx

Leo
  • 81
  • 1
  • 1
6

Something like this will give you raw access to the resource data and get you started:

HRSRC res = ::FindResource(NULL, MAKEINTRESOURCE(MY_VERSION_ID), RT_VERSION);
DWORD size = ::SizeofResource(NULL, res);
HGLOBAL mem = ::LoadResource(NULL, res);
LPVOID raw_data = ::LockResource(mem);
...
::FreeResource(mem);
Rob
  • 76,700
  • 56
  • 158
  • 197
0

Sometimes I receive Access Violation when use VerQueryValueA. But I never got this error when use VerQueryValueW. I think something wrong with VerQueryValueA in version.dll. Therefore I use VerQueryValueW instead of VerQueryValueA even in projects Multi-byte Character Encoding. Here is my code of ReadVersion function

SingerOfTheFall
  • 29,228
  • 8
  • 68
  • 105
Vitaly
  • 1
0

Ok, a bit more googleing found the following on CodeGuru. Basically this approach uses the CFileVersionInfo object to get on any given file. It should be interesting to see if it works on the currently running .EXE file and on Windows CE.

SmacL
  • 22,555
  • 12
  • 95
  • 149