2015-01-19 5 views
-3

Я пытаюсь подключить асинхронный обратный вызов от Wininet, который вызывается из TWebbrowser. Однако после завершения подключения происходит ошибка. Почему это происходит?Hooking InternetStatusCallback

Первое исключение случайности в $ 0018B7A2. Класс исключения $ C000008C с границами массива сообщений превышен на 0x0018b7a2. Process Project3.exe (3292)

THttpMonitor = class 
    private 
    FInternetStatusCallback: procedure(hInternet: HINTERNET; dwContext: DWORD_PTR; dwInternetStatus: DWORD; lpvStatusInformation: LPVOID; dwStatusInformationLength: DWORD); 
    FInternetSetStatusCallback: function(hInet: HINTERNET; lpfnInternetCallback: PFNInternetStatusCallback): PFNInternetStatusCallback; stdcall; 
    public 
     class function InternetSetStatusCallback(hInet: HINTERNET; lpfnInternetCallback: PFNInternetStatusCallback): PFNInternetStatusCallback; stdcall; static; 
     class procedure InternetStatusCallback(hInternet: HINTERNET; dwContext: DWORD_PTR; dwInternetStatus: DWORD; lpvStatusInformation: LPVOID; dwStatusInformationLength: DWORD); stdcall; static; 
     constructor Create; 
     destructor Destroy; override; 
    end; 

class procedure THttpMonitor.InternetStatusCallback(hInternet: HINTERNET; dwContext: DWORD_PTR; dwInternetStatus: DWORD; lpvStatusInformation: LPVOID; dwStatusInformationLength: DWORD); 
begin 
    HttpMonitor.FInternetStatusCallback(hInternet, dwContext, dwInternetStatus, lpvStatusInformation, dwStatusInformationLength); 
end; 

class function THttpMonitor.InternetSetStatusCallback(hInet: HINTERNET; lpfnInternetCallback: PFNInternetStatusCallback): PFNInternetStatusCallback; stdcall; 
begin 
    HttpMonitor.FInternetStatusCallback := @lpfnInternetCallback; 
    Result := HttpMonitor.FInternetSetStatusCallback(hInet, @HttpMonitor.InternetStatusCallback); // ERROR! 
end; 

constructor THttpMonitor.Create; 
begin 
    FInternetSetStatusCallback := InterceptCreate('wininet.dll', 'InternetSetStatusCallback', @InternetSetStatusCallback); 
end; 

destructor THttpMonitor.Destroy; 
begin 
    InterceptRemove(FInternetSetStatusCallback); 
    inherited; 
end; 

.... 

procedure TForm1.Button1Click(Sender: TObject); 
begin 
    Webrowser1.Navigate('www.stackoverflow.com'); 
end; 

ответ

1

В дополнение к тому, что сказал Дэвид Хеффернан, у вас есть большая проблема для решения. Обратные вызовы статуса назначаются на основе HINTERNET, но вы рассматриваете их как один глобальный обратный вызов, который не будет работать. Вы должны отслеживать каждый обработчик HINTERNET, который передается в InternetSetStatusCallback(), чтобы затем вызывать его соответствующий обратный вызов изнутри вашего обратного вызова на основе указанного HINTERNET.

Вы также должны уметь удалять HINTERNET ручки из списка отслеживания, когда они закрыты. Вы можете использовать для этого статус INTERNET_STATUS_HANDLE_CLOSING, однако в документации говорится, что он активирован только для дескрипторов HINTERNET, которые имеют ненулевое значение Context. Таким образом, вам придется зацепить InternetCloseHandle() для учета HINTERNET ручек с нулем Context.

Попробуйте что-то больше, как это:

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; 

С учетом сказанного, есть одна последняя проблема, решить, и я не есть решение для этого - как назначить callback к дескриптору HINTERNET, который никогда не передается InternetSetStatusCallback(), так что вы видите его? InternetStatusCallback() имеет статус INTERNET_STATUS_HANDLE_CREATED, но в документации указывается, что он активируется только InternetConnect(). Существуют и другие функции WinInet, которые создают дескрипторы HINTERNET. Таким образом, вам могут потребоваться дополнительные крючки для учета всех дескрипторов HINTERNET, на которые вы заинтересованы.

+0

Гораздо лучше в глубине ответить. С другой стороны, я знаю об этом. –

+0

Однако. 'InternetSetStatusCallback' вызывается только один раз? –

+0

Если это вызов только один раз, то есть только 1 дескриптор 'HINTERNET', который' TWebBrowser' заинтересован в получении статуса, хотя может быть несколько дескрипторов HINTERNET, участвующих в операции HTTP. Почему вы подключаете 'InternetSetStatusCallback()' для начала? Какой статус вы ищете, что вы не можете получить из собственных событий TWebBrowser? –

1

Вы берете адрес переменной, содержащей указатель на функцию. Но вам нужно запомнить указатель на функцию.

Таким образом, вместо

HttpMonitor.FInternetStatusCallback := @lpfnInternetCallback; 

вам нужно

HttpMonitor.FInternetStatusCallback := lpfnInternetCallback; 

И следующая строка должна быть

Result := HttpMonitor.FInternetSetStatusCallback(hInet, lpfnInternetCallback) 

Вы можете обнаружить, что позволяет напечатанный вариант адреса компилятора поможет.

Вы также пропустили stdcall в декларации FInternetStatusCallback.

+0

Уверены ли вы? Это приводит к этому 'E2010 Несовместимые типы:« Процедура »и« PFNInternetStatusCallback » –

+1

Имейте в виду, что я пропускаю все типы defs, знание ваших параметров компилятора, подробное знание обходной библиотеки lib. Суть проблемы в том, что я видел. Конечно, вы можете увидеть ошибочное направление. –

+0

Это было решение :) 'HttpMonitor.FInternetStatusCallback: = INTERNET_STATUS_CALLBACK (lpfnInternetCallback); \t Результат: = HttpMonitor.FInternetSetStatusCallback (hInet, INTERNET_STATUS_CALLBACK (@InternetStatusCallback)); ' –