Udostępnij za pośrednictwem


Delay Load is not a good way to check for functionality

On my previous post, Koro made the following comment:

“Don't ever check windows versions.  Instead check for functionality being present or not."

You can't always do that.

Do I want to add a __try/__except to catch delay-load exceptions around every UxTheme call or just do:

g_bTheme=(g_bWinNT&&(g_nWinVer>0x00050001));

Then check that flag before calling OpenThemeData?

In some other cases too (all the Crypt Hash functions - trying to compute an MD5) the functions is documented as working fine in Win98 but it just fails - there is no way to know except of checking the version beforewards.

At least, as implied earlier, I just pack the Windows version in a DWORD at program startup to avoid nasty version comparision errors.

IMHO Koro’s misusing the delayload functionality.

 

DelayLoad is primarily a performance tool – when you DelayLoad a function, a tiny stub for the DelayLoad function is inserted into your application which calls LoadLibrary/GetProcAddress on the function.  That then means that when your application is launched, the loader doesn’t resolve references to the DelayLoaded function and thus your application will launch faster.

For example many components in the OS delay load WINMM.DLL because all they use in WINMM is the PlaySound API, and even then they only use it on relatively rare circumstances.  By delayloading WINMM.DLL they avoid having the performance penalty of having WINMM.DLL loaded into their address until it’s needed.

 

As Koro mentioned, DelayLoad can also be used as a mechanism to check to see if a particular piece of OS functionality is present, but the challenge is that now you need to wrap every API call with an exception handler (or you need to specify a delay load handler that provides a more reasonable default behavior).  Personally I wouldn’t do that – instead I’d manually call LoadLibrary/GetProcAddress to load the required functions because it allows you to have complete control over when you access over your error handling.  It also allows you to avoid using structured exception handling (which should be avoided if at all possible).

 

If you DO have need to use DelayLoad as a functionality check, you could try this trick (which works only for Koro’s problem).  Instead of wrapping all the theme API calls with SEH, you just add code to your app like this (I haven’t compiled this code, it’s just an example):

 BOOL g_EnableThemes = FALSE;
__try
{
    g_EnableThemes = IsThemeActive();
}
__except(<Your Exception Filter>)
{
}
 if (g_EnableThemes)

