Compile different code on whether a function is available or not
Asked Answered
R

10

5

Windows provides only GetTickCount up to Windows Vista and starting from that OS also GetTickCount64. How can I make a C program compile with calls to different functions?

How can I make a C compiler check whether a function is declared in the included header files and compile different portions of code depending on whether that particular function is available or not?

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

Looking for a working sample file not just hints.

Edit: I tried the following using gcc 3.4.5 from MinGW on a (64-bit) Windows 7 RC but it didn't help. If this is a MinGW problem, how can I work around this issue?

#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
Refresher answered 18/6, 2009 at 13:4 Comment(3)
Note that if your code is going to be run on an XP machine or earlier, then NONE of the suggestions to use compiler directives will work. GetTickCount64() does not exist on an XP machine, and your code WILL CRASH.Inefficacy
You actually need to #define WINVER to a suitable value before including <windows.h>. That allows <windows.h> to define functions and constants that are not available to older versions. The default value for WINVER is always something "safe", and probably varies depending on the source of your copy. MinGW keeps it at a relatively low value so that it is easy to write programs that load and run on a wider range of Windows versions.Chuckwalla
Also, note that selecting an API at compile time prevents the EXE from running on older versions that don't have the new API without recompilation. Compile time configuration makes sense when you assume that the program is built clean before installation. This is not common practice on Windows.Chuckwalla
G
1

Previous answers have pointed out checking for the particular #define that would be present for your particular case. This answer is for a more general case of compiling different code whether a function is available or not.

Rather than trying to do everything in the C file itself, this is the sort of thing where configure scripts really shine. If you were running on linux, I would point you to the GNU Autotools without hesitation. I know there's ports available for Windows, at least if you're using Cygwin or MSYS, but I have no idea how effective they are.

A simple (and very very ugly) script that could work if you have sh handy (I don't have a Windows setup handy to test this on) would look something like this:

#!/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

This should be easy enough to translate into your scripting language of choice. If your program requires extra include paths, compiler flags, or whatever, make sure to add the necessary flags to both the test compile and the real compile.

'yourfile.c' would look something like this:

#include <windows.h>

unsigned long long get_tick_count(void) {
#ifdef CONF_HASGETTICKCOUNT64
  return GetTickCount64();
#else
  return GetTickCount();
#endif
}
Grandiloquent answered 18/6, 2009 at 14:17 Comment(0)
C
14

Compile time selection of an API based on the target Windows version locks the built executable to that version and newer. This is a common technique for open source, *nix targeted projects where it is assumed that the user will configure the source kit for his platform and compile clean to install.

On Windows, this is not the usual technique because it isn't generally safe to assume that an end user will have a compiler at all, let alone want to deal with the intricacies of getting a project to build.

Often, just using the older API that is present in all versions of Windows is a sufficient answer. This is also simple: you just ignore the existence of a new API.

When that isn't sufficient, you use LoadLibrary() and GetProcAddress() to attempt to resolve the new symbol at run time. If it can't be resolved, then you fall back to the older API.

Here's a possible implementation. It detects the first call, and at attempts to load the library and resolve the name "GetTickCount64". In all calls, if the pointer to resolved symbol is non-null, it calls it and returns the result. Otherwise, it falls back on the older API, casting its return value to match the wrapper's type.

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();
}

Note that I used the ...A flavors of the API functions since it is known that the library name and the symbol name will only be ASCII... if using this technique to load symbols from an installed DLL that might be in a folder named with non-ASCII characters, then you will need to worry about using a Unicode build.

This is untested, your mileage will vary, etc...

Chuckwalla answered 18/6, 2009 at 22:55 Comment(0)
B
4

You can achieve it using preprocessor definitions in Windows headers.

unsigned long long
get_tick_count(void)
{
#if WINVER >= 0x0600
    return GetTickCount64();
#else
    return GetTickCount();
#endif
}
Buyse answered 18/6, 2009 at 13:11 Comment(3)
I tried this #include <windows.h> #if (WINVER >= 0x0600) int high_precision(void) { return 1; } unsigned long long get_tick_count(void) { return 600/*GetTickCount64()*/; } #else int high_precision(void) { return 0; } unsigned long long get_tick_count(void) { return 0/*GetTickCount()*/; } #endif using gcc 3.4.5 on from the latest MinGW I could find for Windows but it did not want to work somehow o__ORefresher
I am only importing windows.h though, should I include additional headers? I couldn't get any information on this from the information you have also linked to.Refresher
I am not familiar with MinGW, you will probably have to define WINVER manually before including Windows headers, or define it in build command line depending on what OS you are building for (e.g. -DWINVER=0x0600). In VS it "just works" since you select target OS in project build options and IDE adds appropriate preprocessor macros for you.Buyse
Y
2

The right way to deal with this kind of problems is to check whether the function is available, but this cannot be done reliably during the project compilation. You should add a configuration stage, which details depend on your build tool, both cmake and scons, two cross platforms build tools, provide the facilities. Basically, it goes like this:

/* config.h */
#define HAVE_GETTICKSCOUNT64_FUNC

And then in your project, you do:

#include "config.h"

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

Although it looks similar to the obvious way, it is much more maintainable in the long term. In particular, you should avoid as much as possible to depend on versions, and check for capabilities instead. Checking for versions quickly leads to complicated, interleaved conditionals, whereas with the technique above, everything is controlled from one config.h, hopefully generated automatically.

