Скомпилируйте другой код в зависимости от того, доступна функция или нет - PullRequest
5 голосов
/ 18 июня 2009

Windows предоставляет только GetTickCount до Windows Vista и, начиная с этой ОС, также GetTickCount64. Как сделать так, чтобы C программа компилировалась с вызовами различных функций?

Как я могу заставить C компилятор проверить, объявлена ​​ли функция во включенных заголовочных файлах, и скомпилировать различные части кода в зависимости от того, доступна эта конкретная функция или нет?

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

Ищем рабочий образец файла, а не только подсказки.

Редактировать: Я попробовал следующее, используя gcc 3.4.5 из MinGW на (64-битной) Windows 7 RC, но это не помогло. Если это проблема 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 ]

13 голосов
/ 19 июня 2009

Выбор времени компиляции API на основе целевой версии Windows блокирует встроенный исполняемый файл для этой версии и новее. Это обычная техника для проектов с открытым исходным кодом, * nix, где предполагается, что пользователь настроит исходный комплект для своей платформы и скомпилирует чистый для установки.

В Windows это не обычный метод, потому что обычно небезопасно предполагать, что конечный пользователь вообще будет иметь компилятор, не говоря уже о том, чтобы разобраться с тонкостями создания проекта.

Часто достаточно использовать более старый API, который присутствует во всех версиях Windows. Это также просто: вы просто игнорируете существование нового API.

Если этого недостаточно, вы используете LoadLibrary() и GetProcAddress(), чтобы попытаться разрешить новый символ во время выполнения. Если это не может быть решено, тогда вы вернетесь к более старому 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();
}

Обратите внимание, что я использовал ... разновидности функций API, поскольку известно, что имя библиотеки и имя символа будет только ASCII ... при использовании этого метода для загрузки символов из установленной DLL, которая может быть в папке с именами, отличными от символов ASCII, вам нужно будет позаботиться об использовании сборки Unicode.

Это не проверено, ваш пробег будет разным, и т.д ...

4 голосов
/ 18 июня 2009

Вы можете достичь этого, используя определения препроцессора в заголовках Windows .

unsigned long long
get_tick_count(void)
{
#if WINVER >= 0x0600
    return GetTickCount64();
#else
    return GetTickCount();
#endif
}
2 голосов
/ 18 июня 2009

Правильный способ решения подобных проблем - проверить, доступна ли функция, но это невозможно сделать надежно во время компиляции проекта. Вы должны добавить этап конфигурации, детали которого зависят от вашего инструмента сборки, и 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 на любой платформе, а затем собирает config.h для Windows XP, Vista и т. Д.). Я не думаю, что это вообще относится к unix.

1 голос
/ 18 июня 2009

Если ваш код будет работать в операционных системах, предшествующих Vista, вы не можете просто скомпилировать ваши вызовы до GetTickCount64 (), потому что GetTickCount64 () не существует на машине с XP.

Вам необходимо определить во время выполнения, какую операционную систему вы используете, а затем вызвать правильную функцию. В общем случае оба вызова должны быть в коде.

Теперь это может быть неверно в вашем случае, если вам действительно не нужно иметь возможность вызывать GetTickCount64 () на компьютерах с Vista + и GetTickCount () на машинах с XP. Вы можете просто вызвать GetTickCount () независимо от того, на какой ОС вы работаете. В документах нет указаний на то, что они удаляют GetTickCount () из API.

Я бы также отметил, что, возможно, GetTickCount () не совсем подходит для использования. Документы говорят, что он возвращает количество миллисекунд, но на самом деле точность функции даже не близка к 1 миллисекунде. В зависимости от машины (а во время выполнения AFAIK нет способа узнать) точность может составлять 40 миллисекунд или даже больше. Если вам нужна точность в 1 миллисекунду, вы должны использовать QueryPerformanceCounter () . На самом деле, на самом деле нет практической причины не использовать QPC во всех случаях, когда вы все равно используете GetTickCount ().

1 голос
/ 18 июня 2009

Предыдущие ответы указывали на проверку конкретного #define, который будет присутствовать в вашем конкретном случае. Этот ответ предназначен для более общего случая компиляции другого кода независимо от того, доступна функция или нет.

Вместо того, чтобы пытаться делать все в самом файле C, это именно то, где скрипты configure действительно сияют. Если бы вы работали в 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
}
1 голос
/ 18 июня 2009

Вы спрашиваете о C, но вопрос также помечен C ++ ...

В C ++ вы бы использовали технику SFINAE, смотрите похожие вопросы:

Можно ли написать шаблон для проверки существования функции?

Но используйте директивы препроцессора в Windows, если они есть.

1 голос
/ 18 июня 2009

G'day,

Разве NTDDI_VERSION - это то, что вам нужно искать?

Обновление: Вы хотите проверить, равен ли WINVER 0x0600. Если это так, то вы работаете в Vista.

Редактировать: Для семантического заголовка я имел в виду запуск компилятора в среде Vista. Вопрос касается только компиляции, вопрос касается только заголовочных файлов, которые используются только во время компиляции. Большинство людей понимали, что это предназначено для компиляции в среде Vista. В вопросе не упоминалось поведение во время выполнения.

Если кто-то не работает под управлением Vista и, возможно, компилирует для Windows XP?

Sheesh!

НТН

ура

0 голосов
/ 25 октября 2009

Может быть, это хорошая замена для 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;
}
0 голосов
/ 18 июня 2009

Если вам нужна поддержка до Vista, я бы придерживался только использования GetTickCount (). В противном случае вам нужно реализовать код времени выполнения, чтобы проверить версию Windows и вызвать GetTickCount () в версиях Windows, предшествующих Vista, и GetTickCount64 () в Vista и более поздних версиях. Так как они возвращают значения разных размеров (ULONGLONG v DWORD), вам также нужно будет отдельно обрабатывать то, что они возвращают. Использование только GetTickCount () (и проверка на переполнение) будет работать в обеих ситуациях, тогда как использование GetTickCount64 (), когда оно доступно, увеличивает сложность вашего кода и удваивает объем кода, который вы должны написать.

Придерживайтесь только GetTickCount (), пока не убедитесь, что ваше приложение больше не должно работать на компьютерах, предшествующих Vista.

0 голосов
/ 18 июня 2009

Компилятор Microsoft определит _WIN64 при компиляции для 64-битных машин.

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
...