You can peek to see whether your delay-loaded function loaded successfully

The Query­Optional­Delay­Loaded­API function lets you ask whether a function marked as delay-loaded was in fact found. Here's a tiny demonstration:

#include <windows.h>
#include <commdlg.h>
#include <libloaderapi2.h>
#include <stdio.h>

#define HMODULE_THISCOMPONENT reinterpret_cast<HMODULE>(&__ImageBase)

int __cdecl main(int argc, char** argv)
    if (QueryOptionalDelayLoadedAPI(HMODULE_THISCOMPONENT,
          "comdlg32.dll", "GetOpenFileNameW", 0))
        printf("GetOpenFileNameW can be called!\n");
    return 0;

This gives you function-by-function granularity on checking whether a delay-loaded function was successfully loaded, which is an improvement over being told whether all the imports for a DLL were loaded.

Note also that the original problem with the Win16 model for weak linking wasn't that developers built but never ran their programs. Developers built their programs, and they ran fine on all the systems they tested because the function was present on all the systems they tested. It never occurred to them that the function might not exist in the first place. I mean, suppose you wrote a 16-bit program that called Get­Open­File­Name. It runs great on all your systems! But oh no, you get a report from a customer that it crashes on their system. The reason: COMMDLG.DLL was not a mandatory OS component. Users had the option of installing Windows without it, at which point all the programs that called Get­Open­File­Name would start crashing.

Win32's response to this was "If you want weak linking, you know where to find it." Namely, Get­Proc­Address. The fact that you called a function to get an address will hopefully remind you to check whether the function actually succeeded.

The introduction of the Query­Optional­Delay­Loaded­API function is to allow Store apps (which are not allowed by policy to call Load­Library) to detect whether their delay-loaded function actually got loaded. The fact that the requested functions are in the delay-loaded function table means that a static analysis can still find all the functions that the program could potentially call.

Comments (18)
  1. kantos says:

    Odd they added a new ANSI API. I had thought adding new ANSI APIs was explicitly banned in WINDIV. When I checked it appears there is no Unicode version of this.

    1. SimonRev says:

      The reason, I imagine, is because there is no GetProcAddressW function. Function exports are in ANSI (or possibly even ASCII).

      I do find it mildly surprising that the filename parameter isn’t Unicode though. I wonder what you would do if the DLL file were inexpressable in the current character set.

      1. kantos says:

        so apparently the method name parameter is safe because “Error C3872 ‘0x2019’: this character is not allowed in an identifier”. But I was able to make a dll with the name 💩.dll, although it did give a linker warning that it might not load on other systems, so in theory the module name parameter should be Unicode.

        1. Myria says:

          If you name a DLL with non-ASCII letters, you won’t be able to hard-link programs to the import library and have them run properly. This is because the DLL name in the PE import table format is ASCII – the OS won’t be able to load the DLL.

          However, if you’re willing to always use LoadLibrary[Ex]W+GetProcAddress with the DLL, Unicode DLL names are fine.

          1. kantos says:

            I tested this this morning, it seems that the linker is mixed in some ways. It will generate a UTF-8 exports table. But the Module itself can’t be linked because the .lib parser does require CP1252 for some reason. In theory the fix should be upgrading the lib parser to support a flag saying that it’s UTF-8 and then make all future libraries use that.

        2. Yukkuri says:

          Poop dot dll. Now that’s honest naming!

      2. kantos says:

        I correct myself identifiers can be unicode, just not emoji

        1. Karellen says:

          Note that a platform ABI may be more strict than the C++ language allows for. The PE file format may restrict identifiers more than the C++ language allows for. And the Windows platform may further restrict identifiers more than its file formats allow.

          Conversely, a platform ABI may be less strict than the C++ language allows. The C++ language disallows identifiers that are also C++ keywords, e.g. “class”. But the Windows platform may allow an identifier called “class” – you just can’t use it natively from C++. You may have to use LoadLibrary()/GetProcAddress() to bind it to a different identifier in your program.

      3. Myria says:

        The filename parameter isn’t UTF-16 because this function is reading the PE headers and delay-load import table of hParentModule. The DLL names are in ASCII in the import table, so you might as well use the same format for the API. Same for the function name.

    2. Myria says:

      The DLL names and export names in the PE file format are byte strings, not UTF-16 strings, so it wouldn’t serve a purpose. It’s more like they are ASCII, rather than ANSI.

  2. Yuhong Bao says:

    AFAIK COMMDLG was not optional in Win3.1, but it was not part of Win3.0 and programs was supposed to redistribute it onto Win3.0 systems.

  3. Joshua says:

    Incidentally, you can always call LoadLibrary. Procedure:

    1) Take the address of ReadFileW. It will be in your own image area (trampoline placed by the linker to reduce the number of fixups).
    2) Follow the jmp instruction at the address of ReadFileW to get the real address.
    3) Ascend memory until you find MZ at the start of a page with the PE header pointer pointing to a valid PE header.
    4) Interpret the executable image to find the exports section. Walk the exports table for LoadLibrary.

    1. Stephen says:

      5) Hope that the bytes ‘MZ’ doesn’t appear anywhere else.


      1. Joshua says:

        Non-issue. You keep going until you decode a valid header.

    2. Tautvydas Zilys says:

      Or just use VirtualQuery instead of scanning memory manually. Way less error prone.

  4. Medinoc says:

    But since it’s a Windows-10-only function, you still need to access it dynamically (through delay-loading if you’re on a Metro app?) if you want to run on earlier platforms, so there’s a bit of a chicken-and-egg scenario here…

    1. skSdnW says:

      It exists in 8/8.1 as well, never trust MSDN version numbers!

  5. Neil says:

    Actually even on Win16 your program wouldn’t even start if the DLL hadn’t been installed. Where things went wrong is when the installed version wasn’t new enough and was missing an export that the program needed. Or both programs had installed the DLL locally, which didn’t work when you started the older program first. Or someone else’s broken installer had downgraded the shared copy of the DLL to an earlier version.

Comments are closed.

Skip to main content