使用新的Windows功能并具备回退功能

4

我已经使用动态库和GetProcAddress等方法有一段时间了,但这种方式总是让人感到繁琐、智能感知不友好且难看。

有没有人知道一种干净的方法来导入新功能并保持与旧操作系统兼容呢?

比如说,我想使用 Vista 中的 XML 库。我调用 LoadLibraryW,然后如果 HANDLE 非空,就可以使用函数了。

但我真的不想走 typedef (void*)(PFNFOOOBAR)(int, int, int) 和 PFNFOOOBAR foo = reinterpret_cast(GetProcAddress(GetModuleHandle(), "somecoolfunction")) 这样的方式,因为这太麻烦了,需要写 50 次。

有没有一种非 hack 的解决方案,可以避免这种混乱呢?

我考虑在项目设置中添加 coolxml.lib,然后在延迟加载 DLL 列表中包含 coolxml.dll,并在所需文件中复制我将使用的少量函数签名。然后检查 LoadLibraryW 返回值是否非空,如果非空,则像常规程序流程一样分支到 Vista 分支。

但我不确定 LoadLibrary 和延迟加载是否可以一起工作,也不确定某些分支预测不会在某些情况下搞乱事情。

此外,我也不确定这种方法是否可行,如果升级到下一个 SDK 后会不会出现问题。


这是那种情况之一,你真的想要一个小脚本为你生成函数加载和头文件代码。 - ltjax
3个回答

4

在我看来,LoadLibrary和GetProcAddress是最好的方法。

(可以创建一些包装对象来为您处理这些内容,以便您不会在主要代码中污染逻辑或代码风格。)

  1. DelayLoad带来了安全问题 (参见OldNewThing文章) (编辑:如果确保您从未在旧版本的Windows上调用这些API,则不会出现安全问题)。

    DelayLoad还使得意外依赖于不可在所有目标上使用的API变得太容易。是的,您可以使用工具在运行时检查调用哪些API,但是在编译时处理这些问题更好,在我看来,那些工具只能检查您实际上在运行下使用的代码。

  2. 此外,请避免使用不同版本的Windows头文件编译代码的某些部分,除非您非常小心地隔离代码和传递给它的对象。

    这并不是绝对错误--在像插件DLL之类的东西中完全正常,因为两个完全不同的团队可能在两个模块上工作而不知道彼此针对的SDK版本--但是,如果不小心,它可能会导致困难的问题,因此通常最好避免。

    如果您混合使用头文件版本,则可能会出现非常奇怪的错误。例如,我们有一个静态对象,其中包含在Vista中更改大小的OS结构。我们的大部分项目都是为XP编译的,但是我们添加了一个新的.cpp文件,其名称以A开头,并设置为使用Vista头文件。然后,这个新文件(任意)成为触发静态对象分配的文件,使用Vista结构大小,但实际上为该对象构建的代码使用了XP结构。构造函数认为对象的成员与分配对象的代码处于不同的位置。结果变得很奇怪!

    一旦我们弄清楚了这一点,就完全禁止了这种做法;我们项目中的所有内容都使用XP头文件,如果我们需要来自新头文件的任何内容,我们手动复制它,必要时重命名结构。

编写所有typedef和GetProcAddress内容以及从头文件中复制结构和定义(这似乎是错误的,但它们是二进制接口,所以不会更改)(别忘了检查#pragma pack stuff,也要很繁琐),但在我看来,如果您想要最佳的编译时通知问题的方法,那么这是最好的方法。

我相信其他人会有不同的意见!

PS: 我有一个小模板,可以使GetProcAddress的内容稍微少一些。正在尝试找到它;如果我找到了,将更新此内容。 找到了,但实际上并不那么有用。事实上,我的代码甚至没有使用它。 :)


2

是的,使用延迟加载。这将把丑陋留给编译器。当然,您仍需要确保不在XP上调用Vista函数。


好的,我应该如何处理函数签名?我只需将_WIN32_WINNT设置为0x0502并复制粘贴所需的签名,还是有更清晰/更安全/更好的方法? - Coder
另外,使用LoadLibrary验证DLL加载成功是否可行? - Coder
1
使用DelayLoad检测功能是安全漏洞:http://blogs.msdn.com/b/oldnewthing/archive/2010/11/11/10089223.aspx - Leo Davidson
@Leo:没有人建议使用它来检测功能。请注意答案中的第二句话:“当然,您仍然必须确保在XP上不调用Vista函数。” - jalf
通常情况下,您确实需要将WIN32_WINNT保留在0x0502。原因相当简单:许多结构具有多个版本,这些版本通过第一个cb成员在运行时进行区分。Vista将接受结构的XP版本,但反之则不行。Frast的包装文件是使用仅在WIN32_WINNT >= 0x0600可用的签名的不错替代方案。由于问题集中在新的“coolxml.dll”上,我跳过了那些位。 - MSalters
显示剩余9条评论

1
延迟加载是避免直接使用LoadLibrary()GetProcAddress()的最佳方法。关于提到的安全性问题,你可以做的唯一事情就是使用延迟加载钩子确保(并可以选择强制)在dliNotePreLoadLibrary通知期间加载所需的DLL,并使用正确的系统路径而不是相对于应用程序文件夹。使用回调函数还可以在dliFailLoadLib/dliFailGetProc通知中替代自己的备选实现,当所需的API函数不可用时。这样,你的代码的其余部分就不必担心平台差异(或很少担心)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接