/// /// Copyright © 2003-2008 JetBrains s.r.o. /// You may distribute under the terms of the GNU General Public License, as published by the Free Software Foundation, version 2 (see License.txt in the repository root folder). /// // JetRpcClient.cpp : Implementation of CJetRpcClient // // © JetBrains Inc, 2005 // Written by (H) Serge Baltic #include "stdafx.h" #include "JetRpcClient.h" // CJetRpcClient #ifdef _TRACE int CJetRpcClient::m_nInstances = 0; volatile LONG CJetRpcClient::m_nBstrOnTheRun = 0; #endif CJetRpcClient::CJetRpcClient() { #ifdef _TRACE TRACE(L"CJetRpcClient::CJetRpcClient (+%d)", ++m_nInstances); #endif m_state = stateIdle; m_marshalling.dwTag = 0; m_bTerminationCallbackInvoked = false; m_bAsync = true; } CJetRpcClient::~CJetRpcClient() { ASSERT(m_state == stateIdle); // Must not be killed in action ASSERT(!IsWindow()); if(IsWindow()) DestroyWindow(); ASSERT(m_marshalling.dwTag == 0); // TODO: close the handles, just in case #ifdef _TRACE TRACE(L"CJetRpcClient::~CJetRpcClient (-%d) bstrs(%d)", --m_nInstances, m_nBstrOnTheRun); #endif } void CJetRpcClient::InvokeServer(bool bAsync) { // Check if we're permitted to run a request at this time if(m_state != stateIdle) ThrowError(CJetIe::LoadString(IDS_E_REQUEST_ALREADY_RUNNING)); m_state = stateIdle; // Will be changed by the FSM to the value appropriate m_bAsync = bAsync; // Call the FSM which will either complete the request synchronously or initiate the async operation RunFSM(); ASSERT((m_bAsync) || (m_state == stateIdle)); // Check that, if sync, the operation has completed // Report the error, if there was one; do in sync case only because the only place we can set an error to this string is OnMarshalError, and in async case it has already reported the error to the caller; don't do it twice if((!m_bAsync) && (!m_sLastError.IsEmpty())) ThrowError(m_sLastError); } bool CJetRpcClient::BeginRequest() { // Initialize WinInet if(m_hInet == NULL) // This object has not been used yet { m_hInet.Attach(InternetOpenA("JetIe", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, (m_bAsync ? INTERNET_FLAG_ASYNC : 0))); // Optional asynchronous mode if(m_hInet == NULL) ThrowError(CJetIe::LoadString(IDS_E_WININET_SESSION) + L'\n' + CJetIe::GetSystemError()); } // Establish the connection if(m_hConnection == NULL) // The second request could be run on the same connection { m_hConnection.Attach(InternetConnectA(m_hInet, OnGetHostName(), OnGetPort(), "", "", INTERNET_SERVICE_HTTP, (m_bAsync ? INTERNET_FLAG_ASYNC : 0), (DWORD_PTR)&m_marshalling)); if(m_hConnection == NULL) // TODO: check whether this function may return the ERROR_IO_PENDING code ThrowError(CJetIe::LoadString(IDS_E_WININET_CONNECT) + L'\n' + CJetIe::GetSystemError()); if(!IsWindow()) { // Try to create the marshalling window HWND hwndParent = CJetIe::IsWinNT() ? HWND_MESSAGE : NULL; // Parent of this window, use HWND_MESSAGE under WinNT to create a message-only window if(Create(hwndParent, NULL, _T("JetRpcClient Marshalling Window"), WS_POPUP) == NULL) ThrowError(CJetIe::GetSystemError()); } // Register for the callback if(InternetSetStatusCallbackA(m_hConnection, InternetStatusCallback) == INTERNET_INVALID_STATUS_CALLBACK) ThrowError(CJetIe::LoadString(IDS_E_WININET_SESSION) + L'\n' + CJetIe::GetSystemError()); } // Open the request LPCSTR szMediaFormats[] = { "text/xml", NULL }; m_hRequest.Attach(HttpOpenRequestA(m_hConnection, "POST", OnGetObjectName(), NULL, NULL, szMediaFormats, HTTP_OPEN_REQUEST_FLAGS | (m_bAsync ? INTERNET_FLAG_ASYNC : 0), (DWORD_PTR)&m_marshalling)); if(m_hRequest == NULL) { InternetSetStatusCallbackA(m_hConnection, NULL); // Cancel the callback on failure ThrowError(CJetIe::LoadString(IDS_E_WININET_OPENREQ) + L'\n' + CJetIe::GetSystemError()); } // Add headers specifying the content-type CStringA sHeaders = "Content-Type: " + OnGetContentType(); if(!HttpAddRequestHeadersA(m_hRequest, sHeaders, -1, HTTP_ADDREQ_FLAG_ADD | HTTP_ADDREQ_FLAG_REPLACE)) { DWORD dwErr = GetLastError(); // Is non-null as there was not a success if(dwErr != ERROR_IO_PENDING) // Pending-error means that the request was stared OK, otherwise, indicates a failure ThrowError(CJetIe::LoadString(IDS_E_WININET_SENDREQ) + L'\n' + CJetIe::GetSystemError(dwErr)); return false; // Wait async } // TODO: is this function capable of the asynchronous operation? // Request initated, now prepare the data, send it, and execute the request return true; } void CJetRpcClient::ValidateXmlResponse() { // Check the response for simple parsing errors if(m_xmlResponse->parseError->errorCode != 0) { CStringW sError; sError.Format(L"%s\n%s (line %d, char %d)", CJetIe::LoadString(IDS_E_XML_INVALID), (LPCWSTR)m_xmlResponse->parseError->reason, m_xmlResponse->parseError->line, m_xmlResponse->parseError->linepos); ThrowError(sError); } // Invoke the per-protocol handler OnValidateResponse(m_xmlResponse); } bool CJetRpcClient::SendData() { // Measure the stream size STATSTG statstg = {0}; COM_CHECK(m_streamTransmit, Stat(&statstg, STATFLAG_NONAME)); // Read the data int nBufSize = (int)statstg.cbSize.QuadPart; char *pBuf = m_sBufA.GetBuffer(nBufSize); // TODO: here we assume that all the content fits within HTTP_BUFFER_SIZE bytes. Overcome this assumption later … DWORD dwRead; COM_CHECK(m_streamTransmit, Read(pBuf, nBufSize, &dwRead)); m_streamTransmit = NULL; // Release the stream buffers // Prepare the buffers INTERNET_BUFFERSA ibIn; ZeroMemory(&ibIn, sizeof(ibIn)); ibIn.dwStructSize = sizeof(ibIn); ibIn.Next = NULL; ibIn.lpcszHeader = NULL; ibIn.dwHeadersLength = 0; ibIn.dwHeadersTotal = 0; ibIn.lpvBuffer = pBuf; ibIn.dwBufferLength = dwRead; ibIn.dwBufferTotal = dwRead; // Start transmitting the request if(!HttpSendRequestExA(m_hRequest, &ibIn, NULL, (m_bAsync ? INTERNET_FLAG_ASYNC : 0), (DWORD_PTR)&m_marshalling)) // If the method has not succeeded { DWORD dwErr = GetLastError(); // Is non-null as there was not a success if(dwErr != ERROR_IO_PENDING) // Pending-error means that the request was stared OK, otherwise, indicates a failure ThrowError(CJetIe::LoadString(IDS_E_WININET_SENDREQ) + L'\n' + CJetIe::GetSystemError(dwErr)); return false; // Wait for async completion } return true; // Go on sync } bool CJetRpcClient::ReadData() { bool bFirst = (m_streamReceive == NULL); // Is this the first call? Prepare structures, if yes if(bFirst) { // Init the stream ASSERT(m_xmlResponse == NULL); // Must be cleaned up after the prev run CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); // TODO: workaround m_xmlResponse = CJetIe::CreateXmlDocument(); m_streamReceive = m_xmlResponse; // Take a stream that will load the XML file async while downloading // Prepare the buffers ZeroMemory(&m_ibOut, sizeof(m_ibOut)); m_ibOut.dwStructSize = sizeof(m_ibOut); } for(int a = 0; a < 0x1000; a++) // An infinite loop with some kind of a watchdog. This loop executes as long as there is data ready for immediate reading, otherwise, it escapes and lets WinINet prepare some more of data { if(!bFirst) // If we already have some of the information read, either thru the sync or async operation { if(m_ibOut.dwBufferLength == 0) // No data was received on the last step return false; // Completed, the stream contains the full data received from the server // Process the newly-gotten chunk DWORD dwWritten; CHECK(m_streamReceive->Write(m_ibOut.lpvBuffer, m_ibOut.dwBufferLength, &dwWritten)); if(dwWritten != m_ibOut.dwBufferLength) ThrowError(CJetIe::LoadString(IDS_E_WININET_READRESP)); } bFirst = false; // Prepare the buffers for the next iteration m_ibOut.lpvBuffer = m_sBufA.GetBuffer(HTTP_BUFFER_SIZE); m_ibOut.dwBufferLength = HTTP_BUFFER_SIZE; // Initiate the asynchronous read operation // Note: here we use InternetReadFileExA instead of T because the W version always returns the "Not implemented" error, and this is the only code it contains. Such a shit :) if(!InternetReadFileExA(m_hRequest, &m_ibOut, (m_bAsync ? INTERNET_FLAG_ASYNC : 0), (DWORD_PTR)&m_marshalling)) // If the method has failed to complete immediately { DWORD dwErr = GetLastError(); // Is non-null as there was not a success if(dwErr != ERROR_IO_PENDING) // Pending-error means that the request was stared OK, otherwise, indicates a failure ThrowError(CJetIe::LoadString(IDS_E_WININET_READRESP) + L'\n' + CJetIe::GetSystemError(dwErr)); return true; // Request pending. Wait for it asynchronously } } return false; // Have read all the data synchronously — good luck … } bool CJetRpcClient::EndRequest() { if(!HttpEndRequestA(m_hRequest, NULL, (m_bAsync ? INTERNET_FLAG_ASYNC : 0), (DWORD_PTR)&m_marshalling)) // If the method has failed to complete immediately { DWORD dwErr = GetLastError(); // Is non-null as there was not a success if(dwErr != ERROR_IO_PENDING) // Pending-error means that the request was stared OK, otherwise, indicates a failure ThrowError(CJetIe::LoadString(IDS_E_WININET_SENDREQ) + L'\n' + CJetIe::GetSystemError(dwErr)); return false; // Request pending. Wait for it asynchronously } return true; // Happened to be sync; execute one more step immediately } void CJetRpcClient::PrepareDataToSend() { m_streamTransmit = OnPrepareDataToSend(); if(m_streamTransmit == NULL) ThrowError(CJetIe::LoadString(IDS_E_NODATATOSEND)); } /// Some additional services for displaying the internet status constants as human-readable text; included in the Debug version only. #ifdef _TRACE typedef struct tagStatus { DWORD dwStatus; LPCWSTR sStatus; LPCWSTR sDescription; }InternetStatus; CStringW LookupInternetStatus(DWORD dwStatus) { static InternetStatus statuses[] = { { INTERNET_STATUS_CLOSING_CONNECTION, L"INTERNET_STATUS_CLOSING_CONNECTION", L"Closing the connection to the server. The lpvStatusInformation parameter is NULL." }, { INTERNET_STATUS_CONNECTED_TO_SERVER, L"INTERNET_STATUS_CONNECTED_TO_SERVER", L"Successfully connected to the socket address (SOCKADDR) pointed to by lpvStatusInformation." }, { INTERNET_STATUS_CONNECTING_TO_SERVER, L"INTERNET_STATUS_CONNECTING_TO_SERVER", L"Connecting to the socket address (SOCKADDR) pointed to by lpvStatusInformation." }, { INTERNET_STATUS_CONNECTION_CLOSED, L"INTERNET_STATUS_CONNECTION_CLOSED", L"Successfully closed the connection to the server. The lpvStatusInformation parameter is NULL." }, { INTERNET_STATUS_CTL_RESPONSE_RECEIVED, L"INTERNET_STATUS_CTL_RESPONSE_RECEIVED", L"Not implemented." }, { INTERNET_STATUS_DETECTING_PROXY, L"INTERNET_STATUS_DETECTING_PROXY", L"Notifies the client application that a proxy has been detected." }, { INTERNET_STATUS_HANDLE_CLOSING, L"INTERNET_STATUS_HANDLE_CLOSING", L"This handle value has been terminated." }, { INTERNET_STATUS_HANDLE_CREATED, L"INTERNET_STATUS_HANDLE_CREATED", L"Used by InternetConnect to indicate it has created the new handle. This lets the application call InternetCloseHandle from another thread, if the connect is taking too long. The lpvStatusInformation parameter contains the address of an INTERNET_ASYNC_RESULT structure." }, { INTERNET_STATUS_INTERMEDIATE_RESPONSE, L"INTERNET_STATUS_INTERMEDIATE_RESPONSE", L"Received an intermediate (100 level) status code message from the server." }, { INTERNET_STATUS_NAME_RESOLVED, L"INTERNET_STATUS_NAME_RESOLVED", L"Successfully found the IP address of the name contained in lpvStatusInformation." }, { INTERNET_STATUS_PREFETCH, L"INTERNET_STATUS_PREFETCH", L"Not implemented." }, { INTERNET_STATUS_RECEIVING_RESPONSE, L"INTERNET_STATUS_RECEIVING_RESPONSE", L"Waiting for the server to respond to a request. The lpvStatusInformation parameter is NULL." }, { INTERNET_STATUS_REDIRECT, L"INTERNET_STATUS_REDIRECT", L"An HTTP request is about to automatically redirect the request. The lpvStatusInformation parameter points to the new URL. At this point, the application can read any data returned by the server with the redirect response and can query the response headers. It can also cancel the operation by closing the handle. This callback is not made if the original request specified INTERNET_FLAG_NO_AUTO_REDIRECT." }, { INTERNET_STATUS_REQUEST_COMPLETE, L"INTERNET_STATUS_REQUEST_COMPLETE", L"An asynchronous operation has been completed. The lpvStatusInformation parameter contains the address of an INTERNET_ASYNC_RESULT structure." }, { INTERNET_STATUS_REQUEST_SENT, L"INTERNET_STATUS_REQUEST_SENT", L"Successfully sent the information request to the server. The lpvStatusInformation parameter points to a DWORD value that contains the number of bytes sent." }, { INTERNET_STATUS_RESOLVING_NAME, L"INTERNET_STATUS_RESOLVING_NAME", L"Looking up the IP address of the name contained in lpvStatusInformation." }, { INTERNET_STATUS_RESPONSE_RECEIVED, L"INTERNET_STATUS_RESPONSE_RECEIVED", L"Successfully received a response from the server. The lpvStatusInformation parameter points to a DWORD value that contains the number of bytes received." }, { INTERNET_STATUS_SENDING_REQUEST, L"INTERNET_STATUS_SENDING_REQUEST", L"Sending the information request to the server. The lpvStatusInformation parameter is NULL." }, { INTERNET_STATUS_STATE_CHANGE, L"INTERNET_STATUS_STATE_CHANGE", L"Moved between a secure (HTTPS) and a nonsecure (HTTP) site. The user must be informed of this change; otherwise, the user is at risk of disclosing sensitive information involuntarily. When this flag is set, the lpvStatusInformation parameter points to a status DWORD that contains additonal flags." }, { NULL, NULL, NULL } }; CStringW sRet; int a; for(a = 0; (a < 0x1000) && (statuses[a].sStatus != NULL); a++) { if(statuses[a].dwStatus == dwStatus) { sRet.Format(L"%s (%#010X) — %s", statuses[a].sStatus, statuses[a].dwStatus, statuses[a].sDescription); return sRet; } } sRet.Format(L"UNKNOWN (%#010X) — UNKNOWN", statuses[a].dwStatus); return sRet; } CStringW LookupInternetState(DWORD dwStatus) { static InternetStatus statuses[] = { { INTERNET_STATE_CONNECTED, L"INTERNET_STATE_CONNECTED", L"Connected state (mutually exclusive with disconnected state)." }, { INTERNET_STATE_DISCONNECTED, L"INTERNET_STATE_DISCONNECTED", L"Disconnected state. No network connection could be established." }, { INTERNET_STATE_DISCONNECTED_BY_USER, L"INTERNET_STATE_DISCONNECTED_BY_USER", L"Disconnected by user request." }, { INTERNET_STATE_IDLE, L"INTERNET_STATE_IDLE", L"No network requests are being made by Windows Internet." }, { INTERNET_STATE_BUSY, L"INTERNET_STATE_BUSY", L"Network requests are being made by Windows Internet." }, { INTERNET_STATUS_USER_INPUT_REQUIRED, L"INTERNET_STATUS_USER_INPUT_REQUIRED", L"The request requires user input to be completed." }, { NULL, NULL, NULL } }; CStringW sRet; int a; for(a = 0; (a < 0x1000) && (statuses[a].sStatus != NULL); a++) { if(statuses[a].dwStatus == dwStatus) { sRet.Format(L"%s (%#010X) — %s", statuses[a].sStatus, statuses[a].dwStatus, statuses[a].sDescription); return sRet; } } sRet.Format(L"UNKNOWN (%#010X) — UNKNOWN", statuses[a].dwStatus); return sRet; } /// Retuns the display-name of an FSM step. CStringW LookupFsmState(int nState) { static LPCWSTR states[] = { L"stateIdle", L"stateInitializing", L"stateReadyToSend", L"stateTransmitting", L"stateReadyToReceive", L"stateReceiving", L"stateParsingReply", L"stateCompleted", L"stateCloseRequest", L"stateShutdown", L"stateNULL" }; if((nState >= 0) && (nState < sizeof(states) / sizeof(*states))) return states[nState]; return L""; } #endif void CALLBACK CJetRpcClient::InternetStatusCallback(HINTERNET hInternet, DWORD_PTR dwContext, DWORD dwInternetStatus, LPVOID lpvStatusInformation, DWORD dwStatusInformationLength) { // Diagnotic trace just to see whatsappenin #ifdef _TRACE TRACE(L"InternetStatusCallback: %s.", LookupInternetStatus(dwInternetStatus)); if(dwInternetStatus == INTERNET_STATUS_STATE_CHANGE) TRACE(L"InternetStatusCallback: • state has changed to %s.", LookupInternetState(*(DWORD*)lpvStatusInformation)); #endif // _TRACE // Extract the passed context data CMarshallingData *pMD = (CMarshallingData*)dwContext; // Check if this is the second call informing that the handle is being closed (all the deinit has occured in response to the first call) if(IsBadReadPtr(pMD, sizeof(CMarshallingData))) // Some deinit has already invalidated the context, don't use it! return; // We cannot issue an error in this case, so just return … if((dwInternetStatus == INTERNET_STATUS_HANDLE_CLOSING) && (pMD->dwTag != MARSHALLING_DATA_TAG)) return; // Just have nothing to do HWND hwndMarshal = pMD->hwnd; bool bAsync = pMD->bAsync; // Errors generated by this block should be marshalled to be handled correctly try { // What is the notification we have received in this callback? switch(dwInternetStatus) { case INTERNET_STATUS_REQUEST_COMPLETE: // This indicates that an asynchronous operation has completed; check which exactly it was, whether it has succeeded, and what to do then { // First, check for errors INTERNET_ASYNC_RESULT *pResult = (INTERNET_ASYNC_RESULT*)lpvStatusInformation; if(pResult->dwError != ERROR_SUCCESS) { // Notify the object on the main apartment about the error; then, just quit the callback — the request object's handler should initiate the shutdown sequence CoInitialize(NULL); // Just in case, if it's another thread without an initialized apartment CJetIeException::Throw(CJetIe::LoadString(IDS_E_WININET_SENDREQ) + L'\n' + CJetIe::GetSystemError()); // Do not use the instance pointer from another thread. Instead, throw the exception using JetIeException } // OK, this is a notification about a successful completion of some async operation // Marshal the notification to our home apartment if(bAsync) { if(!::PostMessage(hwndMarshal, WM_JETRPCCLIENT_MARSHAL_REQUESTCOMPLETE, 0, 0)) ASSERT(FALSE && "Marshalling has failed"); } else ::SendMessage(hwndMarshal, WM_JETRPCCLIENT_MARSHAL_REQUESTCOMPLETE, 0, 0); } break; case INTERNET_STATUS_HANDLE_CLOSING: if(bAsync) { if(!::PostMessage(hwndMarshal, WM_JETRPCCLIENT_MARSHAL_HANDLECLOSING, 0, (LONG)(INT_PTR)hInternet)) ASSERT(FALSE && "Marshalling has failed"); } else ::SendMessage(hwndMarshal, WM_JETRPCCLIENT_MARSHAL_HANDLECLOSING, 0, (LONG)(INT_PTR)hInternet); break; case INTERNET_STATUS_HANDLE_CREATED: if(bAsync) { if(!::PostMessage(hwndMarshal, WM_JETRPCCLIENT_MARSHAL_HANDLECREATED, 0, (LONG)(INT_PTR)hInternet)) ASSERT(FALSE && "Marshalling has failed"); } else ::SendMessage(hwndMarshal, WM_JETRPCCLIENT_MARSHAL_HANDLECREATED, 0, (LONG)(INT_PTR)hInternet); break; } } catch(_com_error e) { // Try marshalling the exception information to the calling object try { CStringW sErrorMessage = COM_REASON(e); COM_TRACE(); ReportError(NULL, hwndMarshal, sErrorMessage, bAsync); // TODO: check if we're trying to jump from another thread here } catch(_com_error e) { // Whoa … everything is quite bad. Fatal error again (the callee has failed to initiate the graceful shutdown) // Deinitialize this instance InternetCloseHandle(hInternet); ASSERT(FALSE); // TODO: write implementation similar to one at the first catch of this func } } } bool CJetRpcClient::FSM() { #ifdef _TRACE TRACE(L"Request FSM is stepping from the %s=%d state.", LookupFsmState(m_state), m_state); // Report the state #endif // FSM logics switch(m_state) { case stateIdle: // Clear the errors left from the previous run m_sLastError.Empty(); m_bTerminationCallbackInvoked = false; m_state = stateInitializing; m_xmlResponse = NULL; m_streamTransmit = NULL; m_streamReceive = NULL; return true; case stateInitializing: m_state = stateReadyToSend; return BeginRequest(); // Start the request. Either sync or async case stateReadyToSend: PrepareDataToSend(); m_state = stateTransmitting; return SendData(); case stateTransmitting: m_state = stateReadyToReceive; // The next state return EndRequest(); case stateReadyToReceive: m_state = stateReceiving; return true; // Go to receiving, exec now case stateReceiving: if(ReadData()) // Request data for reading or process the results of the last request, returns true if wants to be called again return false; // Go async // If we're here, then we're thru with reading the data m_state = stateParsingReply; return true; // Jump to the state case stateParsingReply: m_streamReceive = NULL; // Finish with the stream. Now we have the XML object loaded and will work with it ValidateXmlResponse(); // Finally, the data was received OK! TRACE(L"Server reply has been received async:\n%s", (LPCWSTR)(_bstr_t)m_xmlResponse->xml); // Switch to the Completed state m_state = stateCompleted; return true; // Do it now case stateCompleted: // Invoke the external callback if(!m_bTerminationCallbackInvoked) // Call back no more than once { m_bTerminationCallbackInvoked = true; OnComplete(m_xmlResponse); } // Run one more request? if(OnWhetherInvokeAgain()) { m_state = stateIdle; return true; } // Cleanup, as no more requests needed m_state = OnWhetherCloseConnection() ? stateShutdown : stateCloseRequest; return true; case stateCloseRequest: // Cleanup ASSERT(m_streamReceive == NULL); m_streamReceive = NULL; ASSERT(m_streamTransmit == NULL); m_streamTransmit = NULL; // Shutdown the request m_hRequest.Detach(); m_state = stateIdle; ASSERT(FALSE && "(H) This branch was not used by the moment of implementation and has not been tested yet for correct workflow following this point. Please take care."); return false; case stateShutdown: // Cleanup ASSERT(m_streamReceive == NULL); m_streamReceive = NULL; ASSERT(m_streamTransmit == NULL); m_streamTransmit = NULL; // Shutdown the request m_hRequest.Detach(); // Close the connection m_hConnection.Detach(); return false; // Wait for the notification on that the connection has been closed. There will switch to Idle default: // A state that does not suppose asynchronous operations completion ASSERT(FALSE); ThrowError(CJetIe::LoadString(IDS_INCONSISTENCY)); } ASSERT(FALSE); // Kinda lost branch return true; // Hmm … nothing returned explicitly. Call once more, as there's a watchdog outta there anyway } void CJetRpcClient::RunFSM() { try { // Call the FSM handler function and keep calling it again al long a required int a; for(a = 0; (a < 0x1000) && (FSM()); a++) ; if(!(a < 0x1000)) // Check the watchdog ThrowError(CJetIe::LoadString(IDS_INCONSISTENCY)); } catch(_com_error e) // Trap errors of the FSM and register them { CStringW sErrorMessage = COM_REASON(e); COM_TRACE(); ReportError(this, NULL, sErrorMessage, false); // Can do it sync because it's the same thread } } void CJetRpcClient::ReportError(CJetRpcClient *pInstance, HWND hwndMarshal, CStringW sErrorMessage, bool bAsync) { // Prepare the data for marshlling #ifdef _TRACE InterlockedIncrement(&m_nBstrOnTheRun); #endif _bstr_t bsErrorMessage = (LPCWSTR)sErrorMessage; if(pInstance != NULL) hwndMarshal = pInstance->m_hWnd; // Use the instance window for marshalling, if known else ASSERT((hwndMarshal != NULL) && "No instance is specified for error message marshalling, but the marshalling window handle is NULL."); // Check if there is a window that can be used for marshalling if(::IsWindow(hwndMarshal)) { // Marshal using the window if(bAsync) { if(!::PostMessage(hwndMarshal, WM_JETRPCCLIENT_MARSHAL_ERROR, 0, (LONG)(INT_PTR)bsErrorMessage.Detach())) // Async ASSERT(FALSE && "Marshalling has failed"); } else ::SendMessage(hwndMarshal, WM_JETRPCCLIENT_MARSHAL_ERROR, 0, (LONG)(INT_PTR)bsErrorMessage.Detach()); // Sync } else // No window. Either it has not been created yet, or there was a problem on or before creating of the window { // We're still working sync in this case, even though the mode may be set to async BOOL bDummy; if(pInstance != NULL) pInstance->OnMarshalError(WM_JETRPCCLIENT_MARSHAL_ERROR, 0, (LONG)(INT_PTR)bsErrorMessage.Detach(), bDummy); else ASSERT(FALSE && "Some error needs to be marshalled, but there is no window to marshal to and the instance is not known at the same time."); } } LRESULT CJetRpcClient::OnMarshalError(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { bHandled = TRUE; try { // Extract the error message _bstr_t bsErrorMessage((BSTR)(INT_PTR)lParam, false); #ifdef _TRACE InterlockedDecrement(&m_nBstrOnTheRun); #endif TRACE(L"An error has occured while processing the request. %s", (LPCWSTR)bsErrorMessage); // Save the error message for later use if(m_sLastError.IsEmpty()) // Do not overwrite the original error message with subsequent errors m_sLastError = (LPCTSTR)bsErrorMessage; // Inform the external callbacks, if any, of the failure if(!m_bTerminationCallbackInvoked) { m_bTerminationCallbackInvoked = true; // And call no more OnError((LPCWSTR)bsErrorMessage); } } catch(_com_error e) { // An error while reporting an error — wo we can do nothing more // Just try to shut down gently COM_TRACE(); ASSERT(FALSE && "A failure handler has in turn reported a failure. Can do nothing about that."); } // Cancel further processing (these will cause global request deinit by the callback) m_hRequest.Detach(); m_hConnection.Detach(); if(!IsWindow()) // No window, and the callback most likely has not been set => noone will invoke the OnHandleClosing callback, do some cleanup right here m_state = stateIdle; return 0; } LRESULT CJetRpcClient::OnMarshalRequestComplete(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { bHandled = TRUE; try { // Invoke the next pack of FSM steps RunFSM(); } catch(_com_error e) { CStringW sErrorMessage = COM_REASON(e); COM_TRACE(); ReportError(this, NULL, sErrorMessage, false); // Do it sync, on the same thread } return 0; } LRESULT CJetRpcClient::OnMarshalHandleCreated(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { bHandled = TRUE; return 0; } LRESULT CJetRpcClient::OnMarshalHandleClosing(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { bHandled = TRUE; // Monitor closing of the connection only. There may be more than one request per connection, do not monitor closing of request handles // Note that if the connection has actually been closed, we have to look at the previous value! if(((m_hConnection != NULL) && ((HINTERNET)(DWORD_PTR)lParam == m_hConnection)) || ((HINTERNET)(DWORD_PTR)lParam == m_hConnection.Previous)) { try { // Detach the callback function InternetSetStatusCallbackA(m_hConnection, NULL); // Set state to idle m_state = stateIdle; // Deinitialize marshalling DestroyWindow(); // TODO: close the handles, just in case } COM_CATCH(); } return 0; // Don't report any errors as that's gonna make no good } LRESULT CJetRpcClient::OnCreateWindow(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { bHandled = TRUE; TRACE(L"JetRpcClient Marshalling window created."); // Lock the object lifetime while this window exists (and request runs). OnLockObject(); // Initialize the marshalling data. ASSERT(m_marshalling.dwTag == 0); m_marshalling.dwTag = MARSHALLING_DATA_TAG; m_marshalling.hwnd = m_hWnd; m_marshalling.bAsync = m_bAsync; return 0; } void CJetRpcClient::OnFinalMessage(HWND hWnd) { TRACE(L"JetRpcClient Marshalling window destroyed."); // Deinit the marshalling data. ASSERT(m_marshalling.dwTag == MARSHALLING_DATA_TAG); m_marshalling.dwTag = 0; m_marshalling.hwnd = NULL; // Unlock the object lifetime OnUnlockObject(); } bool CJetRpcClient::IsAsync() { return m_bAsync; } XmlDocument CJetRpcClient::GetResponse() { return m_xmlResponse; }