延迟加载的 DLL 是否有助于避免链接特定函数?

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

考虑以下代码,它需要调用 User32.dll 中的两个函数之一。

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

如果我没记错的话,这个程序在 WinXP 上将无法启动,因为 AddClipboardFormatListener 在 XP 下的 User32.dll 中不存在。

解决这个问题的一种方法是不直接调用 AddClipboardFormatListener 而是我们自己获取指向它的指针:
GetProcAddress(GetModuleHandle("User32.dll"), "AddClipboardFormatListener").

但是,如果我指示链接器延迟加载 User32.dll...

  1. 这是否可以避免在 XP 下加载该特定函数,这样我就不需要调用 GetModuleHandleGetProcAddress
  2. 当只有少数功能需要延迟加载时,是否建议延迟加载 DLL?

User32.dll 的情况在第二点上尤为引人注目,因为程序中使用的大多数函数都存在于所有 Windows 版本的 DLL 中。
我猜想加载时的链接比 运行 时的链接更有效,因为后者在每次函数调用之前需要额外的检查。
但我只是在猜测,因此才有了这个问题。

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

是的。这正是延迟加载发明的情况类型。您的代码可以调用 DLL 函数,就好像它是静态链接的一样,但可执行文件不会在运行时加载 DLL 函数指针,直到第一次实际调用该函数。在内部,延迟加载机制为您使用 LoadLibrary()GetProcAddress()

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

如果一个DLL是延迟加载的,那么它的所有函数都是延迟加载的,你不能挑选你想要的。所以,如果你的应用程序需要使用同一个 DLL 中的很多函数,比如 user32.dll,那么静态链接通常更有效,然后你可以手动使用 GetProcAddress() 来获得你真正需要的少数函数以不同的方式处理。

在这种情况下,我建议完全取消 OS 检查,只依赖 DLL 函数是否实际存在,例如:

typedef BOOL (WINAPI *LPFN_ACFL)(HWND);

LPFN_ACFL lpAddClipboardFormatListener = (LPFN_ACFL) GetProcAddress(GetModuleHandle(TEXT("user32")), "AddClipboardFormatListener");

if( lpAddClipboardFormatListener != NULL )
    lpAddClipboardFormatListener(hWnd);
else
    SetClipboardViewer(hWnd);

延迟加载真正闪耀的地方在于它的钩子。例如,在较早的系统上,您可以使用延迟加载失败挂钩来实现您自己的 AddClipboardFormatListener() 版本,然后您的主代码可以在所有系统上无条件地调用 AddClipboardFormatListener() 而它不会知道区别。例如(只是演示,没有实际测试):

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);
}