There is an interesting article about checking the version of the operating system. If you are too lazy to read it then just remember this: Do not check it at all!
Well, I changed my mind. You should read it! So go to Yochay Kiriaty’s Article, read it and then get back her for the Delphi part.

What have we learned from the article?

  1. Avoid version checks at all
  2. Check for functionality
  3. Use the right functions
  4. Learn logic

1. It is always frustrating if an application doesn’t start because of version checking.

If you have a Win32API function that is not available on older OS, do not use a version check. If you don’t dive the internal structures of Window it is very likely that your application will work.

2. Instead: try to load the function with GetProcAddress. If it fails, well then use something else.

That is the reason why I tell people to set the DYNAMIC_LINK compiler switch when using any of the JEDI functions. In this case the JEDI API libraries loads a function using GetProcedureAddress (located in JwaWinTypes – don’t ask me why it is located there).
GetProcedureAddress can raise two types of Exception.

  • EJwaLoadLibraryError : If a necessary library could not be found.
  • EJwaGetProcAddressError : If the function could not be located in the library.

So now it is easy to write an exception handler:

try
  API_Call(...);
except
  on E1 : EJwaLoadLibraryError do
    //fail safe library not found
  on E2 : EJwaGetProcAddressError do
   //fail safe function not found
end;

Well, this code only works if you define DYNAMIC_LINK in your project options. Don’t forget it! If it is not defined, and you try to run your application on an older Windows, it just won’t start. Instead you get a nasty error message.

Delphi 2010 comes with a new feature that MS Visual C++ people know as Linker support for Delay-loaded DLLs.
Allen Bauer posted an article about this new feature for Delphi under the strange headline Procrastinators Unite… Eventually! You can also think of it as “Delay loaded functions for Delphi”.

function NewAPIFunction(Parameter : TParameter): BOOL; stdcall;
  external 'user32.dll' name 'NewAPIFunction' delayed;
...

begin
  NewAPIFunction(Parameter);
end;

Do you recognize the new word “delayed” ? If you try to access the function for the first time, it will be loaded automatically from the library “user32.dll”. So we don’t have to be afraid of running the application on an older Windows version. Unfortunately, we also have to add code that checks for the Windows version.

begin
  if OSVersion > 5.1 then
    NewAPIFunction(Parameter);
end;

(Don’t be confused about GreaterThan. There is a bug in the code plugin that prevents the greater sign to be shown correctly.)

Haven’t we learned that it is not good to check for the OS version? Shouldn’t we check for functionality? Apparently, delay loading in Delphi 2010 wasn’t created with this idea in mind.
If we change the library name or the function name to something incorrect, like:

function NewAPIFunction(Parameter : TParameter): BOOL; stdcall;
  external 'user32Bla.dll' name 'NewAPIFunction' delayed;
...

begin
  NewAPIFunction(Parameter);
end;

…and call this function, at least, I get a nasty EExternalException with the message “External Exception C0FB007E“.
Even using the correct library but an incorrect function name doesn’t change the exception type, but the message :
“External Exception C0FB007F“.

Well, It could have been so nice! And in Update 2 we’re going to see how…

function NewAPIFunction(Parameter : TParameter): BOOL; stdcall;
  external 'user32.dll' name 'NewAPIFunction' delayed;
...

begin
  try
    NewAPIFunction(Parameter);
  on E1 : ELoadLibraryError do
    //fail safe library not found
  on E2 : EGetProcAddressError do
   //fail safe function not found
end;

Instead we need to check:

function NewAPIFunction(Parameter : TParameter): BOOL; stdcall;
  external 'user32.dll' name 'NewAPIFunction' delayed;
...

begin
  try
    NewAPIFunction(Parameter);
  on E : EExternalException do
    //failsafe
end;

However, I wouldn’t even be sure that this exception type is always the same. EExternalException is always suspect to me, isn’t it? So in the end it is all about Exception.

UPDATE

I was so short sighted! To the COM people C0FB007E should look quite familiar. Yeah, it is a HRESULT code. It maps to

Severity (ERROR_SEVERITY_ERROR = $C) + Facility Code ($FB) + Code (7E and 7F)

Well $7E is 126 in decimal and can be found in JwaWinError.pas as ERROR_MOD_NOT_FOUND. Same goes for $7F =127 which is ERROR_PROC_NOT_FOUND.

Knowing this we can use:

begin
  try
    NewAPIFunction(Parameter);
  on E : EExternalException do
     RaiseLastOSError(Windows.HResultCode(E.ExceptionRecord.ExceptionCode));
  end;
end;

This exception should be known to Delphi programmers. And you can add another try except block around to catch the Win32 exception.

The exception EExternalException comes from an internal call to RaiseException.

UPDATE2:

If you read the system.pas file from Delphi 2010 you’ll find the function SetDliFailureHook. It allows us to get notified when an delayed loading error occured. So how does it look?

uses
  Windows,
  SysUtils;

type
  ELoadLibraryError = class(EWin32Error);
  EGetProcAddressError = class(EWin32Error);

