Скомпилируйте другой код в зависимости от того, доступна функция или нет

StackOverflow https://stackoverflow.com/questions/1012530

Вопрос

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
Это было полезно?

Решение

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

Вместо того, чтобы пытаться сделать все в самом файле C, это та вещь, в которой скрипты configure действительно великолепны.Если бы вы работали под Linux, я бы указал вам на Автоинструменты GNU без колебаний.Я знаю, что есть доступные порты для 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
}

Другие советы

Выбор 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, то вам нужно будет побеспокоиться об использовании сборки в Юникоде.

Это непроверено, ваш пробег будет отличаться и т.д...

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

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

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

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

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

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

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

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

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

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

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

Добрый день,

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

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

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

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

Блин!

HTH

ваше здоровье,

Компилятор 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

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

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

Возможно, это хорошая замена для 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;
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top