Using an intermediate library to make the main library retargetable

A customer was developing a static library targetting both Windows XP Win32 applications and universal Windows apps. (This was before Windows XP reached end-of-life.)

Our library uses critical sections, but unfortunately there is no version Initialize­Critical­Section that is available to both Windows XP Win32 applications and universal Windows apps. Universal Windows apps must use Initialize­Critical­Section­Ex, but that function is not available to Windows XP Win32 applications. Is there a way to dynamically target both Windows XP Win32 applications and universal Windows apps, pass WACK validation, and still have one library?

We thought we could use Get­Module­Handle and Get­Proc­Address to detect which platform we are one, but Get­Module­Handle is not allowed in universal Windows apps, so we're back where we started.

Are we stuck having two versions of our library, one for Windows XP Win32 applications and one for universal Windows apps?

Runtime dynamic linking (Load­Library, Get­Proc­Address) is not permitted in universal Windows apps, which means that for universal Windows apps, you must have an entry for Initialize­Critical­Section­Ex in your import table. But if that function is in your input table, then it won't load on Windows XP.

(You might think that you could have a second library to be used by Windows XP clients that implements the Initialize­Critical­Section­Ex function. Unfortunately, you will run afoul of dllimport.)

You are going to have to have separate libraries at some point, but you don't have to have two versions of your library. You could build your library to call, say, Contoso­Initialize­Critical­Section, and have two helper libraries, one for Windows XP Win32 applications and one for universal Windows apps, each of which implement the Contoso­Initialize­Critical­Section function in a manner appropriate to the target.

In other words, people targeting Windows XP would link to ContosoCore.dll and ContosoXPSupport.dll. People writing universal Windows apps would link to ContosoCore.dll and ContosoStoreSupport.dll.

This approach has a few advantages:

  • It's simple, works (because it's so simple), and everybody understands it.
  • All the files in your core library need to be compiled only once.

The second clause pays off if your library is large, or if you need to add new operating system targets.

Update: I guess I didn't make it clear. My suggestion is that Contoso­Core.dll link to the nonexistent Contoso­Support.dll. If your program targets Windows XP, then rename Contoso­XP­Support.dll to Contoso­Support.dll. If your program is a universal Windows app, then rename Contoso­Store­Support.dll to Contoso­Support.dll.

This technique also works with static libraries. You have a single Contoso­Core.lib which calls a Contoso­Initialize­Critical­Section function. There are two implementations of Contoso­Initialize­Critical­Section, one in Contoso­XP­Support.lib and another in Contoso­Store­Support.lib. Each application chooses which support library to link in.

