从 dll 函数调用中正确获取 Windows 版本?

Get the Windows version correctly from a dll function call?

假设我正在编写一个多用途 dll,其中包含一个用于获取 OS 版本的函数:

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;
}

要为高于 Windows 8 的版本正确检索 Windows 版本,需要嵌入指定支持平台的清单(请参阅详细信息 here)。

因此,我在编译时使用 /MANIFEST:NO 标志禁用了为我的 dll 文件自动生成清单的功能,而是添加了以下清单:

<?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>

,使用mt工具:

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

一切正常,没有错误。现在要使用 dll,我创建了一个简单的 App.exe 来加载 dll 并调用它的函数:

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

但是当运行 App.exe on Windows 10时,惊喜惊喜,输出是:

6.2

,这是 Windows 的版本 8. 如果我也将清单应用到 App.exe:

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

,输出是预期的:

10.0

为什么会这样?我可以在不向可执行文件添加清单的情况下解决这个问题吗?

我无法控制将使用我的库的应用程序,但我仍想正确检索 OS 版本。

App (executable) manifest MSDN page 相当明确地描述了此事:

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.

如果清单不是可执行清单,则完全忽略兼容性(和其他应用程序设置,例如 DPI 感知)。这是有道理的,否则不同 dll 中的清单之间可能会发生清单冲突。

清单中的大部分节点适用于整个过程,并且仅从主 .exe 模块读取:

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.

您应该使用 GetProcAddressCoCreateInstance 来检查您需要的功能是否存在,而不是 Windows 版本。

如果您确实需要该信息,只需稍加操作,GetProcAddress 也可用于确定您使用的是哪个版本。看看MSDN上各种kernel32和user32函数的最低OS版本...

确定实际操作系统版本的替代方法记录在 MSDN 页面 "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.

无论应用程序是否有清单,这都可以从 DLL 运行。

(当然,GetVersionInfo 和朋友们不 return 实际的操作系统版本是有原因的:程序员有滥用这些信息的不良倾向。你应该认真考虑是否提供这样的信息在你的 DLL 中添加一个函数确实是个好主意。)

作为对 的补充,这里有一些入门代码供其他想要实施它的人使用:

#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);
}

Windows10 的输出是:

Version is 10.0