Introduction
If you have ever dug 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 finally managed 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 (these 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 multi-proc NT server machines).
Theory
To use WinInet functions in full asynchronous mode, you must do things in the right order:
- Use
INTERNET_FLAG_ASYNC
to open the session
- Set a status callback using
InternetSetStatusCallback
- Open the connection using
InternetOpenUrl
- 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.
- 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
- Use
InternetReadFileEx
with the IRF_ASYNC
flag to read the remaining data asynchronously. Don’t use InternetReadFile
since it is a synchronous function.
- 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).
- If the
dwBufferLength
member is not 0, move the lpvBuffer
pointer from this amount and subtract this amount from the buffer length so dwBufferLength
reflects the remaining size lpvBuffer
points to, then loop to 6.
- 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 occurs – 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 un-register 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 are 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.
BOOL bOk = InternetReadFileEx( m_hHttpFile, &ib, IRF_ASYNC, (LPARAM)this );
if(!bOk && GetLastError()==ERROR_IO_PENDING)
wait...
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 last 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 license of my shareware.
Bibliography
About the Author
Other popular Internet / Network articles:
|