Get the Windows version correctly from a dll funct

2019-07-28 08:09发布

问题:

Suppose I'm writing a multi-purpose dll which includes a function for getting the OS version:

void get_os_version(DWORD *major, DWORD *minor)
{
    OSVERSIONINFOEX osvi;

    ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
    osvi.dwOsVersionInfoSize = sizeof(OSVERSIONINFOEX);

    // deprecated but easier to use for this example's sake
    GetVersionEx((OSVERSIONINFO*)&osvi);

    *major = osvi.dwMajorVersion;
    *minor = osvi.dwMinorVersion;
}

For the Windows version to be retrieved correctly for versions higher than Windows 8, it is required to embed a manifest that specifies the supported platforms (see details here).

So I disable automatic generation of a manifest for my dll file using the /MANIFEST:NO flag when compiling, and instead add the following manifest:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
    <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
        <security>
            <requestedPrivileges>
                <requestedExecutionLevel level="asInvoker" uiAccess="false"></requestedExecutionLevel>
            </requestedPrivileges>
        </security>
    </trustInfo>
    <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
        <application>
            <!-- Windows 10 --> 
            <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
            <!-- Windows 8.1 -->
            <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
            <!-- Windows Vista -->
            <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/> 
            <!-- Windows 7 -->
            <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
            <!-- Windows 8 -->
            <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
        </application>
    </compatibility>
</assembly>

, using the mt tool:

mt -manifest GetOsVersion.dll.manifest -outputresource:GetOsVersion.dll;#2

All good and no errors. Now to use the dll, I create a simple App.exe which loads the dll and calls its function:

int _tmain(int argc, _TCHAR* argv[])
{
    DWORD major, minor;
    get_os_version(&major, &minor);
    printf("%d.%d\n", major, minor);
    return 0;
}

But when running App.exe on Windows 10, surprise surprise, the output is:

6.2

, which is the version for Windows 8. If I apply the manifest to the App.exe too:

mt -manifest GetOsVersion.dll.manifest -outputresource:App.exe;#1

, the output is the expected one:

10.0

Why is this happening? And can I solve this problem without adding a manifest to the executable?

I don't have control over the applications that will use my library, but I still want to correctly retrieve the OS version.

回答1:

An alternative method for determining the actual operating system version is documented on the MSDN page "Getting the System Version":

To obtain the full version number for the operating system, call the GetFileVersionInfo function on one of the system DLLs, such as Kernel32.dll, then call VerQueryValue to obtain the \\StringFileInfo\\<lang><codepage>\\ProductVersion subblock of the file version information.

This will work from a DLL regardless of whether the application has a manifest.

(Of course, there is a reason that GetVersionInfo and friends don't return the actual operating system version: programmers have a nasty tendency to misuse this information. You should seriously consider whether or not providing such a function in your DLL is really a good idea.)



回答2:

Complementing the accepted answer, here is some starter code for anyone else who wants to implement it:

#pragma comment(lib, "version")

static void print_version()
{
    DWORD buffer_size = GetFileVersionInfoSize(_T("kernel32.dll"), NULL);
    if (buffer_size == 0)
    {
        // get error from GetLastError()
        return;
    }

    VOID *buffer = malloc(buffer_size);
    if (buffer == NULL)
    {
        // out of memory
        return;
    }

    if (!GetFileVersionInfo(_T("kernel32.dll"), 0, buffer_size, buffer))
    {
        goto error;
    }

    VS_FIXEDFILEINFO *version = NULL;
    UINT version_len = 0;
    if (!VerQueryValue(buffer,
                       _T("\\"),
                       (LPVOID*)&version,
                       &version_len))
    {
        goto error;
    }

    _tprintf(_T("Version is: %u.%u\n"),
             HIWORD(version->dwProductVersionMS),
             LOWORD(version->dwProductVersionMS));

error:
    free(buffer);
}

And the output on Windows 10 is:

Version is 10.0


回答3:

App (executable) manifest MSDN page describes this matter rather explicitly:

The compatibility section of the app (executable) manifest introduced in Windows helps the operating system determine the versions of Windows an app was designed to target.

Compatibility (and other application settings, such as DPI awareness) are completely ignored if manifest is not an executable manifest. This makes sense because otherwise there would be possible manifest conflicts occurring between manifests in different dlls.



回答4:

Most of the nodes in a manifest applies to the whole process and is only read from the main .exe module:

The compatibility section of the app (executable) manifest introduced in Windows helps the operating system determine the versions of Windows an app was designed to target.

You should use GetProcAddress and CoCreateInstance to check if the feature you need is present, not the Windows version.

With a little work, GetProcAddress can also be used to figure out what version you are on if you really need that information. Look at the minimum OS version of various kernel32 and user32 functions on MSDN...