5

Windows 仅提供直到 Windows Vista 的 GetTickCount,并且从该操作系统开始还提供 GetTickCount64。如何通过调用不同的函数来编译C程序?

如何让C编译器检查是否在包含的头文件中声明了一个函数,并根据该特定函数是否可用来编译代码的不同部分?

#if ??????????????????????????????
unsigned long long get_tick_count(void) { return GetTickCount64(); }
#else
unsigned long long get_tick_count(void) { return GetTickCount(); }
#endif

寻找工作示例文件而不仅仅是提示。

编辑:我在(64 位)Windows 7 RC 上使用 MinGW 的 gcc 3.4.5 尝试了以下操作,但没有帮助。如果这是 MinGW 问题,我该如何解决这个问题?

#include <windows.h>
#if (WINVER >= 0x0600)
unsigned long long get_tick_count(void) { return 600/*GetTickCount64()*/; }
#else
unsigned long long get_tick_count(void) { return 0/*GetTickCount()*/; }
#endif
4

10 回答 10

14

基于目标 Windows 版本的 API 的编译时间选择会将构建的可执行文件锁定到该版本和更高版本。这是开源、*nix 目标项目的常用技术,假设用户将为他的平台配置源工具包并编译干净以进行安装。

在 Windows 上,这不是常用的技术,因为假设最终用户将拥有编译器通常是不安全的,更不用说要处理构建项目的复杂性了。

通常,仅使用所有版本的 Windows 中都存在的旧 API 就足够了。这也很简单:您只需忽略新 API 的存在。

如果这还不够,您可以使用LoadLibrary()andGetProcAddress()尝试在运行时解析新符号。如果无法解决,则回退到旧 API。

这是一个可能的实现。它检测到第一次调用,并尝试加载库并解析名称"GetTickCount64"。在所有调用中,如果指向已解析符号的指针不为空,则调用它并返回结果。否则,它会退回到旧 API,将其返回值转换为匹配包装器的类型。

unsigned long long get_tick_count(void) {
    static int first = 1;
    static ULONGLONG WINAPI (*pGetTickCount64)(void);

    if (first) {
        HMODULE hlib = LoadLibraryA("KERNEL32.DLL");
        pGetTickCount64 = GetProcAddressA(hlib, "GetTickCount64");
        first = 0;
    }
    if (pGetTickCount64)
        return pGetTickCount64();
    return (unsigned long long)GetTickCount();
}

请注意,我使用了 ...A 风格的 API 函数,因为已知库名称和符号名称只会是 ASCII...如果使用此技术从可能位于文件夹中的已安装 DLL 加载符号用非 ASCII 字符命名,那么您将需要担心使用 Unicode 构建。

这是未经测试的,您的里程会有所不同,等等......

于 2009-06-18T22:55:24.803 回答
4

您可以使用Windows 标头中的预处理器定义来实现它。

unsigned long long
get_tick_count(void)
{
#if WINVER >= 0x0600
    return GetTickCount64();
#else
    return GetTickCount();
#endif
}
于 2009-06-18T13:11:12.397 回答
2

处理此类问题的正确方法是检查该功能是否可用,但这在项目编译期间无法可靠地完成。您应该添加一个配置阶段,其详细信息取决于您的构建工具,cmake 和 scons,两个跨平台构建工具,提供设施。基本上,它是这样的:

/* config.h */
#define HAVE_GETTICKSCOUNT64_FUNC

然后在您的项目中,您执行以下操作:

#include "config.h"

#ifdef HAVE_GETTICKSCOUNT64_FUNC
....
#else
...
#endif

尽管它看起来与显而易见的方式相似,但从长远来看,它更易于维护。特别是,您应该尽可能避免依赖版本,而是检查功能。检查版本很快会导致复杂的交错条件,而使用上述技术,一切都由一个 config.h 控制,希望是自动生成的。

在 scons 和 cmake 中,它们会自动运行测试以检查函数是否可用,并根据检查在 config.h 中定义变量。基本思想是将功能检测/设置与您的代码分离

请注意,这可以处理您需要构建在不同平台上运行的二进制文件的情况(比如在 XP 上运行,即使是在 Vista 上构建的)。只需更改 config.h 即可。如果做得好,那只是改变 config.h 的问题(你可以有一个脚本在任何平台上生成 config.h,然后为 windows xp、Vista 等收集 config.h ......)。我认为它根本不是unix特有的。

于 2009-06-18T13:30:40.830 回答
1

天,

您需要寻找的不是 NTDDI_VERSION 吗?

更新:您想检查 WINVER 是否为 0x0600。如果是,那么您正在运行 Vista。

编辑:对于语义啄木鸟头,我的意思是在 Vista 环境中运行编译器。该问题仅指编译,该问题仅指仅在编译时使用的头文件。大多数人都明白,您打算在 Vista 环境中进行编译。该问题未提及运行时行为。

除非有人正在运行 Vista,并且可能为 Windows XP 编译?

嘘!

高温高压

干杯,

于 2009-06-18T13:09:01.013 回答
1

你问的是C,但问题也被标记为C++......

