Programmatically Extracting Version Information from a Windows Resource File

115 Views Asked by At

Win10, C++20, MSVS Community 17.8.3

I'm attempting to retrieve file version information from a compiled .rc file (.res) created in MSVS's resource editor (Solution Explorer > Resource Files > Add > Resource > Version).

I've reviewed the answer at how to use the GetFileVersionInfo function? but it's fairly old and I felt there might be an updated, cleaner way that avoided raw new[]/delete[] calls.

The version resource .rc contains the FILEVERSION key with a value of 1, 0, 0, 1 and PRODUCTVERSION key with 1, 0, 0, 1 and it compiles to a .res file.

I use the following code to load the resource and extract the file version info. The project compiles and links w/o errors or warnings. I'm expecting the output to reflect the resource file contents.

#include <iostream>
#include <format>
#include "resource.h"

using std::cout;
using std::cerr;
using std::endl;
using std::format;

int main() {
    struct VersionInfo {
        DWORD dwFileVersionMS;
        DWORD dwFileVersionLS;
        DWORD dwProductVersionMS;
        DWORD dwProductVersionLS;
    };

    // Update the resource identifier to reflect the file name and ID
    HRSRC hVersionResource = FindResource(NULL, MAKEINTRESOURCE(1), RT_VERSION);

    if (!hVersionResource) {
        cerr << "Error: Could not find version resource." << endl;
        return -1;
    }

    HGLOBAL hVersionData = LoadResource(NULL, hVersionResource);

    if (!hVersionData) {
        cerr << "Error: Could not load version resource." << endl;
        return -1;
    }

    LPVOID pVersionData = LockResource(hVersionData);

    if (!pVersionData) {
        cerr << "Error: Could not lock version resource." << endl;
        return -1;
    }

    VersionInfo* versionInfo = (VersionInfo*)pVersionData;

    // Access and format version information
    cout << format("File version: {}.{}.{}.{}\n",
        HIWORD(versionInfo->dwFileVersionMS),
        LOWORD(versionInfo->dwFileVersionMS),
        HIWORD(versionInfo->dwFileVersionLS),
        LOWORD(versionInfo->dwFileVersionLS));

    cout << format("Product version: {}.{}.{}.{}\n",
        HIWORD(versionInfo->dwProductVersionMS),
        LOWORD(versionInfo->dwProductVersionMS),
        HIWORD(versionInfo->dwProductVersionLS),
        LOWORD(versionInfo->dwProductVersionLS));

    // Use other version information fields and format with format as needed

    FreeResource(hVersionData);
    return 0;
}

However, every time I run the code the output is:

File version: 52.792.86.0
Product version: 95.83.69.86

What am I missing?

1

There are 1 best solutions below

2
SimpleCoder On

It took a little while but I crafted the successful code derived from @273K suggestion and a little help from MS documentation pages, Codeium and Bing Chat.

DWORD infoHandle{ 0 };
VS_FIXEDFILEINFO* lpFileInfoBuffer{ nullptr };
UINT uLen{ 0 };

struct Deleter      // Custom delete function for unique_ptr
{
    void operator()(BYTE* ptr) const {
        delete[] ptr;
    }
};

DWORD infoSize = GetFileVersionInfoSizeA("VersionTest.exe", &infoHandle /*ignored*/);

if (infoSize == 0) {
    DWORD err = GetLastError();
    cerr << format("Error: Could not get version info size. ({})", err);
    return -1;
}
cout << format("Version info size: {}\n", infoSize);

std::unique_ptr<BYTE[], Deleter> data(new BYTE[infoSize]);      // create a unique_ptr to an array the size of Version Info that will delete data on exit

if (!GetFileVersionInfoA("VersionTest.exe", 0 /*ignored*/, infoSize, data.get()))
{
    DWORD err = GetLastError();
    cerr << format("Error: Could not get version info. ({})", err);
    return -1;
}
cout << "Version info acquired\n";

VerQueryValueA                  // get version info with https://learn.microsoft.com/en-us/windows/win32/api/winver/nf-winver-verqueryvaluea...
(data.get(),                    // a pointer to the data retrieved by GetFileVersionInfoA
    "\\\\",                     // and a "path" to the version info
    (LPVOID*)&lpFileInfoBuffer, // using a pointer to the buffer that will hold the retrieved version info
    &uLen);                     // with this containing the length of the buffer returned in lpFileInfoBuffer

cout << format("Product version: {}.{}.{}.{}\n",
    HIWORD(lpFileInfoBuffer->dwProductVersionMS),
    LOWORD(lpFileInfoBuffer->dwProductVersionMS),
    HIWORD(lpFileInfoBuffer->dwProductVersionLS),
    LOWORD(lpFileInfoBuffer->dwProductVersionLS));

return 0;

I definitely don't understand the lpSubBlock parameter of the VerQueryValueA function.