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?
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.
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.
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.
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.
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:
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.
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:
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*