在 C++ 中,您将使用 SFINAE 技术,请参阅类似问题:

是否可以编写模板来检查函数的存在?

但在提供时使用 Windows 中的预处理器指令。

于 2009-06-18T13:12:25.047 回答
1

以前的答案指出检查特定情况下将出现的特定#define。无论函数是否可用,此答案适用于编译不同代码的更一般情况。

与其尝试在 C 文件本身中做所有事情,不如说这是配置脚本真正发挥作用的事情。如果您在 linux 上运行,我会毫不犹豫地向您指出GNU Autotools 。我知道有可用于 Windows 的端口,至少如果您使用的是 Cygwin 或 MSYS,但我不知道它们的效果如何。

如果您有 sh 方便(我没有方便的 Windows 设置来测试它),一个简单(而且非常难看)的脚本可能看起来像这样:

#!/bin/sh

# First, create a .c file that tests for the existance of GetTickCount64()

cat >conftest.c <<_CONFEOF
#include <windows.h>
int main() {
    GetTickCount64();
    return 0;
}
_CONFEOF

# Then, try to actually compile the above .c file

gcc conftest.c -o conftest.out

# Check gcc's return value to determine if it worked.
#   If it returns 0, compilation worked so set CONF_HASGETTICKCOUNT64
#   If it doesn't return 0, there was an error, so probably no GetTickCount64()

if [ $? -eq 0 ]
then
    confdefs='-D CONF_HASGETTICKCOUNT64=1'
fi

# Now get rid of the temporary files we made.

rm conftest.c
rm conftest.out

# And compile your real program, passing CONF_HASGETTICKCOUNT64 if it exists.

gcc $confdefs yourfile.c

这应该很容易翻译成您选择的脚本语言。如果您的程序需要额外的包含路径、编译器标志或其他任何东西,请确保将必要的标志添加到测试编译和实际编译中。

'yourfile.c' 看起来像这样:

#include <windows.h>

unsigned long long get_tick_count(void) {
#ifdef CONF_HASGETTICKCOUNT64
  return GetTickCount64();
#else
  return GetTickCount();
#endif
}
于 2009-06-18T14:17:50.703 回答
1

如果您的代码要在 Vista 之前的操作系统上运行,您不能只将调用编译为 GetTickCount64(),因为在 XP 机器上不存在 GetTickCount64()。

您需要在运行时确定您正在运行哪个操作系统,然后调用正确的函数。一般来说,这两个调用都需要在代码中。

现在,如果您真的不需要在 Vista+ 机器上调用 GetTickCount64() 和在 XP- 机器上调用 GetTickCount(),那么在您的情况下这可能不是真的。无论您在什么操作系统上运行,您都可以只调用 GetTickCount()。文档中没有迹象表明我看到他们正在从 API 中删除 GetTickCount()。

我还要指出,也许 GetTickCount() 根本不适合使用。文档说它返回毫秒数,但实际上该函数的精度甚至不接近 1 毫秒。取决于机器(并且在运行时无法知道 AFAIK),精度可能是 40 毫秒甚至更多。如果您需要 1 毫秒的精度,您应该使用QueryPerformanceCounter()。事实上,在所有使用 GetTickCount() 的情况下,确实没有不使用 QPC 的实际理由。

于 2009-06-18T14:45:47.980 回答
0

Microsoft 编译器在为 64 位机器编译时会定义 _WIN64。

http://msdn.microsoft.com/en-us/library/b0084kay%28VS.80%29.aspx

#if defined(_WIN64)
unsigned long long get_tick_count(void) { return GetTickCount64(); }
#else
unsigned long long get_tick_count(void) { return GetTickCount(); }
#endif
于 2009-06-18T13:10:56.983 回答
0

如果你必须支持 pre-Vista,我会坚持只使用 GetTickCount()。否则,您必须实现运行时代码来检查 Windows 版本,并在 Windows 的 Vista 及更高版本上调用 GetTickCount() 和 GetTickCount64()。由于它们返回不同大小的值(ULONGLONG v DWORD),您还需要单独处理它们返回的内容。仅使用 GetTickCount()(并检查溢出)将适用于这两种情况,而在可用时使用 GetTickCount64() 会增加您的代码复杂性并使您必须编写的代码量加倍。

坚持只使用 GetTickCount() 直到您可以确定您的应用程序不再需要在 Vista 之前的机器上运行。

于 2009-06-18T19:13:35.310 回答
0

也许它是 GetTickCount() 的一个很好的替代品

double __stdcall
thetimer (int value)
{
    static double freq = 0;
    static LARGE_INTEGER first;
    static LARGE_INTEGER second;

    if (0 == value)
        {
            if (freq == 0)
            {
                QueryPerformanceFrequency (&first);
                freq = (double) first.QuadPart;
            }
            QueryPerformanceCounter (&first);
            return 0;
        }
    if (1 == value)
        {
            QueryPerformanceCounter (&second);
            second.QuadPart = second.QuadPart - first.QuadPart;
            return (double) second.QuadPart / freq;
        }
    return 0;
}
于 2009-10-25T17:01:43.423 回答