windowswinapidlldllimportdelay-load

Are delay-loaded DLLs useful to avoid linking specific functions?


Consider the following code which needs to call one of two functions in User32.dll.

if( IsWindowsVistaOrGreater() )
    AddClipboardFormatListener(hWnd) ;
else
    SetClipboardViewer(hWnd) ;

If I'm not mistaken, this program will fail to start on WinXP because AddClipboardFormatListener does not exist in User32.dll under XP.

One way to solve this is not calling AddClipboardFormatListener directly but rather get a pointer to it ourselves:
GetProcAddress(GetModuleHandle("User32.dll"), "AddClipboardFormatListener").

However, if I instruct the linker to delay-load User32.dll...

  1. Would this avoid loading that specific function under XP so that I don't need to call GetModuleHandle and GetProcAddress?
  2. Is it recommended to delay-load a DLL when only a few functions need to be delay-loaded?

The case of User32.dll is particularly dramatic on the second point since most of the functions used in the program are know to exist in that DLL on all Windows versions.
I would guess that linking at load-time is more efficient than at run-time since the latter needs additional checks before each function call.
But I'm just guessing, hence the question.


Solution

  • Would this avoid loading that specific function under XP so that I don't need to call GetModuleHandle and GetProcAddress?

    Yes. This is exactly the type of situation that delay-loading was invented for. your code can call a DLL function as if it were statically linked, but the executable will not load the DLL function pointer at runtime until the function is actually called for the first time. Internally, the delay-load mechanism uses LoadLibrary() and GetProcAddress() for you.

    Is it recommended to delay-load a DLL when only a few functions need to be delay-loaded?

    If a DLL is delay-loaded, ALL of its functions are delay-loaded, you cannot pick and choose which ones you want. So, if your app needs to use a lot of functions from the same DLL, like user32.dll, then static linking is usually more efficient, and then you can use GetProcAddress() manually for the few functions you really need to handle differently.

    In this case, I would suggest getting rid of the OS check altogether and rely only on whether the DLL function actually exists or not, eg:

    typedef BOOL (WINAPI *LPFN_ACFL)(HWND);
    
    LPFN_ACFL lpAddClipboardFormatListener = (LPFN_ACFL) GetProcAddress(GetModuleHandle(TEXT("user32")), "AddClipboardFormatListener");
    
    if( lpAddClipboardFormatListener != NULL )
        lpAddClipboardFormatListener(hWnd);
    else
        SetClipboardViewer(hWnd);
    

    Where delay-loading really shines is in its hooks. For instance, on earlier systems, you can use a delay-load failure hook to implement your own version of AddClipboardFormatListener(), and then your main code can just call AddClipboardFormatListener() unconditionally on all systems and it won't know the difference. For example (just a demo, not actually tested):

    LRESULT CALLBACK ClipboardSubClassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
    {
        switch (uMsg)
        {
            case WM_NCDESTROY:
                RemoveWindowSubclass(hWnd, &ClipboardSubClassProc, uIdSubclass);
                break;
    
            case WM_CHANGECBCHAIN:
            {
                if (wParam == dwRefData) 
                    SetWindowSubclass(hWnd, &ClipboardSubClassProc, uIdSubclass, lParam);
    
                else if (dwRefData != 0) 
                    SendMessage((HWND)dwRefData, uMsg, wParam, lParam); 
    
                break;
            }
    
            case WM_DRAWCLIPBOARD:
            {
                SendMessage(hWnd, WM_CLIPBOARDUPDATE, 0, 0);
    
                if (dwRefData != 0)
                    SendMessage((HWND)dwRefData, uMsg, wParam, lParam);
    
                break;
            }
        }
    
        return DefSubclassProc(hWnd, uMsg, wParam, lParam);
    }
    
    BOOL WINAPI My_AddClipboardFormatListener(HWND hWnd)
    {
        HWND hWndNext = SetClipboardViewer(hWnd);
        if ((!hWndNext) && (GetLastError() != 0))
            return FALSE;
    
        if (!SetWindowSubclass(hWnd, &ClipboardSubClassProc, 1, (DWORD_PTR)hWndNext))
        {
            DWORD dwErr = GetLastError();
            ChangeClipboardChain(hWnd, hwndNext);
            SetLastError(dwErr);
            return FALSE;
        }
    
        return TRUE;
    }
    
    BOOL WINAPI My_RemoveClipboardFormatListener(HWND hWnd)
    {
        DWORD_PTR dwRefData;
        if (!GetWindowSubclass(hWnd, &ClipboardSubClassProc, 1, &dwRefData))
        {
            SetLastError(ERROR_NOT_FOUND);
            return FALSE;
        }
    
        RemoveWindowSubclass(hWnd, &ClipboardSubClassProc, 1);
    
        return ChangeClipboardChain(hWnd, (HWND)dwRefData);
    }
    
    FARPROC WINAPI MyDliFailureHook(unsigned dliNotify, PDelayLoadInfo pdli)
    {
        if ((dliNotify == dliFailGetProc) && (pdli->dlp.fImportByName))
        {
            if (strcmp(pdli->dlp.szProcName, "AddClipboardFormatListener") == 0)
                return (FARPROC) &My_AddClipboardFormatListener;
    
            if (strcmp(pdli->dlp.szProcName, "RemoveClipboardFormatListener") == 0)
                return (FARPROC) &My_RemoveClipboardFormatListener;
        }
    
        return NULL;
    }  
    
    __pfnDliFailureHook2 = &MyDliFailureHook;
    
    ...
    
    LRESULT CALLBACK MyWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        switch (uMsg)
        {
            case WM_CREATE:
                AddClipboardFormatListener(hWnd);
                break;
    
            case WM_DESTROY: 
                RemoveClipboardFormatListener(hWnd);
                break;
    
            case WM_CLIPBOARDUPDATE:
                // do all of your clipboard processing here...
                break;
    
            ...
        }
    
        return DefWindowProc(hWnd, uMsg, wParam, lParam);
    }