|
Broken links? Email us! |
|
If you have ever dig into
the MSDN for WinInet API, you noticed that it can be used asynchronously and
that it is the recommended way to use it.
If then you decide to use it, you won’t find any explanation of how to use it
asynchronously. And no samples are available anywhere on internet.
After a long research and lots of testing, I’ll finally manage to reconstruct a
big part of the (voluntary?) missing documentation.
Why asynchronous is better
? Because it can handle timeouts correctly. Just what’s missing in Wininet under
IE5.5.
If you try to use TerminateThread or CloseHandle functions to handle timeouts
(theses methods are given in MSDN articles), you’ll fall into unrecoverable
leaks of all kinds.
This has been tested successfully
with: IE4.01SP3, IE5.0, IE5.01, IE5.5SP1 under WinNT4 on mono and multiprocessor
machines, under a stressed environment (15 concurrent instances running non-stop
for 12 hours on multiproc NT server machines).
Theory
To use WinInet functions in
Full Asynchronous Mode, you must do things in the right order:
1 – Use INTERNET_FLAG_ASYNC
to open the session
2 – Set a status callback using InternetSetStatusCallback
3 – Open the connection using InternetOpenUrl
4 – If InternetOpenUrl returned NULL and GetLastError is ERROR_IO_PENDING:
* wait for the INTERNET_STATUS_HANDLE_CREATED notification in the callback, and
save the connection Handle.
* wait for the INTERNET_STATUS_REQUEST_COMPLETE notification in the callback
before going further.
5 – Extract the content-length from the header and set up an INTERNET_BUFFERS
structure:
dwStructSize = sizeof(INTERNET_BUFFERS)
lpvBuffer = your allocated buffer
dwBufferLength = its length
6 – Use InternetReadFileEx with the IRF_ASYNC flag to read the remaining data
asynchronously. Don’t use InternetReadFile since it is a synchronous function.
7 – If InternetReadFileEx returned False and GetLastError is ERROR_IO_PENDING:
* wait for the INTERNET_STATUS_REQUEST_COMPLETE notification in the callback
before going further.
Warning: INTERNET_BUFFERS members are modified asynchronously (only the dwBufferLength
member and the content of the buffer).
8 – If the dwBufferLength member is not 0, move the lpvBuffer pointer from this
amount and substract this amout from the buffer length so dwBufferLength reflects
the remaining size lpvBuffer points to, then loop to 6.
9 – Close the connection handle with InternetCloseHandle and wait for INTERNET_STATUS_HANDLE_CLOSING
and the facultative INTERNET_STATUS_REQUEST_COMPLETE notification (sent only if
an error occur – like a sudden closed connection -, you must test the cases).
At this state, you can
either begin a new connection process or close the session handle. But before
closing it, you should unregister the callback function.
Detail
After the Theory, let’s
look at some code for some of the points:
1&2: Create the
connection using INTERNET_FLAG_ASYNC and setup the callback func:
m_Session = InternetOpen(AGENT_NAME, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL,
INTERNET_FLAG_ASYNC);
InternetSetStatusCallback( m_Session, (INTERNET_STATUS_CALLBACK)InternetCallbackFunc
);
3&4: Open
the connection using InternetOpenUrl and wait for INTERNET_STATUS_REQUEST_COMPLETE
Use the lParam to send a session identifier to your callback. I always use the this
pointer of my class for it. I assume you know how to handle callbacks.
InternetOpenUrl( m_Session, uurl, NULL, 0, INTERNET_FLAG_RELOAD |
INTERNET_FLAG_PRAGMA_NOCACHE | INTERNET_FLAG_NO_CACHE_WRITE, (LPARAM)this );
The callback will receive a lots of messages then. Here their orders along with
the "dwInternetStatus" value:
[openUrl] InternetStatus: 60 INTERNET_STATUS_HANDLE_CREATED
**At this point you can save the HINTERNET handle using code like this in your
callback:
INTERNET_ASYNC_RESULT* res = (INTERNET_ASYNC_RESULT*)lpvStatusInformation;
m_hHttpFile = (HINTERNET)(res->dwResult);
[openUrl] InternetStatus: 10
[openUrl] InternetStatus: 11
[openUrl] InternetStatus: 20
[openUrl] InternetStatus: 21
[openUrl] InternetStatus: 30
[openUrl] InternetStatus: 31
[openUrl] InternetStatus: 40
[openUrl] InternetStatus: 41
[openUrl] InternetStatus: 100 INTERNET_STATUS_REQUEST_COMPLETE
5: Extract the content-length and set
up the INTERNET_BUFFERS structure
Once you have the handle, try to call HttpQueryInfo with
HTTP_QUERY_CONTENT_LENGTH to get the size of the data to retrieve. This
function can fail if the content-length field is not in the HTTP header.
Set up the INTERNET_BUFFERS structure
INTERNET_BUFFERS ib = { sizeof(INTERNET_BUFFERS) };
ib.lpvBuffer = your allocated buffer
ib.dwBufferLength = its length
The dwBufferTotal is provided for your own use and is never modified by WinInet
(as far as I know).
I use it to store the total size of the received data.
6&7&8 Read
the remaining data in a loop
Use InternetReadFileEx with the IRF_ASYNC flag to read the remaining data
asynchronously. Don’t use InternetReadFile since it is a synchronous function.
You must loop on InternetReadFileEx while the ib.dwBufferLength is not 0.
Before each iteration you must adjust the lpvBuffer pointer and reset the dwBufferLength
members of ib: add the received length to the pointer and set dwBufferLength to
your remaining buffer size.
//Start the pump
BOOL bOk = InternetReadFileEx( m_hHttpFile, &ib, IRF_ASYNC, (LPARAM)this );
if(!bOk && GetLastError()==ERROR_IO_PENDING)
wait...
//Pump
while( bOk && ib.dwBufferLength!=0 )
{
(adjust ib values)
bOk = InternetReadFileEx( m_hHttpFile,
&ib, IRF_ASYNC, (LPARAM)this );
if(!bOk &&
GetLastError()==ERROR_IO_PENDING)
wait...
}
Your callback should receive these notifications (maybe more than once):
[connect] InternetStatus: 40 (receiving response)
[connect] InternetStatus: 41 (response received)
[connect] InternetStatus: 50
[connect] InternetStatus: 51
and maybe [connect] InternetStatus: 100 INTERNET_STATUS_REQUEST_COMPLETE
The lattest is received
only if GetLastError() returned ERROR_IO_PENDING.
If you stored the total data size (in bytes) in the dwBufferTotal member, use
it to set the final “0” in your string buffer (if it’s a string).
buf[ib.dwBufferTotal] = 0;
9 Close the connection handle
InternetCloseHandle( m_httpFile );
The callback will receive this notification when the handle is closed:
[connect] InternetStatus: 70 INTERNET_STATUS_HANDLE_CLOSING
In most error cases, the
connection is closed unexpectedly. If it happens you’ll receive a 70 followed
by a 100 (INTERNET_STATUS_REQUEST_COMPLETE). This can happen anywhere during the
process.
10 Before closing the m_Session handle:
you must deregister the callback:
InternetSetStatusCallback( m_Session, NULL );
This should help those who tried to go through asynchronous mode in WinInet !
Sorry, there are no attached files but you should be able to use the functions
and create nice classes now.
If you liked this article please add an entry in my guestbook and buy me a licence of my
shareware J.
If you don't then I never wrote this article.
B.M.
Bibliography:
http://msdn.microsoft.com/workshop/networking/wininet/reference/functions/InternetReadFileEx.asp
Q176420: BUG: InternetSetOption Does Not Set
Timeout Values
Q176176: FIX: InternetOpenUrl Does Not Work
When Called Asynchronously
Q238425: INFO: WinInet Not Supported for Use in Services
Q190542: INFO: Using WinInet APIs in a System Service to access SSL sites
news://microsoft.public.inetsdk.programming.wininet
Hint: For improved responsiveness, use Internet Explorer 4 (or above) with Javascript enabled, choose 'Use DHTML' from the View dropdown and hit 'Refresh'. Note: The forums are currently read-only due to maintainance work. The Forums are being re-threaded. Apologies for any inconvenience. | |||||||||||||||||||
|
|