2

考虑以下代码,它需要调用User32.dll.

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

如果我没记错的话,这个程序在WinXP下会启动失败,因为在XP下AddClipboardFormatListener不存在User32.dll

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

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

  1. 这是否会避免在 XP 下加载该特定功能,以便我不需要调用GetModuleHandleand GetProcAddress
  2. 当只需要延迟加载几个函数时,是否建议延迟加载 DLL?

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

4

1 回答 1

4

这是否会避免在 XP 下加载该特定功能,以便我不需要调用GetModuleHandleand GetProcAddress

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

当只需要延迟加载几个函数时,是否建议延迟加载 DLL?

如果一个 DLL 是延迟加载的,它的所有函数都是延迟加载的,你无法选择你想要的那些。因此,如果您的应用程序需要使用来自同一个 DLL 的大量函数,例如user32.dll,那么静态链接通常更有效,然后您可以GetProcAddress()手动使用您真正需要以不同方式处理的少数函数。

在这种情况下,我建议完全摆脱操作系统检查,只依赖 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);
}
于 2017-07-28T00:47:37.107 回答