{
    g_ThemeHandle = OpenThemeData(…)





}
 .csharpcode, .csharpcode pre
{
   font-size: small;
   color: black;
   font-family: consolas, "Courier New", courier, monospace;
   background-color: #ffffff;
  /*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt 
{
   background-color: #f4f4f4;
  width: 100%;
    margin: 0em;
}
.csharpcode .lnum { color: #606060; }

In other words check for functionality being enabled once with an exception handler and later on use just the EnableThemes global variable to key off the behavior.

But this doesn’t change the fact that (IMHO) you’re abusing the DelayLoad functionality and using it as a versioning mechanism.

Comments

  • Anonymous
    March 06, 2009
    PingBack from http://www.anith.com/?p=16239

  • Anonymous
    March 06, 2009
    Unless I mis-reading yours, I think you're mis-reading his comment. He's saying that he doesn't want to use a __try/__except around a delay-load, but would rather use version checking.

  • Anonymous
    March 06, 2009
    And I'm saying that instead of using a version check, use LoadLibrary/GetProcAddress - that way you don't need to do either.

  • Anonymous
    March 06, 2009
    Maybe someone else will like this. I use a lot of LoadLibrary/GetProcAddress calls and I got sick of having to put the function signatures in two places, slightly different to each other. Then I realised a simple template could be used so you only have to put the signature in once: (Hope this stuff pastes okay!) template < typename T > class GetProcAddr { public: T f; __forceinline GetProcAddr(FARPROC (WINAPI *fpGetProcAddress)(HMODULE hModule, LPCSTR lpProcName), HMODULE hModule, const char *lpProcName) { f = reinterpret_cast< T >(fpGetProcAddress(hModule, lpProcName)); } }; You can then use that like this: GetProcAddr< HRESULT (STDAPICALLTYPE *)(LPVOID pvReserved) > tfpCoInitialize( pArgs->fpGetProcAddress, hModuleOle32, pArgs->szCoInitialize ); if (0 != tfpCoInitialize.f) { tfpCoInitialize.f(NULL) } (CoInitialize is a bit of a silly example of course, but it's a function with a short arg list that came to mind.) It'd be great of the compiler had support that allowed you to say "the same type as CoInitialize" so that you didn't have to look the function signature up in the header and work out how to turn that into a C type (which has painful syntax) with the correct calling convention etc. Or does the compiler have something like that which I don't know of? It'd also be great if there was an easier way to use SDK defines for later versions of Windows -- not just functions but flags and (extended) structures -- in a way where you could say, for example: "I am targeting Windows 2000 as a baseline, but I want to be able to use features from Vista. However, I should have to explicitly tell the compiler where it's okay to use features that are not in Windows 2000 and I want to get an error if I fail to do so." Right now the choice is to either: a) Set the SDK #defines to target Vista, and then risk accidentally using features that don't work on earlier versions which you only find through testing. Or: b) Set the SDK #defines to target Windows 2000, and then copy & paste any extra stuff you need from the headers. (And if you can be bothered, do that in a way which prevents you using it by accident outside of blocks that have checked for the existence of features.) The way it is now is understandable but it'd be great if it was made less painful. I think it would encourage people to take advantage of new OS features more (rather than encourage people to support old OS for longer, which I think people will do regardless).

  • Anonymous
    March 06, 2009
    Wow, I get my own post :) I'd like to add a little about this... First, yes, LoadLibrary+GetProcAddress is great in the case where you have only one or two functions to import and/or can't use delay-loading (such as KERNEL32 or USER32 exports). But it becomes a pain to do it on a large scale. In this case especially, doing UI code that calls in UxTheme usually means calling 20 or so different UxTheme exports, some of which may change over the course of development - this week you need to call GetThemeColor, next week you change the rendering logic and you don't need it anymore, or something like that - and you don't want to have to maintain a list of manual GetProcAddress calls. Also, calling IsThemeActive one time at program startup is bad, it can change if themes get enabled or disabled, and anyway OpenThemeData will return NULL if no theme is active, no need to check if one's active first. Which means I'd have to put the __try/__except everywhere anyway. As for version checking, I agree that it's sometimes bad. But in this specific case, I know, and the documentation spells you, that UxTheme APIs are available on Windows XP and higher. It's not like Windows 2000 will suddenly start supporting them, or another weird case that would make my version check not work. For stuff whose installation state can change (redistributable packages, "not-so" documented functions that are subject to be removed in subsequent versions of Windows) I DO check by presence of feature if possible. Finally, some of the stuff can hardly be "checked for presence". Think "new flags to existing functions", like flags to SystemParametersInfo, GetSysColor(COLOR_HOTLIGHT), or the BIF_USENEWUI flag for SHBrowseForFolder. These are all actual examples from my code. Quite old though, I now develop only for Windows 2000 and up, but still, with the advent of Vista, it just added a new bunch of things I have to look for.

  • Anonymous
    March 06, 2009
    Why do my comments take so much time to appear? To add on my previous (still not displayed) comment, I'd like to say that I guess the "proper" way of checking for presence with DelayLoad would be: __try {    __HrLoadAllImportsForDll(_sz_UxTheme_dll);    g_bTheme=TRUE; } __except(DelayLoadExceptionFilter(GetExceptionInformation())) {    g_bTheme=FALSE; }

  • Anonymous
    March 06, 2009
    My usage example was slightly obfuscated due to the unusual project I copied it from. The pArgs stuff can be removed so the usage looks like this: GetProcAddr< HRESULT (STDAPICALLTYPE *)(LPVOID pvReserved) > tfpCoInitialize( ::GetProcAddress, hModuleOle32, "CoInitialize" ); if (0 != tfpCoInitialize.f) { tfpCoInitialize.f(NULL); } Obviously hModuleOle32 is the result of a LoadLibrary call.

  • Anonymous
    March 06, 2009
    Gah, posting before awake enough. :) Apologies. You can simplify the code even more. It's the way it was because it was being used for code injected into other processes, where you can't just call GetProcAddress. In a normal process you could just do this: { public: T f; __forceinline GetProcAddr(HMODULE hModule, const char *lpProcName) { f = reinterpret_cast< T >(::GetProcAddress(hModule, lpProcName)); } }; GetProcAddr< HRESULT (STDAPICALLTYPE *)(LPVOID pvReserved) > tfpCoInitialize( hModuleOle32, "CoInitialize"); if (0 != tfpCoInitialize.f) { tfpCoInitialize.f(NULL); } Sorry for the multiple follow-ups! Hope someone finds this useful. :-)

  • Anonymous
    March 06, 2009
    LoadLibrary/GetProcAddress() is not always safe without a version check. I got burnt by this when I added dynamic linking to DWMAPI.DLL to interact with the Desktop Window Manager on Vista and then my program started blowing up on some XP systems. Apparently a number of XP systems have the Vista DWMAPI.DLL installed -- a lot of fingers have been pointed at Internet Explorer 7 as the culprit -- and if you don't do a version check as well, you end up loading this bogus DLL and failing with an error dialog about a missing import in USER32.

  • Anonymous
    March 06, 2009
    The comment has been removed

  • Anonymous
    March 07, 2009
    Koro: usual warning for using __HrLoadAllImportsForDll: http://developertips.blogspot.com/2007/07/delay-load-dll-and-hrloadallimportsford.html

  • Anonymous
    March 07, 2009
    "By delayloading WINMM.DLL they avoid having the performance penalty of having WINMM.DLL loaded into their address until it’s needed." I believe 'space' is missing here. "It also allows you to avoid using structured exception handling (which should be avoided if at all possible)." Why? Here are a few ideas I can think of. a) You can't get it right every time, not worth it. b) You mess up somehow and corrupt critical data structures. c) OS doesn't want to play with you.

  • Anonymous
    March 08, 2009
    There are a few reasons to avoid SEH. (Or, at least, to use it only as a last-resort safety net and not for normal flow-control.) One is that, unlike C++ exceptions, if an SEH exception is thrown then the stack isn't unwound. Destructors don't run and resources are not freed or released (in general). The compiler will complain at you if it looks like you're mixing SEH and objects on the stack which have destructors, even if you've been careful to ensure the destructors do get called. That can force you to move your code around and make it less clear than it was. Equally, the compiler can (does) miss the fact you are calling sub-routines which depend on destuctors so the warnings cannot be relied upon (but are still better than nothing). I expect that using SEH (or exceptions in general) for flow control is also slower than using normal language flow control (e.g. if-statements). Also, any code that routinely triggers exception (of any type) can be a pain to debug. The VS debugger has an option to automatically break on exceptions and that is extremely useful when trying to track down bugs that are throwing exceptions, except if there are a lot of non-bugs that throw them all the time as well.

  • Anonymous
    March 08, 2009
    "LoadLibrary/GetProcAddress() is not always safe without a version check. I got burnt by this when I added dynamic linking to DWMAPI.DLL to interact with the Desktop Window Manager on Vista and then my program started blowing up on some XP systems. Apparently a number of XP systems have the Vista DWMAPI.DLL installed -- a lot of fingers have been pointed at Internet Explorer 7 as the culprit -- and if you don't do a version check as well, you end up loading this bogus DLL and failing with an error dialog about a missing import in USER32." Except that IE 7 on XP do not install DWMAPI.DLL, so the user must have copied it from Vista, which is unnecessary because the IEFRAME.DLL dependency on DWMAPI.DLL is a delay-load one, which is only called if a version check shows that the OS is Vista or later.

  • Anonymous
    March 17, 2009
    "One is that, unlike C++ exceptions, if an SEH exception is thrown then the stack isn't unwound. Destructors don't run and resources are not freed or released (in general)." /EHa tries to fix this.