c++ - 如何安全地防止在 Wininet 中调用状态回调函数?
问题描述
我们在 DLL 中使用 WinInet 进行异步网络调用。
当应用程序退出时,我们通过使用删除挂起请求的注册回调函数InetSetStatusCallback(connect_handle, NULL);
。但是,有时在 DLL 卸载后仍会调用回调函数,从而导致应用程序崩溃。
症状与本博客的最后一个常见问题解答完全相同:WinHTTP Questions: About Callbacks
我试图找出一种方法来安全地删除所有挂起请求的回调函数,以便在卸载 DLL 后 WinInet 不会调用它们。
解决方案
在我们将指针传递给回调函数之后——当然不能卸载包含这个回调函数的模块,直到回调可能被调用。如果回调函数在EXE模块中,这当然没有问题,它永远不会被卸载。但如果是DLL,我们需要在设置回调之前添加对DLL的引用,以防止DLL卸载。这很容易可以通过GetModuleHandleExW
函数来完成
HMODULE hmod;
GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (PCWSTR)&__ImageBase, &hmod);
好的,但如何不让DLL被卸载?FreeLibrary
当我们的回调不再被调用时,我们需要取消引用它(调用)。这会是什么时候?
为InternetSetStatusCallback
某个句柄设置回调函数。这个句柄当然应该通过InternetCloseHandle
和关闭:
在关闭句柄的回调中调用InternetCloseHandle是安全的。如果为正在关闭的句柄注册了状态回调,并且该句柄是使用非 NULL 上下文值创建的,则将进行INTERNET_STATUS_HANDLE_CLOSING回调。该指示将是从句柄发出的最后一个回调,并指示句柄正在被销毁。
如果该句柄或其任何子句柄的异步请求处于挂起状态,则该句柄不能立即关闭,但会失效。尝试使用句柄的任何新请求都将返回ERROR_INVALID_HANDLE通知。异步请求将通过INTERNET_STATUS_REQUEST_COMPLETE完成。应用程序必须准备好在最终的INTERNET_STATUS_HANDLE_CLOSING指示发出 之前接收句柄上的任何INTERNET_STATUS_REQUEST_COMPLETE指示,这表明句柄已完全关闭。
所以INTERNET_STATUS_HANDLE_CLOSING
- 将是从句柄发出的最后一个回调。最后打电话。正是在这个时候,我们需要取消引用DLL模块——回调将不再被调用。不需要更多参考。
好的。什么时候清楚。但如何?我们不能FreeLibrary
从这一点直接调用,因为如果DLL存在于最后一个引用上——它将在FreeLibrary
调用内部被卸载,当我们返回时——我们返回到 emply 空间并崩溃。
我们也不能调用FreeLibraryAndExitThread
- 因为我们在任意线程上。然而存在不正确但在实际解决方案中工作:
ULONG WINAPI SafeUnload(void*)
{
//Sleep(*);
FreeLibraryAndExitThread((HMODULE)&__ImageBase, 0);
}
if (HANDLE hThread = CreateThread(0, 0, SafeUnload, 0, 0, 0))
{
CloseHandle(hThread);
}
我们可以自己创建新线程,并从这个线程调用FreeLibraryAndExitThread
(这里这个调用是正确的)。但我怎么说 - 这在理论上不是正确的解决方案。这里存在竞争-在我们从调用返回之前FreeLibraryAndExitThread
,调用可以执行和卸载DLL 。结果我们在之后崩溃(我们尝试执行已经卸载的代码)。当然这不太可能,但是..CreateThread
CreateThread
我们可以Sleep
在通话之前插入(一些延迟),FreeLibraryAndExitThread
但无论如何 - 理论上可以为任何延迟而竞争。再次延迟选择的具体价值是什么?!并且DLL将被延迟卸载。不正确也不好看。尽管这是相对简单的“解决方案”,但我不会使用它。
正确的解决方案将是 - 调用(更确切地说是跳转)到FreeLibrary
不返回该跳转的位置。但必须回到调用回调的地方。这在c/c++中是不可能的,但在asm代码中是可能的。当然,这需要每个平台目标的单独asm文件(x86,x64最低)
接下来是正确而优雅的解决方案:INTERNET_STATUS_CALLBACK
无论如何,回调函数必须与某个类相关联,我们在其中保存句柄上下文。this 必须是类中的静态函数,并且DWORD_PTR dwContext - 与 hInternet 关联的应用程序定义的上下文值必须指向类的实例(实际上是this指针)。通常这是通过以下方式完成的:
class REQUEST_CONTEXT
{
// ...
static void WINAPI _StatusCallback(
__in HINTERNET hRequest,
__in DWORD_PTR dwContext,
__in DWORD dwInternetStatus,
__in LPVOID lpvStatusInformation,
__in DWORD dwStatusInformationLength
)
{
reinterpret_cast<REQUEST_CONTEXT*>(dwContext)->StatusCallback(
hRequest,
dwInternetStatus,
lpvStatusInformation,
dwStatusInformationLength
);
}
void StatusCallback(
__in HINTERNET hRequest,
__in DWORD dwInternetStatus,
__in LPVOID lpvStatusInformation,
__in DWORD dwStatusInformationLength
);
};
在这里,我们需要_StatusCallback
在asm代码中实现以便FreeLibrary
在此处调用。并将返回值StatusCallback
从void更改为BOOL -如果这是最终调用 ( )我们返回TRUE ,否则返回FALSE。dwInternetStatus == INTERNET_STATUS_HANDLE_CLOSING
所以开始解决
// helper for get complex c++ names for use in asm code
#if 0
#define ASM_FUNCTION {__pragma(message(__FUNCDNAME__" proc\r\n" __FUNCDNAME__ " endp"))}
#define CPP_FUNCTION __pragma(message("extern " __FUNCDNAME__ " : PROC ; " __FUNCSIG__))
#else
#define ASM_FUNCTION
#define CPP_FUNCTION
#endif
基类骨架:
class REQUEST_CONTEXT
{
// ...
static void WINAPI _StatusCallback(
__in HINTERNET hRequest,
__in DWORD_PTR dwContext,
__in DWORD dwInternetStatus,
__in LPVOID lpvStatusInformation,
__in DWORD dwStatusInformationLength
) ASM_FUNCTION;
BOOL StatusCallback(
__in HINTERNET hRequest,
__in DWORD dwInternetStatus,
__in LPVOID lpvStatusInformation,
__in DWORD dwStatusInformationLength
);
ULONG SendRequest(PCWSTR pwszObjectName);
void OnRequestComplete(HINTERNET hRequest, INTERNET_ASYNC_RESULT* pres);
};
StatusCallback
执行:
BOOL REQUEST_CONTEXT::StatusCallback(
__in HINTERNET hRequest,
__in DWORD dwInternetStatus,
__in LPVOID lpvStatusInformation,
__in DWORD dwStatusInformationLength
)
{
CPP_FUNCTION;
switch (dwInternetStatus)
{
case INTERNET_STATUS_HANDLE_CLOSING:
Release();
return TRUE;
case INTERNET_STATUS_REQUEST_COMPLETE:
OnRequestComplete(hRequest, (INTERNET_ASYNC_RESULT*)lpvStatusInformation);
break;
}
return FALSE;
}
我们如何注册回调(在我的代码中SendRequest
用于返回的句柄HttpOpenRequestW
)
ULONG SendRequest(PCWSTR pwszObjectName)
{
// ... some code ...
AddRef();
HMODULE hmod;
if (GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (PCWSTR)&__ImageBase, &hmod))
{
if (HINTERNET hRequest = HttpOpenRequestW(..., (DWORD_PTR)this))
{
set_handle(hRequest);
if (INTERNET_INVALID_STATUS_CALLBACK != InternetSetStatusCallback(hRequest, _StatusCallback))
{
INTERNET_ASYNC_RESULT ar;
ar.dwResult = HttpSendRequestW(hRequest, 0, 0, 0, 0);
ar.dwError = ar.dwResult ? NOERROR : GetLastError();
if (ar.dwError != ERROR_IO_PENDING)
{
OnRequestComplete(hRequest, &ar);
}
return NOERROR;
}
}
FreeLibrary(hmod);
}
Release();
return GetLastError();
}
所以这里我们要求在设置回调之前GetModuleHandleExW
添加对DLL的引用。如果设置回调失败 - 我们要求FreeLibrary
尊重DLL。注意这里调用FreeLibrary
安全正确,因为调用的人SendRequest
必须有DLL的引用——DLL当然不能在这个调用过程中被卸载。所以当我们FreeLibrary
从这个函数调用时——我们保证不会释放对DLL的最后一个引用(我们从调用中释放引用,但在这个函数执行期间存在对DLLGetModuleHandleExW
的附加引用。这个函数的调用者(直接或间接)关心这个) . 在dwContext我们通过这个指针。
所以现在是最后一部分 -_StatusCallback
实施。
对于x64 ( ml64 /c /Cp $(InputFileName) -> $(InputName).obj )
extern __imp_FreeLibrary:QWORD
extern __ImageBase:BYTE
; void REQUEST_CONTEXT::StatusCallback(void *,unsigned long,void *,unsigned long)
extern ?StatusCallback@REQUEST_CONTEXT@@QEAAHPEAXK0K@Z : PROC
.CODE
?_StatusCallback@REQUEST_CONTEXT@@SAXPEAX_KK0K@Z proc
xchg rcx,rdx
mov rax,[rsp + 28h]
sub rsp,38h
mov [rsp + 20h],rax
call ?StatusCallback@REQUEST_CONTEXT@@QEAAHPEAXK0K@Z
add rsp,38h
test eax,eax
jnz @@1
ret
@@1:
lea rcx, __ImageBase
jmp __imp_FreeLibrary
?_StatusCallback@REQUEST_CONTEXT@@SAXPEAX_KK0K@Z endp
end
对于x86 ( ml /c /Cp $(InputFileName) -> $(InputName).obj )
.MODEL FLAT
extern __imp__FreeLibrary@4:DWORD
extern ___ImageBase:BYTE
; int __thiscall REQUEST_CONTEXT::StatusCallback(void *,unsigned long,void *,unsigned long)
extern ?StatusCallback@REQUEST_CONTEXT@@QAEHPAXK0K@Z : PROC
.CODE
?_StatusCallback@REQUEST_CONTEXT@@SGXPAXKK0K@Z proc
mov ecx,[esp + 8]
push [esp + 20]
push [esp + 20]
push [esp + 20]
push [esp + 16]
call ?StatusCallback@REQUEST_CONTEXT@@QAEHPAXK0K@Z
test eax,eax
jnz @@1
ret 4*5
@@1:
mov eax,[esp]
lea edx, ___ImageBase
add esp,4*4
mov [esp],eax
mov [esp + 4],edx
jmp __imp__FreeLibrary@4
?_StatusCallback@REQUEST_CONTEXT@@SGXPAXKK0K@Z endp
end
推荐阅读
- c++ - 传递给新线程的结构具有错误的值
- c++ - C ++获取对类变量的访问权限
- clojure - clojure 源文件的定时加载
- java - 带有 CardLayout 的 JPanel 在更改时为空白
- git - 如何修复 git log --graph 中的对齐问题
- angular - angular 5下载PDF
- c - 使用 gcc 在 Windows 上编译 C ... -lWs2_32
- c - 在 C 中读取命令行参数
- python - plumbum.commands.processes.ProcessExecutionError:对于返回 null 的命令
- .net-core - 该项目使用 Microsoft.NETCore.App 版本 2.1.3 恢复,但使用当前设置,将使用版本 2.1.3-servicing-26724-03