function DelayedFailureHook(dliNotify: dliNotification; pdli: PDelayLoadInfo): Pointer; stdcall;
begin
  case dliNotify of
    dliNoteStartProcessing: ;
    dliNotePreLoadLibrary: ;
    dliNotePreGetProcAddress: ;
    dliFailLoadLibrary   :
      raise ELoadLibraryError.CreateFmt(
        'Failed to load library "%0:s".'#13#10' Error (%1:d) %2:s',[AnsiString(pdli.szDll),
          pdli.dwLastError, SysErrorMessage(pdli.dwLastError)]);
    dliFailGetProcAddress:
      if pdli.dlp.fImportByName then
        raise EGetProcAddressError.CreateFmt(
            'Failed to load function "%0:s" from "%1:s"'#13#10' Error (%2:d) %3:s',[
           AnsiString(pdli.dlp.szProcName), AnsiString(pdli.szDll),
          pdli.dwLastError, SysErrorMessage(pdli.dwLastError)])
      else
        raise EGetProcAddressError.CreateFmt(
            'Failed to load function #%0:d from "%1:s"'#13#10' Error (%2:d) %3:s',[
           pdli.dlp.dwOrdinal, AnsiString(pdli.szDll),
          pdli.dwLastError, SysErrorMessage(pdli.dwLastError)]);

    dliNoteEndProcessing: ;
  end;
end;

...
begin
  SetDliFailureHook(DelayedFailureHook);

  try
    NewAPIFunction(Parameter);
  on E1 : ELoadLibraryError do
    //fail safe library not found
  on E2 : EGetProcAddressError do
   //fail safe function not found
  end;
end.

All the other errors we don’t process are converted to an EExternalException error automatically. You can try it out by removing the case dliFailLoadLibrary or dliFailGetProcAddress.

By the way:

  1. The function pointer of NewAPIFunction is not nil. It points to an address that does the magic behind the curtain.
  2. If you wonder why the JEDI Windows API hasn’t implemented this feature: We are still in pre alpha stage.
    You could help by telling us your experience with this feature.

3. Using the right functions can be a bit problematic. There are a lot of possible functions you can use:

  1. JwaWinBase/Windows contains GetVersion and GetVersionEx
  2. There is also a more complex function called VerifyVersionInfo(Ex)
  3. And Delphi comes with a function of its own in SysUtils : CheckWin32Version

CheckWin32Version is little known to Delphi programmers. Instead they create their own complicated classes and tool functions. But if you just want to check for the major and minor version of the OS, try this:

if CheckWin32Version(5, 1) then

It checks for Windows XP regardless of a service pack. It is the easiest and safest way to check the OS version. But sometimes you need also to know the service pack or even more.

GetVersion and GetVersionEx can be hard to be used. So Microsoft added support for a more flexible and safe way to check the OS version. VerifyVersionInfo was born.

I have translated Yochay Kiriaty’s example of VerifyVersionInfo for you.

uses
  JwaWinNT,
  JwaNative,
  JwaWinBase;

function IsWindowsXPorLater : Boolean;
const
  Condition = VER_GREATER_EQUAL;
var
  OSInfo : TOSVersionInfoEx;
  ConditionMask : Int64;
begin
  ZeroMemory(@OsInfo, sizeof(OSInfo));
  OSInfo.dwOSVersionInfoSize := SizeOf(OSInfo);
  OSInfo.dwMajorVersion := 5;
  OSInfo.dwMinorVersion := 1;
  OSInfo.wServicePackMajor := 1;
  OSInfo.wServicePackMinor := 0;

  ConditionMask := 0;
  ConditionMask := VerSetConditionMask(ConditionMask, VER_MAJORVERSION, Condition);
  ConditionMask := VerSetConditionMask(ConditionMask, VER_MINORVERSION, Condition);
  ConditionMask := VerSetConditionMask(ConditionMask, VER_SERVICEPACKMAJOR, Condition);
  ConditionMask := VerSetConditionMask(ConditionMask, VER_SERVICEPACKMINOR, Condition);

  result := VerifyVersionInfo(OSInfo, VER_MAJORVERSION or VER_MINORVERSION or
     VER_SERVICEPACKMAJOR or VER_SERVICEPACKMINOR,
     ConditionMask);
end;

I hope more people are going to use this function.

4. In the end I also want to write something about logic.

If you wonder why so many applications ceased to work when Windows Vista came out. Well, if you look at this code you should know why (Windows XP: 5.1 -> Vista: 6.0)

 if (MajorVersion >= 5) and (MinorVersion >= 1) then
 begin 'OK'
 end
 else
 begin
 'Sorry. Old Windows version detected'
 end;

Well, such a (similar) code is (still) used a lot of times. And it seems to work for Windows 2000 and XP. But in Vista MajorVersion was changed to 6 and, because it was the very first release of version 6, MinorVersion was set to zero. So the if condition stopped working on the second condition check. (BTW: This is a reason why Windows 7 is using version 6.1 instead of version 7.0 .)
Sometimes we need to apply the basic rules of logic (read a good book from Amazon) or check (nearly) all possible values in a table:

MajorVersion MinorVersion Condition Result
4 1 False
4 2 False
4 3 False
5 0 False
5 1 True
5 2 True
5 3 True
6 0 False
6 1 True
7 0 False
7 1 True

Not all Windows versions in this table really exists. But how could you know that at that time? So make the table as complete as possible. Then you can change the variables Win32MajorVersion and Win32MinorVersion to the test your if condition and run your application. Software engineers also use unit tests to check the correctness efficiency of their functions.

All in all, if the people of the bad applications had used a function like CheckWin32Version, we hadn’t this article now:

function CheckWin32Version(AMajor: Integer; AMinor: Integer = 0): Boolean;
 begin
 Result := (Win32MajorVersion >AMajor) or
 ((Win32MajorVersion = AMajor) and
 (Win32MinorVersion >= AMinor));
 end;

But then we couldn’t blame Microsoft for their bad compatibility. *g*