Comments (28)
  1. Joshua says:

    Or you delay-load kernel32.dll. This causes the missing symbol to become NULL. So the code reads (InitializeCriticalSectionEx)?(InitializeCriticalSectionEx (args):(InitializeCriticalSection(args));

    The MS linker cannot delay-load kernel32.dll but third party linkers can. The resulting binary is valid because it both early-loads and delay-load kernel32.dll.

    [And how do you think delay-load works? I'll tell you: It uses LoadLibrary and GetProcAddress. -Raymond]
  2. jon says:

    Or you just don't bother with the whole stupid Universal Apps concept in the first place.

  3. Karellen says:

    Hmmm…. I'm not sure I follow the logic here.

    Instead of having 2 versions of your library, one for each platform, you now have 1 version of your library… plus 2 versions of your support library, one for each platform. And your users, instead of linking to either ContosoXP.dll or ContosoStore.dll, have to link to ContosoCore.dll *and* either one of the support libraries.

    I'm not convinced you've simplified things. Surely the 2 self-contained libraries approach is *more* simple than the 1 complete library + 2 support libraries.

    As for the compilation phase, you should still only need to compile each file once. In each case compile core1.c, core2.c, core3.c, winxp.c and store.c into their respective .obj files. For the support library approach, you link core1.obj, core2.obj and core3.obj into one library, winxp.obj into another, and store.obj into the third library. While for the 2-library approach, you link core1.obj, core2.obj, core3.obj and winxp.obj into one library, and core1.obj, core2.obj, core3.obj and store.obj into the second. Done.

    I realise that whole-program optimisations mean that linking can be a slower process than it used to be, so doing that over core1.o, core2.o and core3.o twice could technically give you a significant build hit, but you almost certainly don't need to switch that on for work-in-progress dev builds.

  4. Avenida says:

    lol at the idea that Windows XP reaching end-of-life means developers have the luxury of no longer supporting it

  5. Mason Wheeler says:

    There is no problem in computer science that can't be resolved by adding another layer of indirection… except the problem of too many layers of indirection.

  6. acq says:

    Karellen, it's not that "users, instead of linking to either ContosoXP.dll or ContosoStore.dll, have to link to ContosoCore.dll *and* either one of the support libraries."

    It's that users of ContosoCore link only to ContosoCore. They have to provide ContosoXP in their package for XP but not to link to ContosoXP.

  7. acq says:

    Or not? I guess I have to understand for me too who is user here and who links what.

  8. J says:

    One advantage of the stub library method is that multiple components can make use of the same set of compatibility stubs. So application developers get to link against ContosoNetworking, ContosoLogging, ContosoUtils and (ContosoXPSupport|ContosoStoreSupport), rather than ContosoNetworkingXP, ContosoLoggingXP, ContosoUtilsXP…

  9. John Doe says:

    The thing is that ContosoCore.dll will still be different no matter what, because it can't use LoadLibrary, so the import tables must be different.

  10. laonianren says:

    I've got the same problem as John Doe.  When you link ContosoCore.dll you bake in the choice of ContosoXPSupport.dll or ContosoStoreSupport.dll.  So you've now got four DLLs.

    I suspect I'm missing the point.

  11. Falcon says:

    @laonianren, @John Doe:

    ContosoCore.dll could just link to ContosoSupport.dll. The appropriate helper library gets added to the package with the name ContosoSupport.dll.

    Example: The Windows HAL is always in hal.dll, but there are/were (I haven't bothered checking on recent versions) several different HALs shipped with Windows.

  12. John says:

    The solution is exactly what Falcon says, the fancy word for what is being described here is The Bridge Pattern.

    We leverage this heavily in our system to provide a HAL like assembly for our industrial hand-held scanners which have various implementations on how to interact with the scanning mechanism, at deployment time the correct assembly is chosen and then renamed to ScannerWedge.dll like Raymond's example this allows us to keep our library identical while parts that are at high rates of change (a particular manufacture loves to ship out new handhelds every year, every-time with a different API) are swapped out as needed.

  13. Myria says:

    "Runtime dynamic linking (Load­Library, Get­Proc­Address) is not permitted in universal Windows apps, which means that for universal Windows apps, you must have an entry for Initialize­Critical­Section­Ex in your import table. But if that function is in your input table, then it won't load on Windows XP."

    Runtime dynamic linking is allowed in Metro/Modern/Universal programs, just only with your own libraries, not system libraries.  See LoadPackagedLibrary.  GetProcAddress is allowed, but you're not supposed to be able to get HMODULEs to anything but yourself, or via LoadPackageLibrary.  (However, VirtualQuery can be used nefariously, a hole in the design.)

    [Even if you didn't have VirtualQuery, you can just scan memory in your address space looking for kernel32's header, so blocking VirtualQuery doesn't close any holes. Everything is already in-process. You're on the other side of the airtight hatchway. These are just obstacles to warn you to stay away from the things that aren't supported. -Raymond]
  14. Joshua says:

    [And how do you think delay-load works?]

    I am able to write a delay-load stub for kernel32.dll that only uses allowed APIs from…/mt186421.aspx . LoadLibrary() is not allowed, but GetModuleFileNameA() is. GetProcAddress() is allowed, but it would be a non-issue if it wasn't.

    And yes, I did know about the VirtualQuery hole. Incidentally, InitializeCriticalSection() is on the list so I presume you're using it as a proxy for another function that's not.

  15. Myria says:

    "Even if you didn't have VirtualQuery…other side of the airtight hatchway…"

    Mhmm, I know =^-^=

    There does seem to be some degree of support for delay-loaded APIs/DLLs for Universal applications: QueryOptionalDelayLoadedAPI.  However, I don't know much about it.

    Sadly, our program can't be a Universal application, because it updates itself, and because it does runtime code generation.

  16. Joshua says:

    > and because it does runtime code generation.

    That's what finding the VirtualQuery() hole was all about, to make it possible to get a reference to VirtualProtect() to enable runtime code generation for Universal applications.

  17. Neil says:

    Presumably there's going to be at least one symbol that the library user needs to import from ContosoXP or ContosoStore respectively to make it load, otherwise ContosoCore won't know which one to use?

  18. @Avenida: The end of support of Windows XP is only meaningful for Microsoft, not independent software developers. ISVs wanting to earn money must support every operating system that the user is probably running. Microsoft itself has no such mandate because it is a rich company that can turn a blind eye on that portion of income in exchange for acting consistently.

  19. Nick says:

    @Fleet Commander: I think you have the motivation wrong. Things fall out of support when supporting them becomes more expensive than the income generated from them. Some companies budget for a certain amount of extra support beyond the tipping point, others strictly stop as soon as it's not worth it.

  20. boogaloo says:

    @Fleet Command: I disagree, you should only support the customers choice of operating system if there is a compelling reason to do so. You should also charge legacy customers for the extra costs that you incur. Making your apps run on the latest operating system is much more important. If you focus on your legacy customers then you are just as likely to lose market share to someone that is focussing on the latest operating system and when your legacy customers are forced to buy a new machine they are just as likely to go for the more up to date software your competitor came out with.

    Every time we set a minimum OS and then backtracked, it always caused huge support issues going forward. If XP EOL didn't make your company sell your customers on the benefits of a new operating system then your company is pretty much doomed.

  21. Anon says:

    @boogaloo: Suddenly supporting a new (or old) system always causes issues. But I have some experience supporting XP and if you do it from the outset there are hardly any issues at all.

  22. cheong00 says:

    Regarding software can drop support for WinXP… as (not so) remotely related note, my project was just allowed to drop support for IE6 a few months ago.

    It could be expected most ISVs have to continue to support it at least 5 years (or 10 years for those unfortunate enough) after the EOL of WinXP.

  23. ask says:

    create 2 release configurations of your static lib, one with WINXP preprocessor definition. In the code:

    #ifdef WINXP





    build and ship two libraries separately via installer.

  24. boogaloo says:

    @anon/cheong00: I disagreed that you _MUST_ support old operating systems. If you are already supporting a huge number of customers running XP and you don't plan on rewriting your application to require any new technologies then sure continue supporting XP, but I'd say that was a big risk as someone could come in and eat your lunch. Like any risk it may pay off, it may not. If you can come up with a compelling reason why your application should stop supporting XP then you can sell that to your existing customers as well as your old. I've seen developers rely on Vista unpopularity to stay with XP but then Windows 7 came out and it was embarrassing seeing explaining how XP was better. It's normally because they rely on truly horrible technologies and third party components which makes their application unreliable on newer operating systems. Microsoft do still support XP and will produce updates for it until 2019 (…/details.aspx ). They do exactly what the ISVs should do, support something if it makes financial sense. If the last time a customer paid money to Microsoft was when they bought their PC in 2001 then I think it's justifiable that support can be pulled. An ISV will usually get a yearly support/license fee, but you should pass on any increased costs for supporting old operating systems. You will need to test on each operating system, keep skills current on each operating system, keep old hardware capable of running old operating systems etc. Even if you freeze your XP customers on old versions then you should charge extra for working on those old versions. Once people realise they are paying extra and just buying a new machine will cut that cost as well as making all their software run quicker you will see people upgrade. Of course you then get the problem of printer drivers etc, but it's really the ISVs fault for letting their customers stagnate so long.

  25. cheong00 says:

    Well, like most other developers the final say is on my boss, not me. As long as we have support contracts on companies that uses WinXP, we can't simply drop support from it or will face legal issue.

    And for reason on why people keep using WinXP, the most common reason is that they're using software which's vendor no longer exist and cannot run on Vista+, and they cannot allocate budget for finding other to rewrite it.

    Believe me, the situation is more common than you'd like to know, especially when there is / would be low economic season (people believe there is one coming already).

  26. Boris says:

    I just read 'retargetable' as 'regrettable'.

  27. Anon says:

    @Boogaloo: ‘someone could come in and eat your lunch.’

    That could always happen and has a lot more to do with the actual software you (and that ‘someone’) write than which operating systems you do and don't support. API-wise, Windows XP provides everything you actually *need*, and software developed for XP runs without a hitch on newer operating systems. (In my experience at least. I guess this would heavily depend on the assumptions you make about the target system when developing.) If you wanted to use bits of those newer operating systems, it would likely be for non-essential things that don't interfere with the rest of the software and can easily be turned off for Windows XP. I've never had cause to do so myself though.

  28. Skyborne says:

    XP support is always interesting. I broke some XP customers in the wake of Heartbleed, made another new SHA-1 cert to fix them, and only later discovered that SP3 supported SHA-2 for TLS.  However, the accidental break encouraged them to start a Win7 rollout, so we were free to go back to SHA-2 last fall.

    As for IE6, even the parent megacorp has updated its minimum spec to IE7/Vista. I'm hoping they can get a faster transition to Win7 than "1 year after MS EOL" because of Vista's relative unpopularity.

Comments are closed.

Skip to main content