编译函数是否可用的不同代码

时间:2009-06-18 13:04:31

标签: c macros c-preprocessor conditional-compilation

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

10 个答案:

答案 0 :(得分:13)

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

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

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

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

这是一个可能的实现。它会检测第一个调用,并尝试加载库并解析名称"GetTickCount64"。在所有调用中,如果指向已解析符号的指针为非null,则调用它并返回结果。否则,它会回退到较旧的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();
}

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

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

答案 1 :(得分:4)

您可以使用preprocessor definitions in Windows headers来实现它。

unsigned long long
get_tick_count(void)
{
#if WINVER >= 0x0600
    return GetTickCount64();
#else
    return GetTickCount();
#endif
}

答案 2 :(得分: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的问题。如果dones poperly,那只需要更改config.h(你可以在任何平台上生成一个生成config.h的脚本,然后为windows xp,Vista等收集config.h)。我认为它根本不是针对unix的。

答案 3 :(得分:1)

天儿真好,

你需要寻找NTDDI_VERSION吗?

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

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

除非有人在运行Vista,否则可能会为Windows XP编译?

啧!

HTH

欢呼声,

答案 4 :(得分:1)

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

在C ++中你会使用SFINAE技术,看到类似的问题:

Is it possible to write a template to check for a function's existence?

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

答案 5 :(得分:1)

之前的答案已经指出要检查特定情况下的特定#define。这个答案适用于编写不同代码的更一般情况,无论函数是否可用。

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

一个简单(非常非常丑陋)的脚本,如果你有方便的话可以工作(我没有方便用来测试它的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
}

答案 6 :(得分:1)

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

您需要在运行时确定您正在运行的操作系统,然后调用正确的函数。通常,两个调用都需要在代码中。

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

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

答案 7 :(得分:0)

在编译64位计算机时,Microsoft编译器将定义_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

答案 8 :(得分:0)

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

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

答案 9 :(得分: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;
}