In addition to what DavidHeffernan said, you have a bigger problem to solve. Status callbacks are assigned on a per-HINTERNET
basis, but you are treating them as a single global callback, which will not work. You have to keep track of each individual HINTERNET
handle that is passed to InternetSetStatusCallback()
so you can then call its appropriate callback from inside of your callback, based on the HINTERNET
specified.
You also need to be able to remove HINTERNET
handles from your tracking list when they are closed. You could use the INTERNET_STATUS_HANDLE_CLOSING
status for that, however the documentation says that it is only triggered for HINTERNET
handles that have a non-zero Context
value assigned. So you will have to hook InternetCloseHandle()
to account for HINTERNET
handles that have a zero Context
.
Try something more like this:
unit HttpMonitor;
interface
uses
Windows, WinInet, System.Generics.Collections;
type
// The WinInet unit maps INTERNET_STATUS_CALLBACK to a mere TFarProc, so
// let's spell out its parameters so we can actually make calls to it
// when needed...
INTERNET_STATUS_CALLBACK_TYPE = procedure(hInet: HINTERNET; dwContext: DWORD_PTR; dwInternetStatus: DWORD; lpvStatusInformation: LPVOID; dwStatusInformationLength: DWORD); stdcall;
THttpMonitor = class
private
FCallbacks: TDictionary<HINTERNET, INTERNET_STATUS_CALLBACK_TYPE>;
FInternetCloseHandle: function(hInet: HINTERNET): BOOL; stdcall;
FInternetSetStatusCallback: function(hInet: HINTERNET; lpfnInternetCallback: INTERNET_STATUS_CALLBACK_TYPE): INTERNET_STATUS_CALLBACK_TYPE; stdcall;
public
class function InternetCloseHandle(hInet: HINTERNET): BOOL; stdcall; static;
class function InternetSetStatusCallback(hInet: HINTERNET; lpfnInternetCallback: INTERNET_STATUS_CALLBACK_TYPE): INTERNET_STATUS_CALLBACK_TYPE; stdcall; static;
class procedure InternetStatusCallback(hInet: HINTERNET; dwContext: DWORD_PTR; dwInternetStatus: DWORD; lpvStatusInformation: LPVOID; dwStatusInformationLength: DWORD); stdcall; static; static;
constructor Create;
destructor Destroy; override;
end;
var
HttpMonitor: THttpMonitor = nil;
implementation
class function THttpMonitor.InternetCloseHandle(hInet: HINTERNET): BOOL; stdcall;
begin
HttpMonitor.FCallbacks.Remove(hInet);
Result := FInternetCloseHandle(hInet);
end;
class procedure THttpMonitor.InternetStatusCallback(hInet: HINTERNET; dwContext: DWORD_PTR; dwInternetStatus: DWORD; lpvStatusInformation: LPVOID; dwStatusInformationLength: DWORD); stdcall;
var
Callback: INTERNET_STATUS_CALLBACK_TYPE;
begin
//...
if HttpMonitor.FCallbacks.TryGetValue(hInet, Callback) then
begin
if Assigned(Callback) then
Callback(hInet, dwContext, dwInternetStatus, lpvStatusInformation, dwStatusInformationLength);
end;
end;
class function THttpMonitor.InternetSetStatusCallback(hInet: HINTERNET; lpfnInternetCallback: INTERNET_STATUS_CALLBACK_TYPE): INTERNET_STATUS_CALLBACK_TYPE; stdcall;
begin
HttpMonitor.FCallbacks.TryGetValue(hInet, Result);
HttpMonitor.FCallbacks.AddOrSetValue(hInet, lpfnInternetCallback);
FInternetSetStatusCallback(hInet, @THttpMonitor.InternetStatusCallback);
end;
constructor THttpMonitor.Create;
begin
inherited;
FCallbacks := TDictionary<HINTERNET, INTERNET_STATUS_CALLBACK_TYPE>.Create;
@FInternetCloseHandle := InterceptCreate('wininet.dll', 'InternetCloseHandle', @THttpMonitor.InternetCloseHandle);
@FInternetSetStatusCallback := InterceptCreate('wininet.dll', 'InternetSetStatusCallback', @THttpMonitor.InternetSetStatusCallback);
end;
destructor THttpMonitor.Destroy;
var
item: TPair<HINTERNET, INTERNET_STATUS_CALLBACK_TYPE>;
begin
if Assigned(FInternetSetStatusCallback) then
begin
for item in FCallbacks do
FInternetSetStatusCallback(item.Key, nil);
InterceptRemove(FInternetSetStatusCallback);
end;
if Assigned(FInternetCloseHandle) then
InterceptRemove(FInternetCloseHandle);
FCallbacks.Free;
inherited;
end;
end.
uses
..., HttpMonitor;
procedure TForm1.FormCreate(Sender: TObject);
begin
HttpMonitor := THttpMonitor.Create;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
HttpMonitor.Free;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
Webrowser1.Navigate('www.stackoverflow.com');
end;
With that said, there is one last problem to solve, and I do not have a solution for that - how to assign your callback to an HINTERNET
handle that never gets passed to InternetSetStatusCallback()
so you see it? InternetStatusCallback()
does have an INTERNET_STATUS_HANDLE_CREATED
status available, but the documentation states that it is only triggered by InternetConnect()
. There are other WinInet function that create HINTERNET
handles. So you may need additional hooks to account for all of the HINTERNET
handles that you are interested in hooking status for.