延迟加载的 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
...
- 这是否可以避免在 XP 下加载该特定函数,这样我就不需要调用
GetModuleHandle
和 GetProcAddress
?
- 当只有少数功能需要延迟加载时,是否建议延迟加载 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);
}
考虑以下代码,它需要调用 User32.dll
中的两个函数之一。
if( IsWindowsVistaOrGreater() )
AddClipboardFormatListener(hWnd) ;
else
SetClipboardViewer(hWnd) ;
如果我没记错的话,这个程序在 WinXP 上将无法启动,因为 AddClipboardFormatListener
在 XP 下的 User32.dll
中不存在。
解决这个问题的一种方法是不直接调用 AddClipboardFormatListener
而是我们自己获取指向它的指针:
GetProcAddress(GetModuleHandle("User32.dll"), "AddClipboardFormatListener")
.
但是,如果我指示链接器延迟加载 User32.dll
...
- 这是否可以避免在 XP 下加载该特定函数,这样我就不需要调用
GetModuleHandle
和GetProcAddress
? - 当只有少数功能需要延迟加载时,是否建议延迟加载 DLL?
User32.dll
的情况在第二点上尤为引人注目,因为程序中使用的大多数函数都存在于所有 Windows 版本的 DLL 中。
我猜想加载时的链接比 运行 时的链接更有效,因为后者在每次函数调用之前需要额外的检查。
但我只是在猜测,因此才有了这个问题。
Would this avoid loading that specific function under XP so that I don't need to call
GetModuleHandle
andGetProcAddress
?
是的。这正是延迟加载发明的情况类型。您的代码可以调用 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);
}