after we pass pointer to callback function - module which containing this callback function of course must not be unloaded, until callback may be called. this is of course not problem if callback function was in EXE module, which never will be unloaded. but in case DLL we need add reference to DLL before set callback, for prevent DLL unload. this is easy can be done via GetModuleHandleExW
function
HMODULE hmod;
GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (PCWSTR)&__ImageBase, &hmod);
ok, but how not let DLL be unloaded ? we need dereference it (call FreeLibrary
) when our callback will be no more called. when this will be ?
the InternetSetStatusCallback
sets up a callback function for some handle. this handle of course should ever be closed via InternetCloseHandle
and:
It is safe to call InternetCloseHandle in a callback for the handle
being closed. If there is a status callback registered for the handle
being closed, and the handle was created with a non-NULL context
value, an INTERNET_STATUS_HANDLE_CLOSING callback will be made. This
indication will be the last callback made from a handle and indicates
that the handle is being destroyed.
If asynchronous requests are pending for the handle or any of its
child handles, the handle cannot be closed immediately, but it will be
invalidated. Any new requests attempted using the handle will return
with an ERROR_INVALID_HANDLE notification. The asynchronous requests
will complete with INTERNET_STATUS_REQUEST_COMPLETE. Applications must
be prepared to receive any INTERNET_STATUS_REQUEST_COMPLETE
indications on the handle before the final
INTERNET_STATUS_HANDLE_CLOSING indication is made, which indicates
that the handle is completely closed.
so INTERNET_STATUS_HANDLE_CLOSING
- will be the last callback made from a handle. finall call. exactly at this time we need defereference DLL module - callback will be no more called. not need reference more.
ok. when is clear. but how ? we can not direct call FreeLibrary
from this point, because if DLL live on this last reference - it will be unloaded inside FreeLibrary
call and when we return - we return to the emply space and crash.
we can not call FreeLibraryAndExitThread
too - because we on arbitrary thread. however exist not correct but worked on practic solution:
ULONG WINAPI SafeUnload(void*)
{
//Sleep(*);
FreeLibraryAndExitThread((HMODULE)&__ImageBase, 0);
}
if (HANDLE hThread = CreateThread(0, 0, SafeUnload, 0, 0, 0))
{
CloseHandle(hThread);
}
we can create new thread yourself, and from this thread call FreeLibraryAndExitThread
(here this call is correct). but how i say - this is theoretically not correct solution. here exist race - the FreeLibraryAndExitThread
call can execute and unload DLL before we return from CreateThread
call. as result we crash just after CreateThread
(we try execute already unloaded code). of course this is unlikely, but..
we can insert Sleep
(some delay) before call FreeLibraryAndExitThread
but anyway - can be race theoretically for any delay. again what concrete value for delay select ?! and DLL will be delayed unload for this time. not correct and not nice. despite this is relative simply "solution" i be not use it.
correct solution will be - call (more exactly jump) to FreeLibrary
without return to place, from where this jump will be. but hust return to place, from where callback was called. this is impossible in c/c++ but possible in asm code. of course this require separate asm file for every platform target (x86, x64 how minimum)
so correct and elegant solution is next: the INTERNET_STATUS_CALLBACK
callback function anyway must be associated with some class, where we hold handle context. this must be static function in class and DWORD_PTR dwContext - application-defined context value associated with hInternet must point to the instance of class (by fact this pointer). usually this is done in next way:
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
);
};
be here we need implement _StatusCallback
in asm code for call FreeLibrary
here. and chage return value of StatusCallback
from void to BOOL - TRUE we return if this is final call (dwInternetStatus == INTERNET_STATUS_HANDLE_CLOSING
) and FALSE otherwise.
so begin solution
// 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
base class skeleton:
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
implementation:
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;
}
how we register callback (in my code inside SendRequest
for handle returned from 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();
}
so here we call GetModuleHandleExW
for add reference to DLL before set callback. if set callback fail - we call FreeLibrary
for deference DLL. note that here call FreeLibrary
safe and correct, because the one who called SendRequest
must have reference for DLL - DLL of course must not be unloaded during this call. so when we call FreeLibrary
from this function - we guaranteed to release not the last reference to DLL (we free reference from GetModuleHandleExW
call, but exist additional reference on DLL during this function executing. the caller (direct or indirect) of this function care about this). in place dwContext we pass this pointer.
so now final part - _StatusCallback
implementation.
for 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
for 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