In scons and cmake, they will have tests which are run automatically to check whether the function is available, and define the variable in the config.h or not depending on the check. The fundamental idea is to decouple the capability detection/setting from your code.

Note that this can handle cases where you need to build binaries which run on different platforms (say run on XP even if built on Vista). It is just a matter of changing the config.h. If dones poperly, that's just a matter of changing the config.h (you could have a script which generate the config.h on any platform, and then gather config.h for windows xp, Vista, etc...). I don't think it is specific to unix at all.

Youngman answered 18/6, 2009 at 13:30 Comment(0)
E
1

G'day,

Isn't NTDDI_VERSION what you need to look for?

Update: You want to check if WINVER is 0x0600. If it is then you're running Vista.

Edit: For the semantic pecker head, I meant running a compiler in a Vista environment. The question only refers to compiling, the question only refers to header files which are only used at compile time. Most people understood that it was intended that you're compiling in a Vista env. The question made no reference to runtime behaviour.

Unless someone is running Vista, and compiling for windows XP maybe?

Sheesh!

HTH

cheers,

Elijaheliminate answered 18/6, 2009 at 13:9 Comment(3)
msdn.microsoft.com/en-us/library/aa383745(VS.85).aspx I already read this page but couldn't figure out how to make the compilation fall back to using GetTickCount if !(_WINVER >= 0x0600) and not fail completely.Refresher
"You want to check if WINVER is 0x0600. If it is then you're running Vista." This is NOT TRUE. If WINVER is 0x0600 then you are COMPILING in Vista, not RUNNIGN in Vista. Big difference.Inefficacy
@John: Actually the community (Haskell) I'm writing a little library for uses source code and compilation on the user's machine as their main way of distribution (cabal install, pretty much like Gentoo's emerge) so Rob's suggestion makes sense as those that want to run the library (ok, any code that links to it) has to actually compile the library first: Both compilation and execution will actually take place on the same system.Refresher
T
1

You're asking about C but the question is tagged C++ as well ...

In C++ you would use SFINAE technique, see similar questions:

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

But use preprocessor directives in Windows when provided.

Telemeter answered 18/6, 2009 at 13:12 Comment(1)
Thanks ^^. If I cannot get the directives to work, I'll look into this as a last resort. I don't want to leave C unless I really need to as all that is for a very simple library.Refresher
G
1

Previous answers have pointed out checking for the particular #define that would be present for your particular case. This answer is for a more general case of compiling different code whether a function is available or not.

Rather than trying to do everything in the C file itself, this is the sort of thing where configure scripts really shine. If you were running on linux, I would point you to the GNU Autotools without hesitation. I know there's ports available for Windows, at least if you're using Cygwin or MSYS, but I have no idea how effective they are.

A simple (and very very ugly) script that could work if you have sh handy (I don't have a Windows setup handy to test this on) would look something like this:

#!/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

This should be easy enough to translate into your scripting language of choice. If your program requires extra include paths, compiler flags, or whatever, make sure to add the necessary flags to both the test compile and the real compile.

'yourfile.c' would look something like this:

#include <windows.h>

unsigned long long get_tick_count(void) {
#ifdef CONF_HASGETTICKCOUNT64
  return GetTickCount64();
#else
  return GetTickCount();
#endif
}
Grandiloquent answered 18/6, 2009 at 14:17 Comment(0)
I
1

If your code is going to run on OSes berfore Vista, you can't just compile your calls down to GetTickCount64(), because GetTickCount64() doesn't exist on an XP machine.

You need to determine at runtime which operating system you are running and then call the correct function. In general both calls need to be in the code.

Now this may not be true in your case if you don't really need to be able to call either GetTickCount64() on Vista+ machines and GetTickCount() on XP- machines. You may be able to just call GetTickCount() no matter what OS you're running on. There is no indication in the docs that I have seen that they are removing GetTickCount() from the API.

I would also point out that maybe GetTickCount() isn't the right thing to use at all. The docs say it returns a number of milliseconds, but in reality the precision of the function isn't even close to 1 millisecond. Depending on the machine (and there's no way to know at runtime AFAIK) the precision could be 40 milliseconds or even more. If you need 1 millisecond precision you should be using QueryPerformanceCounter(). In fact, there's really no practical reason to not use QPC in all cases where you'd use GetTickCount() anyway.

Inefficacy answered 18/6, 2009 at 14:45 Comment(0)
D
0

The Microsoft compiler will define _WIN64 when compiling for 64 bit machines.

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
De answered 18/6, 2009 at 13:10 Comment(1)
thanks but the problem is that GetTickCount64 is also available on 32-bit systems: msdn.microsoft.com/en-us/library/ms724411(VS.85).aspxRefresher
V
0

If you have to support pre-Vista, I would stick with only using GetTickCount(). Otherwise you have to implement runtime code to check the Windows version and to call GetTickCount() on pre-Vista versions of Windows and GetTickCount64() on Vista and later. Since they return different sized values (ULONGLONG v DWORD) you'll also need to have separate handling of what they return. Using only GetTickCount() (and checking for overflow) will work for both situations, whereas using GetTickCount64() when it's available increases your code complexity and doubles the amount of code you have to write.

Stick with using only GetTickCount() until you can be sure your app no longer has to run on pre-Vista machines.

Villeneuve answered 18/6, 2009 at 19:13 Comment(0)
F
0

Maybe it is a good replacement for 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;
}
Fullerton answered 25/10, 2009 at 17:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.