【问题标题】:How do I call a C function, which is only available in specifc OS versions?如何调用仅在特定操作系统版本中可用的 C 函数?
【发布时间】:2018-06-09 05:42:05
【问题描述】:

我正在使用“高级语言”(Nim)进行编程,出于性能原因,我必须“使用 C”。我想做这样的事情:

/* Pseudocode */
include <VersionHelpers.h>
/* ...*/
if (isWindows8OrGreater()) {
    /** use InterlockedIncrementNoFence64() */
} else {
    /** use InterlockedIncrement64() ;( */
}

但有人告诉我它会在 Windows 7 上崩溃,因为如果我引用 InterlockedIncrementNoFence64(),它必须可用,即使我不尝试调用它。

我正在编写一个多线程应用程序,通信是通过“消息”实现的(即使在同一个线程中)。这将为每条消息调用,因此内存栅栏会对性能产生重大影响。

我已经习惯了 Java,这样的事情在哪里可以工作,并且不知道这是如何在 C 中完成的。

【问题讨论】:

  • 在编译时使用预处理器宏而不是在运行时使用函数进行相关检查。
  • 使用Linker Support for Delay-Loaded DLLs。无需构建不同的版本,并报告链接时无法解析导入,供您处理。当 Linux 开发人员建议有关在 Windows 中链接的解决方案时,请始终保持怀疑。它们通常是错误的(如本例所示)。
  • @IInspectable 虽然延迟加载通常很有用,但在这种情况下它不起作用,因为您无法延迟加载 kernel32。但在这种情况下它可能也无关紧要,因为实现是由a compiler intrinsic 提供的。没有要导入的操作系统函数。
  • 为您的工具链制作不受支持的补丁通常不被认为是一种好的工程实践。它不可扩展,不可移植,总有一天您会丢失硬盘或重新安装/升级 Visual Studio(或移植到新架构)并丢失补丁。链接器试图将您从自己手中拯救出来:GetProcAddress 在 kernel32 中。那么你怎么能延迟加载GetProcAddress
  • @RbMm 看来您的答案确实只适合您而不适合其他人,因为它与您的专业知识(修补链接器)、您的开发环境(您不为 x86 和 x64 以外的任何东西构建)和您的客户群(他们不在乎他们使用的产品依赖于不受支持的技术)。答案通常没有用,因为大多数人没有相同的技能、目标和客户组合。

标签: c windows operating-system


【解决方案1】:

具体情况下,InterlockedIncrementNoFence64 不是从任何 dll 导入,而是使用大多数平台上的编译器内在实现(x86 - 通过_InterlockedCompareExchange64amd64 - _InterlockedIncrement64 , arm/arm64 - _InterlockedIncrement64_nf)。所以这个具体调用的代码可以在任何 Windows 版本上运行(当然在 win7 中也是如此)。

在更一般的情况下,如果我们需要使用不是在所有操作系统上导出的多个函数 - 我们可以将其声明为函数指针并在运行时解析。例如让LoadPackagedLibrary api

我们可以声明函数指针:

HMODULE (WINAPI * LoadPackagedLibraryAddr)(
                                   _In_       LPCWSTR lpwLibFileName,
                                   _Reserved_ DWORD   Reserved
                                   );

在运行时解决它:

*(void**)&LoadPackagedLibraryAddr = GetProcAddress(
    GetModuleHandleW(L"kernel32"), "LoadPackagedLibrary");

并使用:

if (LoadPackagedLibraryAddr)
{
    LoadPackagedLibraryAddr(L"***",0);
}
else {...}

使用__declspec(dllimport) 声明的函数的另一种可能方式(大多数使用此前缀声明的 windows api)使用下一个语法:

extern "C" {
    PVOID __imp_LoadPackagedLibrary;
}

#ifdef _M_IX86 
__pragma(comment(linker, "/alternatename:__imp__LoadPackagedLibrary@8=___imp_LoadPackagedLibrary"))
#endif

__imp_LoadPackagedLibrary = GetProcAddress(
    GetModuleHandleW(L"kernel32"), "LoadPackagedLibrary");

#pragma warning(disable : 4551)
if (LoadPackagedLibrary)//if (__imp_LoadPackagedLibrary)
{
    LoadPackagedLibrary(L"***",0);
}

然而两种方式都生成完全相同的二进制代码,只是使用不同的语法。

请注意,根本不需要查询windows版本,只需尝试获取指向api的指针。或者我们得到它并且可以使用,或者没有。

__declspec(selectany)#pragma comment(linker, "/alternatename:_pWeakValue=_pDefaultWeakValue") 的方式

不会在这里工作,因为我们在链接时解析符号(结果对于所有Windows版本都是常见的),但我们实际上需要在运行时解析符号强>.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-08-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-28
    • 1970-01-01
    • 2015-02-02
    相关资源
    最近更新 更多