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

Posted

技术标签:

【中文标题】从 dll 函数调用中正确获取 Windows 版本?【英文标题】:Get the Windows version correctly from a dll function call? 【发布时间】:2017-06-20 16:26:47 【问题描述】:

假设我正在编写一个多功能 dll,其中包含一个获取操作系统版本的函数:

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;

但是在 Windows 10 上运行 App.exe 时,意外惊喜,输出是:

6.2

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

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

,输出是预期的:

10.0

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

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

【问题讨论】:

【参考方案1】:

MSDN 页面"Getting the System Version" 上记录了确定实际操作系统版本的另一种方法:

要获取操作系统的完整版本号,请在其中一个系统 DLL(例如 Kernel32.dll)上调用 GetFileVersionInfo 函数,然后调用 VerQueryValue 以获取文件版本信息的\\StringFileInfo\\&lt;lang&gt;&lt;codepage&gt;\\ProductVersion 子块。

无论应用程序是否有清单,这都将从 DLL 中工作。

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

【讨论】:

+1 谢谢,我会试试这个。旁注:我没有在我的实际 dll 中导出这样的函数,它在 dll 中被调用以用于统计和调试目的;我以这种方式制作了示例,以便我可以轻松地演示问题 好的,这是合法使用。不过,我不会编辑我的答案,因为该警告可能仍与未来的读者有关。 :-)【参考方案2】:

作为accepted answer 的补充,这里有一些入门代码供任何想要实现它的人使用:

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

Windows 10 上的输出是:

Version is 10.0

【讨论】:

【参考方案3】:

App (executable) manifest MSDN page 相当明确地描述了这个问题:

Windows 中引入的应用(可执行)清单的兼容性部分可帮助操作系统确定应用的目标 Windows 版本。

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

【讨论】:

谢谢,确实不允许这样的清单标签进入 dll 是有意义的。但是似乎没有可靠的方法可以从 dll 中获取操作系统版本...【参考方案4】:

manifest 中的大部分节点都适用于整个进程,并且只能从主 .exe 模块中读取:

Windows 中引入的compatibility section of the app (executable) manifest 可帮助操作系统确定应用程序所针对的 Windows 版本。

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

如果您真的需要这些信息,只需稍加工作,GetProcAddress 也可以用来确定您使用的版本。看MSDN上各种kernel32和user32函数的最低OS版本...

【讨论】:

问题是我不想将我的应用程序定位到特定版本的 Windows,我只想获得 Windows 版本。 (所以是的,我假设我得到的是 Windows 版本实际上是错误的) 如果您将其写入日志文件,则获取 Windows 版本是可以的,但您不应该使用它来决定您可以调用哪些 API。

以上是关于从 dll 函数调用中正确获取 Windows 版本?的主要内容,如果未能解决你的问题,请参考以下文章

如何从 C 代码正确创建 DLL 并在 C++ 项目中使用它

C#中调用dll,函数参数带指针,如何改写?

从 Dll 调用具有偏移地址的成员函数

从应用程序中获取显式加载的 DLL 的符号

从 C# 调用带有 C++ 标头的 dll

从 Qt (c++) 调用 DLL 中的函数