wininet不仅支持同步的处理方式,也支持异步的处理方式。具体方法如下
1. 调用InternetOpen时设置参数INTERNET_FLAG_ASYNC
hSession = InternetOpen(szAgent, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, INTERNET_FLAG_ASYNC);
2. 设置回调函数
INTERNET_STATUS_CALLBACK pOldStatusCallback = InternetSetStatusCallback(hSession, InternetStatusCallback);
3. 调用InternetConnect打开一个会话
hConnect = InternetConnect(hSession, _T("down.sandai.net"), 80, NULL, NULL, INTERNET_SERVICE_HTTP, 0, dwContext);
根据文档,若为异步操作且dwContext为0时,回调函数不会被调用,但是InternetConnect依然以异步模式执行。但我在win7 x64上的测试结果是当dwContext为0时,回调函数的确不会被调用,但是InternetConnect已同步方式执行。若dwContext不为0时,回调函数被调用,但InternetConnect依然是同步方式执行,回调函数就在InternetConnect上下文中执行。稳妥的方案还是设置非0的dwContext参数,并根据InternetConnect返回值及错误码进行下一步处理。
4. 打开请求
hResource = HttpOpenRequest(hConnect, _T("GET"),
_T("/thunder9/Thunder9.0.16.408.exe"),
_T("HTTP/1.1"),
NULL,
szAcceptType,
INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_RELOAD, (DWORD_PTR)ptc);
请判断返回值及错误码。
5. 发送请求
bResult = HttpSendRequest(hResource, NULL, 0, NULL, 0);
if (!bResult)
{
.............................
WaitForSingleObject(ptc->hRequestCompleteEvent, INFINITE);
}
6 读取Reponse Header
WCHAR ResponseHeader[2000];
DWORD dwSize = 2000;
bResult = HttpQueryInfo(hResource, HTTP_QUERY_RAW_HEADERS_CRLF,
ResponseHeader, &dwSize, NULL);
函数HttpQueryInfo为同步执行函数。
7 读取数据
char buffer[1024];
BOOL bVerbose = FALSE;
BOOL bAllDone=FALSE;
DWORD dwBytesTransfered;
do
{
INTERNET_BUFFERS InetBuff;
FillMemory(&InetBuff, sizeof(InetBuff), 0);
InetBuff.dwStructSize = sizeof(InetBuff);
InetBuff.lpvBuffer = buffer;
InetBuff.dwBufferLength = sizeof(buffer) - 1;
if (!InternetReadFileEx(hResource,
&InetBuff,
0, (DWORD_PTR)ptc))
{
if (GetLastError() == ERROR_IO_PENDING)
{
WaitForSingleObject(ptc->hRequestCompleteEvent, INFINITE);
}
else
{
MessageBox(NULL,_T("InternetReadFileEx Error"),_T("Pause"), MB_OK|MB_ICONHAND);;
}
}
WriteFile(hFile, buffer, InetBuff.dwBufferLength, &dwBytesTransfered, NULL);
if (InetBuff.dwBufferLength == 0)
bAllDone = TRUE;
} while (bAllDone == FALSE);
根据我的测试,如果InternetReadFileEx设置了一个不同于HttpOpenRequest的上下文,回调函数接收的依然是HttpOpenRequest设置的上下文。请注意InternetReadFileEx完成读操作后,结构体INTERNET_BUFFERS成员dwBufferLength被设置成读取的字节长度大小。
根据文档说明,可用于异步操作函数列表中不包括HttpSendRequest和InternetReadFile。但是我在win7 上测试的结果是这两个函数是以异步方式执行的。所以稳妥的方案还是检查函数的返回值及错误码。
在异步操作中使用InternetReadFile是需要注意,参数lpdwNumberOfBytesRead不应该为NULL,否则产生参数错误。而不为NULL的时候,异步回调函数会设置读取的字节数值,所以此内存在调用回调函数时必须有效。
我已上写的处理流程只是异步的一种实现方案而已,文档中有一个样例async,我觉得实现更异步。特将其代码粘贴如下:
/*++
Copyright (c) 2002 - 2006 Microsoft Corporation. All Rights Reserved.
THIS CODE AND INFORMATION IS PROVIDED "AS-IS" WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.
THIS CODE IS NOT SUPPORTED BY MICROSOFT.
--*/
#include <windows.h>
#ifdef DBG
#define ASYNC_ASSERT(x) \
if(!x) \
DebugBreak();
#else
#define ASYNC_ASSERT(x)
#endif
#define BUFFER_LEN 4096
#define ERR_MSG_LEN 512
#define METHOD_NONE 0
#define METHOD_GET 1
#define METHOD_POST 2
#define REQ_STATE_SEND_REQ 0
#define REQ_STATE_SEND_REQ_WITH_BODY 1
#define REQ_STATE_POST_GET_DATA 2
#define REQ_STATE_POST_SEND_DATA 3
#define REQ_STATE_POST_COMPLETE 4
#define REQ_STATE_RESPONSE_RECV_DATA 5
#define REQ_STATE_RESPONSE_WRITE_DATA 6
#define REQ_STATE_COMPLETE 7
#define DEFAULT_TIMEOUT 2 * 60 * 1000 // Two minutes
#define DEFAULT_HOSTNAME L"www.microsoft.com"
#define DEFAULT_RESOURCE L"/"
#define DEFAULT_OUTPUT_FILE_NAME L"response.htm"
#define SPIN_COUNT 4000
//
// Structure to store configuration in that was gathered from
// passed in arguments
//
typedef struct _CONFIGURATION
{
DWORD Method; // Method, GET or POST
LPWSTR HostName; // Host to connect to
LPWSTR ResourceOnServer; // Resource to get from the server
LPWSTR InputFileName; // File containing data to post
LPWSTR OutputFileName; // File to write the data received from the server
BOOL UseProxy; // Flag to indicate the use of a proxy
LPWSTR ProxyName; // Name of the proxy to use
BOOL IsSecureConnection; // Flag to indicate the use of SSL
DWORD UserTimeout; // Timeout for the async operations
} CONFIGURATION, *PCONFIGURATION;
//
// Structure used for storing the context for the asynchronous calls
//
typedef struct _REQUEST_CONTEXT {
HINTERNET RequestHandle;
HINTERNET ConnectHandle;
HANDLE CompletionEvent;
HANDLE CleanUpEvent;
LPSTR OutputBuffer;
DWORD DownloadedBytes;
DWORD WrittenBytes;
DWORD ReadBytes;
HANDLE UploadFile;
DWORD FileSize;
HANDLE DownloadFile;
DWORD Method;
DWORD State;
CRITICAL_SECTION CriticalSection;
BOOL CritSecInitialized;
//
// Synchronized by CriticalSection
//
DWORD HandleUsageCount; // Request object is in use(not safe to close handle)
BOOL Closing; // Request is closing(don't use handle)
} REQUEST_CONTEXT, *PREQUEST_CONTEXT;
// WinInet Callback function
VOID CALLBACK
CallBack(
__in HINTERNET hInternet,
__in DWORD_PTR dwContext,
__in DWORD dwInternetStatus,
__in_bcount(dwStatusInformationLength) LPVOID lpvStatusInformation,
__in DWORD dwStatusInformationLength
);
//
// IO related functions
//
VOID ProcessRequest(
__inout PREQUEST_CONTEXT ReqContext,
__in DWORD Error
);
DWORD SendRequest(
__in PREQUEST_CONTEXT ReqContext
);
DWORD SendRequestWithBody(
__in PREQUEST_CONTEXT ReqContext
);
DWORD GetDataToPost(
__inout PREQUEST_CONTEXT ReqContext
);
DWORD PostDataToServer(
__inout PREQUEST_CONTEXT ReqContext,
__out PBOOL Eof
);
DWORD CompleteRequest(
__inout PREQUEST_CONTEXT ReqContext
);
DWORD RecvResponseData(
__inout PREQUEST_CONTEXT ReqContext
);
DWORD WriteResponseData(
__in PREQUEST_CONTEXT ReqContext,
__out PBOOL Eof
);
//
// Initialization functions
//
DWORD AllocateAndInitializeRequestContext(
__in HINTERNET SessionHandle,
__in PCONFIGURATION Configuration,
__deref_out PREQUEST_CONTEXT *ReqContext
);
DWORD CreateWininetHandles(
__inout PREQUEST_CONTEXT ReqContext,
__in HINTERNET SessionHandle,
__in LPWSTR HostName,
__in LPWSTR Resource,
__in BOOL IsSecureConnection
);
//
// Cleanup functions
//
VOID CleanUpRequestContext(
__inout_opt PREQUEST_CONTEXT ReqContext
);
VOID CleanUpSessionHandle(
__in HINTERNET SessionHandle
);
//
// Cancellation support functions
//
VOID CloseRequestHandle(
__inout PREQUEST_CONTEXT ReqContext
);
BOOL AcquireRequestHandle(
__inout PREQUEST_CONTEXT ReqContext
);
VOID ReleaseRequestHandle(
__inout PREQUEST_CONTEXT ReqContext
);
//
// Utility functions
//
VOID WaitForRequestCompletion(
__in PREQUEST_CONTEXT ReqContext,
__in DWORD Timeout
);
DWORD OpenFiles(
__inout PREQUEST_CONTEXT ReqContext,
__in DWORD Method,
__in LPWSTR InputFileName,
__in LPWSTR OutputFileName
);
DWORD ParseArguments(
__in int argc,
__in_ecount(argc) LPWSTR *argv,
__inout PCONFIGURATION Configuration
);
VOID ShowUsage(
VOID
);
VOID LogInetError(
__in DWORD Err,
__in LPCWSTR Str
);
VOID LogSysError(
__in DWORD Err,
__in LPCWSTR Str
);
/*++
Copyright (c) 2002 - 2006 Microsoft Corporation. All Rights Reserved.
THIS CODE AND INFORMATION IS PROVIDED "AS-IS" WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.
THIS CODE IS NOT SUPPORTED BY MICROSOFT.
--*/
#include "precomp.h"
#include "async.h"
#pragma warning(disable:4127) // Conditional expression is constant
int __cdecl
wmain(
__in int argc,
__in_ecount(argc) LPWSTR *argv
)
{
DWORD Error;
DWORD OpenType = INTERNET_OPEN_TYPE_PRECONFIG; // Use pre-configured options as default
CONFIGURATION Configuration = {0};
PREQUEST_CONTEXT ReqContext = NULL;
HINTERNET SessionHandle = NULL;
// Callback function
INTERNET_STATUS_CALLBACK CallbackPointer;
// Parse the command line arguments
Error = ParseArguments(argc, argv, &Configuration);
if(Error != ERROR_SUCCESS)
{
ShowUsage();
goto Exit;
}
if(Configuration.UseProxy)
{
OpenType = INTERNET_OPEN_TYPE_PROXY;
}
// Create Session handle and specify async Mode
SessionHandle = InternetOpen(L"WinInet HTTP Async Sample", // User Agent
OpenType, // Preconfig or Proxy
Configuration.ProxyName, // Proxy name
NULL, // Proxy bypass, do not bypass any address
INTERNET_FLAG_ASYNC); // 0 for Synchronous
if (SessionHandle == NULL)
{
LogInetError(GetLastError(), L"InternetOpen");
goto Exit;
}
// Set the status callback for the handle to the Callback function
CallbackPointer = InternetSetStatusCallback(SessionHandle,
(INTERNET_STATUS_CALLBACK)CallBack);
if (CallbackPointer == INTERNET_INVALID_STATUS_CALLBACK)
{
fprintf(stderr, "InternetSetStatusCallback failed with INTERNET_INVALID_STATUS_CALLBACK\n");
goto Exit;
}
// Initialize the ReqContext to be used in the asynchronous calls
Error = AllocateAndInitializeRequestContext(SessionHandle,
&Configuration,
&ReqContext);
if (Error != ERROR_SUCCESS)
{
fprintf(stderr, "AllocateAndInitializeRequestContext failed with error %d\n", Error);
goto Exit;
}
//
// Send out request and receive response
//
ProcessRequest(ReqContext, ERROR_SUCCESS);
//
// Wait for request completion or timeout
//
WaitForRequestCompletion(ReqContext, Configuration.UserTimeout);
Exit:
// Clean up the allocated resources
CleanUpRequestContext(ReqContext);
CleanUpSessionHandle(SessionHandle);
return (Error != ERROR_SUCCESS) ? 1 : 0;
}
VOID CALLBACK
CallBack(
__in HINTERNET hInternet,
__in DWORD_PTR dwContext,
__in DWORD dwInternetStatus,
__in_bcount(dwStatusInformationLength) LPVOID lpvStatusInformation,
__in DWORD dwStatusInformationLength
)
/*++
Routine Description:
Callback routine for asynchronous WinInet operations
Arguments:
hInternet - The handle for which the callback function is called.
dwContext - Pointer to the application defined context.
dwInternetStatus - Status code indicating why the callback is called.
lpvStatusInformation - Pointer to a buffer holding callback specific data.
dwStatusInformationLength - Specifies size of lpvStatusInformation buffer.
Return Value:
None.
--*/
{
InternetCookieHistory cookieHistory;
PREQUEST_CONTEXT ReqContext = (PREQUEST_CONTEXT)dwContext;
UNREFERENCED_PARAMETER(dwStatusInformationLength);
fprintf(stderr, "Callback Received for Handle %p \t", hInternet);
switch(dwInternetStatus)
{
case INTERNET_STATUS_COOKIE_SENT:
fprintf(stderr, "Status: Cookie found and will be sent with request\n");
break;
case INTERNET_STATUS_COOKIE_RECEIVED:
fprintf(stderr, "Status: Cookie Received\n");
break;
case INTERNET_STATUS_COOKIE_HISTORY:
fprintf(stderr, "Status: Cookie History\n");
ASYNC_ASSERT(lpvStatusInformation);
ASYNC_ASSERT(dwStatusInformationLength == sizeof(InternetCookieHistory));
cookieHistory = *((InternetCookieHistory*)lpvStatusInformation);
if(cookieHistory.fAccepted)
{
fprintf(stderr, "Cookie Accepted\n");
}
if(cookieHistory.fLeashed)
{
fprintf(stderr, "Cookie Leashed\n");
}
if(cookieHistory.fDowngraded)
{
fprintf(stderr, "Cookie Downgraded\n");
}
if(cookieHistory.fRejected)
{
fprintf(stderr, "Cookie Rejected\n");
}
break;
case INTERNET_STATUS_CLOSING_CONNECTION:
fprintf(stderr, "Status: Closing Connection\n");
break;
case INTERNET_STATUS_CONNECTED_TO_SERVER:
fprintf(stderr, "Status: Connected to Server\n");
break;
case INTERNET_STATUS_CONNECTING_TO_SERVER:
fprintf(stderr, "Status: Connecting to Server\n");
break;
case INTERNET_STATUS_CONNECTION_CLOSED:
fprintf(stderr, "Status: Connection Closed\n");
break;
case INTERNET_STATUS_HANDLE_CLOSING:
fprintf(stderr, "Status: Handle Closing\n");
//
// Signal the cleanup routine that it is
// safe to cleanup the request context
//
ASYNC_ASSERT(ReqContext);
SetEvent(ReqContext->CleanUpEvent);
break;
case INTERNET_STATUS_HANDLE_CREATED:
ASYNC_ASSERT(lpvStatusInformation);
fprintf(stderr,
"Handle %x created\n",
((LPINTERNET_ASYNC_RESULT)lpvStatusInformation)->dwResult);
break;
case INTERNET_STATUS_INTERMEDIATE_RESPONSE:
fprintf(stderr, "Status: Intermediate response\n");
break;
case INTERNET_STATUS_RECEIVING_RESPONSE:
fprintf(stderr, "Status: Receiving Response\n");
break;
case INTERNET_STATUS_RESPONSE_RECEIVED:
ASYNC_ASSERT(lpvStatusInformation);
ASYNC_ASSERT(dwStatusInformationLength == sizeof(DWORD));
fprintf(stderr, "Status: Response Received (%d Bytes)\n", *((LPDWORD)lpvStatusInformation));
break;
case INTERNET_STATUS_REDIRECT:
fprintf(stderr, "Status: Redirect\n");
break;
case INTERNET_STATUS_REQUEST_COMPLETE:
fprintf(stderr, "Status: Request complete\n");
ASYNC_ASSERT(lpvStatusInformation);
ProcessRequest(ReqContext, ((LPINTERNET_ASYNC_RESULT)lpvStatusInformation)->dwError);
break;
case INTERNET_STATUS_REQUEST_SENT:
ASYNC_ASSERT(lpvStatusInformation);
ASYNC_ASSERT(dwStatusInformationLength == sizeof(DWORD));
fprintf(stderr,"Status: Request sent (%d Bytes)\n", *((LPDWORD)lpvStatusInformation));
break;
case INTERNET_STATUS_DETECTING_PROXY:
fprintf(stderr, "Status: Detecting Proxy\n");
break;
case INTERNET_STATUS_RESOLVING_NAME:
fprintf(stderr, "Status: Resolving Name\n");
break;
case INTERNET_STATUS_NAME_RESOLVED:
fprintf(stderr, "Status: Name Resolved\n");
break;
case INTERNET_STATUS_SENDING_REQUEST:
fprintf(stderr, "Status: Sending request\n");
break;
case INTERNET_STATUS_STATE_CHANGE:
fprintf(stderr, "Status: State Change\n");
break;
case INTERNET_STATUS_P3P_HEADER:
fprintf(stderr, "Status: Received P3P header\n");
break;
default:
fprintf(stderr, "Status: Unknown (%d)\n", dwInternetStatus);
break;
}
return;
}
VOID
ProcessRequest(
__inout PREQUEST_CONTEXT ReqContext,
__in DWORD Error
)
/*++
Routine Description:
Process the request context - Sending the request and
receiving the response
Arguments:
ReqContext - Pointer to request context structure
Error - error returned from last asynchronous call
Return Value:
None.
--*/
{
BOOL Eof = FALSE;
ASYNC_ASSERT(ReqContext);
while(Error == ERROR_SUCCESS && ReqContext->State != REQ_STATE_COMPLETE)
{
switch(ReqContext->State)
{
case REQ_STATE_SEND_REQ:
ReqContext->State = REQ_STATE_RESPONSE_RECV_DATA;
Error = SendRequest(ReqContext);
break;
case REQ_STATE_SEND_REQ_WITH_BODY:
ReqContext->State = REQ_STATE_POST_GET_DATA;
Error = SendRequestWithBody(ReqContext);
break;
case REQ_STATE_POST_GET_DATA:
ReqContext->State = REQ_STATE_POST_SEND_DATA;
Error = GetDataToPost(ReqContext);
break;
case REQ_STATE_POST_SEND_DATA:
ReqContext->State = REQ_STATE_POST_GET_DATA;
Error = PostDataToServer(ReqContext, &Eof);
if(Eof)
{
ASYNC_ASSERT(Error == ERROR_SUCCESS);
ReqContext->State = REQ_STATE_POST_COMPLETE;
}
break;
case REQ_STATE_POST_COMPLETE:
ReqContext->State = REQ_STATE_RESPONSE_RECV_DATA;
Error = CompleteRequest(ReqContext);
break;
case REQ_STATE_RESPONSE_RECV_DATA:
ReqContext->State = REQ_STATE_RESPONSE_WRITE_DATA;
Error = RecvResponseData(ReqContext);
break;
case REQ_STATE_RESPONSE_WRITE_DATA:
ReqContext->State = REQ_STATE_RESPONSE_RECV_DATA;
Error = WriteResponseData(ReqContext, &Eof);
if(Eof)
{
ASYNC_ASSERT(Error == ERROR_SUCCESS);
ReqContext->State = REQ_STATE_COMPLETE;
}
break;
default:
ASYNC_ASSERT(FALSE);
break;
}
}
if(Error != ERROR_IO_PENDING)
{
//
// Everything has been procesed or has failed.
// In either case, the signal processing has
// completed
//
SetEvent(ReqContext->CompletionEvent);
}
return;
}
DWORD
SendRequest(
__in PREQUEST_CONTEXT ReqContext
)
/*++
Routine Description:
Send the request using HttpSendRequest
Arguments:
ReqContext - Pointer to request context structure
Return Value:
Error code for the operation.
--*/
{
BOOL Success;
DWORD Error = ERROR_SUCCESS;
ASYNC_ASSERT(ReqContext->Method == METHOD_GET);
Success = AcquireRequestHandle(ReqContext);
if(!Success)
{
Error = ERROR_OPERATION_ABORTED;
goto Exit;
}
Success = HttpSendRequest(ReqContext->RequestHandle,
NULL, // do not provide additional Headers
0, // dwHeadersLength
NULL, // Do not send any data
0); // dwOptionalLength
ReleaseRequestHandle(ReqContext);
if (!Success)
{
Error = GetLastError();
if(Error != ERROR_IO_PENDING)
{
LogInetError(Error, L"HttpSendRequest");
}
goto Exit;
}
Exit:
return Error;
}
DWORD
SendRequestWithBody(
__in PREQUEST_CONTEXT ReqContext
)
/*++
Routine Description:
Send the request with entity-body using HttpSendRequestEx
Arguments:
ReqContext - Pointer to request context structure
Return Value:
Error code for the operation.
--*/
{
BOOL Success;
INTERNET_BUFFERS BuffersIn;
DWORD Error = ERROR_SUCCESS;
//
// HttpSendRequest can also be used also to post data to a server,
// to do so, the data should be provided using the lpOptional
// parameter and it's size on dwOptionalLength.
// Here we decided to depict the use of HttpSendRequestEx function.
//
ASYNC_ASSERT(ReqContext->Method == METHOD_POST);
//Prepare the Buffers to be passed to HttpSendRequestEx
ZeroMemory(&BuffersIn, sizeof(INTERNET_BUFFERS));
BuffersIn.dwStructSize = sizeof(INTERNET_BUFFERS);
BuffersIn.lpvBuffer = NULL;
BuffersIn.dwBufferLength = 0;
BuffersIn.dwBufferTotal = ReqContext->FileSize; // content-length of data to post
Success = AcquireRequestHandle(ReqContext);
if(!Success)
{
Error = ERROR_OPERATION_ABORTED;
goto Exit;
}
Success = HttpSendRequestEx(ReqContext->RequestHandle,
&BuffersIn,
NULL, // Do not use output buffers
0, // dwFlags reserved
(DWORD_PTR)ReqContext);
ReleaseRequestHandle(ReqContext);
if (!Success)
{
Error = GetLastError();
if(Error != ERROR_IO_PENDING)
{
LogInetError(Error, L"HttpSendRequestEx");
}
goto Exit;
}
Exit:
return Error;
}
DWORD
GetDataToPost(
__inout PREQUEST_CONTEXT ReqContext
)
/*++
Routine Description:
Reads data from a file
Arguments:
ReqContext - Pointer to request context structure
Return Value:
Error code for the operation.
--*/
{
DWORD Error = ERROR_SUCCESS;
BOOL Success;
//
//
// ReadFile is done inline here assuming that it will return quickly
// I.E. the file is on disk
//
// If you plan to do blocking/intensive operations they should be
// queued to another thread and not block the callback thread
//
//
Success = ReadFile(ReqContext->UploadFile,
ReqContext->OutputBuffer,
BUFFER_LEN,
&ReqContext->ReadBytes,
NULL);
if(!Success)
{
Error = GetLastError();
LogSysError(Error, L"ReadFile");
goto Exit;
}
Exit:
return Error;
}
DWORD
PostDataToServer(
__inout PREQUEST_CONTEXT ReqContext,
__out PBOOL Eof
)
/*++
Routine Description:
Post data in the http request
Arguments:
ReqContext - Pointer to request context structure
Eof - Done posting data to server
Return Value:
Error code for the operation.
--*/
{
DWORD Error = ERROR_SUCCESS;
BOOL Success;
*Eof = FALSE;
if(ReqContext->ReadBytes == 0)
{
*Eof = TRUE;
goto Exit;
}
Success = AcquireRequestHandle(ReqContext);
if(!Success)
{
Error = ERROR_OPERATION_ABORTED;
goto Exit;
}
//
// The lpdwNumberOfBytesWritten parameter will be
// populated on async completion, so it must exist
// until INTERNET_STATUS_REQUEST_COMPLETE.
// The same is true of lpBuffer parameter.
//
Success = InternetWriteFile(ReqContext->RequestHandle,
ReqContext->OutputBuffer,
ReqContext->ReadBytes,
&ReqContext->WrittenBytes);
ReleaseRequestHandle(ReqContext);
if(!Success)
{
Error = GetLastError();
if(Error == ERROR_IO_PENDING)
{
fprintf(stderr, "Waiting for InternetWriteFile to complete\n");
}
else
{
LogInetError(Error, L"InternetWriteFile");
}
goto Exit;
}
Exit:
return Error;
}
DWORD
CompleteRequest(
__inout PREQUEST_CONTEXT ReqContext
)
/*++
Routine Description:
Perform completion of asynchronous post.
Arguments:
ReqContext - Pointer to request context structure
Return Value:
Error Code for the operation.
--*/
{
DWORD Error = ERROR_SUCCESS;
BOOL Success;
fprintf(stderr, "Finished posting file\n");
Success = AcquireRequestHandle(ReqContext);
if(!Success)
{
Error = ERROR_OPERATION_ABORTED;
goto Exit;
}
Success = HttpEndRequest(ReqContext->RequestHandle, NULL, 0, 0);
ReleaseRequestHandle(ReqContext);
if(!Success)
{
Error = GetLastError();
if(Error == ERROR_IO_PENDING)
{
fprintf(stderr, "Waiting for HttpEndRequest to complete \n");
}
else
{
LogInetError(Error, L"HttpEndRequest");
goto Exit;
}
}
Exit:
return Error;
}
DWORD
RecvResponseData(
__inout PREQUEST_CONTEXT ReqContext
)
/*++
Routine Description:
Receive response
Arguments:
ReqContext - Pointer to request context structure
Return Value:
Error Code for the operation.
--*/
{
DWORD Error = ERROR_SUCCESS;
BOOL Success;
Success = AcquireRequestHandle(ReqContext);
if(!Success)
{
Error = ERROR_OPERATION_ABORTED;
goto Exit;
}
//
// The lpdwNumberOfBytesRead parameter will be
// populated on async completion, so it must exist
// until INTERNET_STATUS_REQUEST_COMPLETE.
// The same is true of lpBuffer parameter.
//
// InternetReadFile will block until the buffer
// is completely filled or the response is exhausted.
//
Success = InternetReadFile(ReqContext->RequestHandle,
ReqContext->OutputBuffer,
BUFFER_LEN,
&ReqContext->DownloadedBytes);
ReleaseRequestHandle(ReqContext);
if(!Success)
{
Error = GetLastError();
if(Error == ERROR_IO_PENDING)
{
fprintf(stderr, "Waiting for InternetReadFile to complete\n");
}
else
{
LogInetError(Error, L"InternetReadFile");
}
goto Exit;
}
Exit:
return Error;
}
DWORD
WriteResponseData(
__in PREQUEST_CONTEXT ReqContext,
__out PBOOL Eof
)
/*++
Routine Description:
Write response to a file
Arguments:
ReqContext - Pointer to request context structure
Eof - Done with response
Return Value:
Error Code for the operation.
--*/
{
DWORD Error = ERROR_SUCCESS;
DWORD BytesWritten = 0;
BOOL Success;
*Eof = FALSE;
//
// Finished receiving response
//
if (ReqContext->DownloadedBytes == 0)
{
*Eof = TRUE;
goto Exit;
}
//
//
// WriteFile is done inline here assuming that it will return quickly
// I.E. the file is on disk
//
// If you plan to do blocking/intensive operations they should be
// queued to another thread and not block the callback thread
//
//
Success = WriteFile(ReqContext->DownloadFile,
ReqContext->OutputBuffer,
ReqContext->DownloadedBytes,
&BytesWritten,
NULL);
if (!Success)
{
Error = GetLastError();
LogSysError(Error, L"WriteFile");
goto Exit;;
}
Exit:
return Error;
}
VOID
CloseRequestHandle(
__inout PREQUEST_CONTEXT ReqContext
)
/*++
Routine Description:
Safely close the request handle by synchronizing
with all threads using the handle.
When this function returns no more calls can be made with the
handle.
Arguments:
ReqContext - Pointer to Request context structure
Return Value:
None.
--*/
{
BOOL Close = FALSE;
EnterCriticalSection(&ReqContext->CriticalSection);
//
// Current implementation only supports the main thread
// kicking off the request handle close
//
// To support multiple threads the lifetime
// of the request context must be carefully controlled
// (most likely guarded by refcount/critsec)
// so that they are not trying to abort a request
// where the context has already been freed.
//
ASYNC_ASSERT(ReqContext->Closing == FALSE);
ReqContext->Closing = TRUE;
if(ReqContext->HandleUsageCount == 0)
{
Close = TRUE;
}
LeaveCriticalSection(&ReqContext->CriticalSection);
if(Close)
{
//
// At this point there must be the guarantee that all calls
// to wininet with this handle have returned with some value
// including ERROR_IO_PENDING, and none will be made after
// InternetCloseHandle.
//
(VOID)InternetCloseHandle(ReqContext->RequestHandle);
}
return;
}
BOOL
AcquireRequestHandle(
__inout PREQUEST_CONTEXT ReqContext
)
/*++
Routine Description:
Acquire use of the request handle to make a wininet call
Arguments:
ReqContext - Pointer to Request context structure
Return Value:
TRUE - Success
FALSE - Failure
--*/
{
BOOL Success = TRUE;
EnterCriticalSection(&ReqContext->CriticalSection);
if(ReqContext->Closing == TRUE)
{
Success = FALSE;
}
else
{
ReqContext->HandleUsageCount++;
}
LeaveCriticalSection(&ReqContext->CriticalSection);
return Success;
}
VOID
ReleaseRequestHandle(
__inout PREQUEST_CONTEXT ReqContext
)
/*++
Routine Description:
release use of the request handle
Arguments:
ReqContext - Pointer to Request context structure
Return Value:
None.
--*/
{
BOOL Close = FALSE;
EnterCriticalSection(&ReqContext->CriticalSection);
ASYNC_ASSERT(ReqContext->HandleUsageCount > 0);
ReqContext->HandleUsageCount--;
if(ReqContext->Closing == TRUE && ReqContext->HandleUsageCount == 0)
{
Close = TRUE;
}
LeaveCriticalSection(&ReqContext->CriticalSection);
if(Close)
{
//
// At this point there must be the guarantee that all calls
// to wininet with this handle have returned with some value
// including ERROR_IO_PENDING, and none will be made after
// InternetCloseHandle.
//
(VOID)InternetCloseHandle(ReqContext->RequestHandle);
}
return;
}
DWORD
AllocateAndInitializeRequestContext(
__in HINTERNET SessionHandle,
__in PCONFIGURATION Configuration,
__deref_out PREQUEST_CONTEXT *ReqContext
)
/*++
Routine Description:
Allocate the request context and initialize it values
Arguments:
ReqContext - Pointer to Request context structure
Configuration - Pointer to configuration structure
SessionHandle - Wininet session handle to use when creating
connect handle
Return Value:
Error Code for the operation.
--*/
{
DWORD Error = ERROR_SUCCESS;
BOOL Success;
PREQUEST_CONTEXT LocalReqContext;
*ReqContext = NULL;
LocalReqContext = malloc(sizeof(REQUEST_CONTEXT));
if(LocalReqContext == NULL)
{
Error = ERROR_NOT_ENOUGH_MEMORY;
goto Exit;
}
LocalReqContext->RequestHandle = NULL;
LocalReqContext->ConnectHandle = NULL;
LocalReqContext->DownloadedBytes = 0;
LocalReqContext->WrittenBytes = 0;
LocalReqContext->ReadBytes = 0;
LocalReqContext->UploadFile = INVALID_HANDLE_VALUE;
LocalReqContext->DownloadFile = INVALID_HANDLE_VALUE;
LocalReqContext->FileSize = 0;
LocalReqContext->HandleUsageCount = 0;
LocalReqContext->Closing = FALSE;
LocalReqContext->Method = Configuration->Method;
LocalReqContext->CompletionEvent = NULL;
LocalReqContext->CleanUpEvent = NULL;
LocalReqContext->OutputBuffer = NULL;
LocalReqContext->State =
(LocalReqContext->Method == METHOD_GET) ? REQ_STATE_SEND_REQ : REQ_STATE_SEND_REQ_WITH_BODY;
LocalReqContext->CritSecInitialized = FALSE;
// initialize critical section
Success = InitializeCriticalSectionAndSpinCount(&LocalReqContext->CriticalSection, SPIN_COUNT);
if(!Success)
{
Error = GetLastError();
LogSysError(Error, L"InitializeCriticalSectionAndSpinCount");
goto Exit;
}
LocalReqContext->CritSecInitialized = TRUE;
LocalReqContext->OutputBuffer = malloc(BUFFER_LEN);
if(LocalReqContext->OutputBuffer == NULL)
{
Error = ERROR_NOT_ENOUGH_MEMORY;
goto Exit;
}
// create events
LocalReqContext->CompletionEvent = CreateEvent(NULL, // Sec attrib
FALSE, // Auto reset
FALSE, // Initial state unsignalled
NULL); // Name
if(LocalReqContext->CompletionEvent == NULL)
{
Error = GetLastError();
LogSysError(Error, L"CreateEvent CompletionEvent");
goto Exit;
}
// create events
LocalReqContext->CleanUpEvent = CreateEvent(NULL, // Sec attrib
FALSE, // Auto reset
FALSE, // Initial state unsignalled
NULL); // Name
if(LocalReqContext->CleanUpEvent == NULL)
{
Error = GetLastError();
LogSysError(Error, L"CreateEvent CleanUpEvent");
goto Exit;
}
// Open the file to dump the response entity body and
// if required the file with the data to post
Error = OpenFiles(LocalReqContext,
Configuration->Method,
Configuration->InputFileName,
Configuration->OutputFileName);
if(Error != ERROR_SUCCESS)
{
fprintf(stderr, "OpenFiles failed with %d\n", Error);
goto Exit;
}
// Verify if we've opened a file to post and get its size
if (LocalReqContext->UploadFile != INVALID_HANDLE_VALUE)
{
LocalReqContext->FileSize = GetFileSize(LocalReqContext->UploadFile, NULL);
if(LocalReqContext->FileSize == INVALID_FILE_SIZE)
{
Error = GetLastError();
LogSysError(Error, L"GetFileSize");
goto Exit;
}
}
Error = CreateWininetHandles(LocalReqContext,
SessionHandle,
Configuration->HostName,
Configuration->ResourceOnServer,
Configuration->IsSecureConnection);
if(Error != ERROR_SUCCESS)
{
fprintf(stderr, "CreateWininetHandles failed with %d\n", Error);
goto Exit;
}
*ReqContext = LocalReqContext;
Exit:
if(Error != ERROR_SUCCESS)
{
CleanUpRequestContext(LocalReqContext);
}
return Error;
}
DWORD
CreateWininetHandles(
__inout PREQUEST_CONTEXT ReqContext,
__in HINTERNET SessionHandle,
__in LPWSTR HostName,
__in LPWSTR Resource,
__in BOOL IsSecureConnection
)
/*++
Routine Description:
Create connect and request handles
Arguments:
ReqContext - Pointer to Request context structure
SessionHandle - Wininet session handle used to create
connect handle
HostName - Hostname to connect
Resource - Resource to get/post
IsSecureConnection - SSL?
Return Value:
Error Code for the operation.
--*/
{
DWORD Error = ERROR_SUCCESS;
INTERNET_PORT ServerPort = INTERNET_DEFAULT_HTTP_PORT;
DWORD RequestFlags = 0;
LPWSTR Verb;
//
// Set the correct server port if using SSL
// Also set the flag for HttpOpenRequest
//
if(IsSecureConnection)
{
ServerPort = INTERNET_DEFAULT_HTTPS_PORT;
RequestFlags = INTERNET_FLAG_SECURE;
}
// Create Connection handle and provide context for async operations
ReqContext->ConnectHandle = InternetConnect(SessionHandle,
HostName, // Name of the server to connect to
ServerPort, // HTTP (80) or HTTPS (443)
NULL, // Do not provide a user name for the server
NULL, // Do not provide a password for the server
INTERNET_SERVICE_HTTP,
0, // Do not provide any special flag
(DWORD_PTR)ReqContext); // Provide the context to be
// used during the callbacks
//
// For HTTP InternetConnect returns synchronously because it does not
// actually make the connection.
//
// For FTP InternetConnect connects the control channel, and therefore
// can be completed asynchronously. This sample would have to be
// changed, so that the InternetConnect's asynchronous completion
// is handled correctly to support FTP.
//
if (ReqContext->ConnectHandle == NULL)
{
Error = GetLastError();
LogInetError(Error, L"InternetConnect");
goto Exit;
}
// Set the Verb depending on the operation to perform
if(ReqContext->Method == METHOD_GET)
{
Verb = L"GET";
}
else
{
ASYNC_ASSERT(ReqContext->Method == METHOD_POST);
Verb = L"POST";
}
//
// We're overriding WinInet's default behavior.
// Setting these flags, we make sure we get the response from the server and not the cache.
// Also ask WinInet not to store the response in the cache.
//
// These flags are NOT performant and are only used to show case WinInet's Async I/O.
// A real WinInet application would not want to use this flags.
//
RequestFlags |= INTERNET_FLAG_RELOAD | INTERNET_FLAG_NO_CACHE_WRITE;
// Create a Request handle
ReqContext->RequestHandle = HttpOpenRequest(ReqContext->ConnectHandle,
Verb, // GET or POST
Resource, // root "/" by default
NULL, // Use default HTTP/1.1 as the version
NULL, // Do not provide any referrer
NULL, // Do not provide Accept types
RequestFlags,
(DWORD_PTR)ReqContext);
if (ReqContext->RequestHandle == NULL)
{
Error = GetLastError();
LogInetError(Error, L"HttpOpenRequest");
goto Exit;
}
Exit:
return Error;
}
VOID
WaitForRequestCompletion(
__in PREQUEST_CONTEXT ReqContext,
__in DWORD Timeout
)
/*++
Routine Description:
Wait for the request to complete or timeout to occur
Arguments:
ReqContext - Pointer to request context structure
Return Value:
None.
--*/
{
DWORD SyncResult;
//
// The preferred method of doing timeouts is to
// use the timeout options through InternetSetOption,
// but this overall timeout is being used to show
// the correct way to abort and close a request.
//
SyncResult = WaitForSingleObject(ReqContext->CompletionEvent,
Timeout); // Wait until we receive the completion
switch(SyncResult)
{
case WAIT_OBJECT_0:
printf("Done!\n");
break;
case WAIT_TIMEOUT:
fprintf(stderr,
"Timeout while waiting for completion event (request will be cancelled)\n");
break;
case WAIT_FAILED:
fprintf(stderr,
"Wait failed with Error %d while waiting for completion event (request will be cancelled)\n",
GetLastError());
break;
default:
// Not expecting any other error codes
ASYNC_ASSERT(FALSE);
break;
}
return;
}
VOID
CleanUpRequestContext(
__inout_opt PREQUEST_CONTEXT ReqContext
)
/*++
Routine Description:
Used to cleanup the request context before exiting.
Arguments:
ReqContext - Pointer to request context structure
Return Value:
None.
--*/
{
if(ReqContext == NULL)
{
goto Exit;
}
if(ReqContext->RequestHandle)
{
CloseRequestHandle(ReqContext);
//
// Wait for the closing of the handle to complete
// (waiting for all async operations to complete)
//
// This is the only safe way to get rid of the context
//
(VOID)WaitForSingleObject(ReqContext->CleanUpEvent, INFINITE);
}
if(ReqContext->ConnectHandle)
{
//
// Remove the callback from the ConnectHandle since
// we don't want the closing notification
// The callback was inherited from the session handle
//
(VOID)InternetSetStatusCallback(ReqContext->ConnectHandle,
NULL);
(VOID)InternetCloseHandle(ReqContext->ConnectHandle);
}
if(ReqContext->UploadFile != INVALID_HANDLE_VALUE)
{
CloseHandle(ReqContext->UploadFile);
}
if(ReqContext->DownloadFile != INVALID_HANDLE_VALUE)
{
CloseHandle(ReqContext->DownloadFile);
}
if(ReqContext->CompletionEvent)
{
CloseHandle(ReqContext->CompletionEvent);
}
if(ReqContext->CleanUpEvent)
{
CloseHandle(ReqContext->CleanUpEvent);
}
if(ReqContext->CritSecInitialized)
{
DeleteCriticalSection(&ReqContext->CriticalSection);
}
if(ReqContext->OutputBuffer)
{
free(ReqContext->OutputBuffer);
}
free(ReqContext);
Exit:
return;
}
VOID
CleanUpSessionHandle(
__in HINTERNET SessionHandle
)
/*++
Routine Description:
Used to cleanup session before exiting.
Arguments:
SessionHandle - Wininet session handle
Return Value:
None.
--*/
{
if(SessionHandle)
{
//
// Remove the callback from the SessionHandle since
// we don't want the closing notification
//
(VOID)InternetSetStatusCallback(SessionHandle,
NULL);
//
// Call InternetCloseHandle and do not wait for the closing notification
// in the callback function
//
(VOID)InternetCloseHandle(SessionHandle);
}
return;
}
DWORD
OpenFiles(
__inout PREQUEST_CONTEXT ReqContext,
__in DWORD Method,
__in LPWSTR InputFileName,
__in LPWSTR OutputFileName
)
/*++
Routine Description:
This routine opens files, one to post data from, and
one to write response into
Arguments:
ReqContext - Pointer to request context structure
Method - GET or POST - do we need to open the input file
InputFileName - Input file name
OutputFileName - output file name
Return Value:
Error Code for the operation.
--*/
{
DWORD Error = ERROR_SUCCESS;
if (Method == METHOD_POST)
{
// Open input file
ReqContext->UploadFile = CreateFile(InputFileName,
GENERIC_READ,
FILE_SHARE_READ,
NULL, // handle cannot be inherited
OPEN_ALWAYS, // if file exists, open it
FILE_ATTRIBUTE_NORMAL,
NULL); // No template file
if (ReqContext->UploadFile == INVALID_HANDLE_VALUE)
{
Error = GetLastError();
LogSysError(Error, L"CreateFile for input file");
goto Exit;
}
}
// Open output file
ReqContext->DownloadFile = CreateFile(OutputFileName,
GENERIC_WRITE,
0, // Open exclusively
NULL, // handle cannot be inherited
CREATE_ALWAYS, // if file exists, delete it
FILE_ATTRIBUTE_NORMAL,
NULL); // No template file
if (ReqContext->DownloadFile == INVALID_HANDLE_VALUE)
{
Error = GetLastError();
LogSysError(Error, L"CreateFile for output file");
goto Exit;
}
Exit:
return Error;
}
DWORD
ParseArguments(
__in int argc,
__in_ecount(argc) LPWSTR *argv,
__inout PCONFIGURATION Configuration
)
/*++
Routine Description:
This routine is used to Parse command line arguments. Flags are
case sensitive.
Arguments:
argc - Number of arguments
argv - Pointer to the argument vector
Configuration - pointer to configuration struct to write configuration
Return Value:
Error Code for the operation.
--*/
{
int i;
DWORD Error = ERROR_SUCCESS;
for (i = 1; i < argc; ++i)
{
if (wcsncmp(argv[i], L"-", 1))
{
printf("Invalid switch %ws\n", argv[i]);
i++;
continue;
}
switch(argv[i][1])
{
case L'p':
Configuration->UseProxy = 1;
if (i < argc - 1)
{
Configuration->ProxyName = argv[++i];
}
break;
case L'h':
if (i < argc - 1)
{
Configuration->HostName = argv[++i];
}
break;
case L'o':
if (i < argc - 1)
{
Configuration->ResourceOnServer = argv[++i];
}
break;
case L'r':
if (i < argc - 1)
{
Configuration->InputFileName = argv[++i];
}
break;
case L'w':
if (i < argc - 1)
{
Configuration->OutputFileName = argv[++i];
}
break;
case L'm':
if (i < argc - 1)
{
if (!_wcsnicmp(argv[i + 1], L"get", 3))
{
Configuration->Method = METHOD_GET;
}
else if (!_wcsnicmp(argv[i + 1], L"post", 4))
{
Configuration->Method = METHOD_POST;
}
}
++i;
break;
case L's':
Configuration->IsSecureConnection = TRUE;
break;
case L't':
if (i < argc - 1)
{
Configuration->UserTimeout = _wtoi(argv[++i]);
}
break;
default:
Error = ERROR_INVALID_PARAMETER;
break;
}
}
if(Error == ERROR_SUCCESS)
{
if(Configuration->UseProxy && Configuration->ProxyName == NULL)
{
printf("No proxy server name provided!\n\n");
Error = ERROR_INVALID_PARAMETER;
goto Exit;
}
if(Configuration->HostName == NULL)
{
printf("Defaulting hostname to: %ws\n", DEFAULT_HOSTNAME);
Configuration->HostName = DEFAULT_HOSTNAME;
}
if(Configuration->Method == METHOD_NONE)
{
printf("Defaulting method to: GET\n");
Configuration->Method = METHOD_GET;
}
if(Configuration->ResourceOnServer == NULL)
{
printf("Defaulting resource to: %ws\n", DEFAULT_RESOURCE);
Configuration->ResourceOnServer = DEFAULT_RESOURCE;
}
if(Configuration->UserTimeout == 0)
{
printf("Defaulting timeout to: %d\n", DEFAULT_TIMEOUT);
Configuration->UserTimeout = DEFAULT_TIMEOUT;
}
if(Configuration->InputFileName == NULL && Configuration->Method == METHOD_POST)
{
printf("Error: File to post not specified\n");
Error = ERROR_INVALID_PARAMETER;
goto Exit;
}
if(Configuration->OutputFileName == NULL)
{
printf("Defaulting output file to: %ws\n", DEFAULT_OUTPUT_FILE_NAME);
Configuration->OutputFileName = DEFAULT_OUTPUT_FILE_NAME;
}
}
Exit:
return Error;
}
VOID
ShowUsage(
VOID
)
/*++
Routine Description:
Shows the usage of the application.
Arguments:
None.
Return Value:
None.
--*/
{
printf("Usage: async [-m {GET|POST}] [-h <hostname>] [-o <resourcename>] [-s] ");
printf("[-p <proxyname>] [-w <output filename>] [-r <file to post>] [-t <userTimeout>]\n");
printf("Option Semantics: \n");
printf("-m : Specify method (Default: \"GET\")\n");
printf("-h : Specify hostname (Default: \"%ws\"\n", DEFAULT_HOSTNAME);
printf("-o : Specify resource name on the server (Default: \"%ws\")\n", DEFAULT_RESOURCE);
printf("-s : Use secure connection - https\n");
printf("-p : Specify proxy\n");
printf("-w : Specify file to write output to (Default: \"%ws\")\n", DEFAULT_OUTPUT_FILE_NAME);
printf("-r : Specify file to post data from\n");
printf("-t : Specify time to wait in ms for operation completion (Default: %d)\n", DEFAULT_TIMEOUT);
return;
}
VOID
LogInetError(
__in DWORD Err,
__in LPCWSTR Str
)
/*++
Routine Description:
This routine is used to log WinInet errors in human readable form.
Arguments:
Err - Error number obtained from GetLastError()
Str - String pointer holding caller-context information
Return Value:
None.
--*/
{
DWORD Result;
PWSTR MsgBuffer = NULL;
Result = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_HMODULE,
GetModuleHandle(L"wininet.dll"),
Err,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPWSTR)&MsgBuffer,
ERR_MSG_LEN,
NULL);
if (Result)
{
fprintf(stderr, "%ws: %ws\n", Str, MsgBuffer);
LocalFree(MsgBuffer);
}
else
{
fprintf(stderr,
"Error %d while formatting message for %d in %ws\n",
GetLastError(),
Err,
Str);
}
return;
}
VOID
LogSysError(
__in DWORD Err,
__in LPCWSTR Str
)
/*++
Routine Description:
This routine is used to log System Errors in human readable form.
Arguments:
Err - Error number obtained from GetLastError()
Str - String pointer holding caller-context information
Return Value:
None.
--*/
{
DWORD Result;
PWSTR MsgBuffer = NULL;
Result = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
Err,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPWSTR)&MsgBuffer,
ERR_MSG_LEN,
NULL);
if (Result)
{
fprintf(stderr,
"%ws: %ws\n",
Str,
MsgBuffer);
LocalFree(MsgBuffer);
}
else
{
fprintf(stderr,
"Error %d while formatting message for %d in %ws\n",
GetLastError(),
Err,
Str);
}
return;
}