/*---------------------------------------------------------------------------------------------- General purpose method to set up and store an error, rather than throw an exception in the normal way. ----------------------------------------------------------------------------------------------*/ HRESULT StackDumper::RecordError(REFGUID iid, StrUni stuDescr, StrUni stuSource, int hcidHelpId, StrUni stuHelpFile) { // We are going to make a new error info object. ICreateErrorInfoPtr qcerrinfo; IErrorInfoPtr qerrinfo; HRESULT hr; // If we can't get a new error object, the only documented cause is E_OUTOFMEMORY. if (FAILED(hr = ::CreateErrorInfo(&qcerrinfo))) { return E_OUTOFMEMORY; } if (FAILED(hr = qcerrinfo->QueryInterface(IID_IErrorInfo, (LPVOID FAR*) &qerrinfo))) { return E_UNEXPECTED; } hr = qcerrinfo->SetDescription(const_cast<OLECHAR *>(stuDescr.Chars())); if (FAILED(hr)) return hr; hr = qcerrinfo->SetGUID(iid); if (FAILED(hr)) return hr; hr = qcerrinfo->SetSource(const_cast<OLECHAR *>(stuSource.Chars())); if (FAILED(hr)) return hr; hr = qcerrinfo->SetHelpFile(const_cast<OLECHAR *>(stuHelpFile.Chars())); if (FAILED(hr)) return hr; if (!hcidHelpId) hcidHelpId = khcidNoHelpAvailable; hr = qcerrinfo->SetHelpContext(hcidHelpId); if (FAILED(hr)) return hr; ::SetErrorInfo(0, qerrinfo); return hr; }
/*---------------------------------------------------------------------------------------------- // This method is called when a Throwable exception is caught at the end of a COM method, // or (with a dummy ThrowableSd) when some other exception is caught. It transforms the info // in the Throwable into a standard COM error report (creating an IErrorInfo and registering // it.) It returns the HRESULT that should be returned by the COM method. // There are several different situations which this method has to handle. The following // comments describe the situations, and how they are indicated. Each "indication" presumes // that the previous "indications" failed. // 1. We called a COM method which supports IErrorInfo and already provides all the information // we need to pass to our own caller. This is indicated by a help ID of -1. // 2. We, or a method we called that doesn't support IErrorInfo, ran out of memory. // We need to set up the special error object pre-created for this case. This is // indicated by thr.Error() being E_OUTOFMEMORY. // 3. A programming error has been caught and a stack dump generated, either in our own code // or in the code that called us. This is indicated by finding that thr is actually // a ThrowableSd. Make an error object, with a description that includes the stack dump. ----------------------------------------------------------------------------------------------*/ HRESULT HandleThrowable(Throwable & thr, REFGUID iid, DummyFactory * pfact) { StrUni stuDesc; HRESULT hrErr = thr.Error(); HRESULT hr; // If we already have error info, we set it again (very likely got cleared by previous // CheckHr), and then just return the HRESULT. if (thr.GetErrorInfo()) { ::SetErrorInfo(0, thr.GetErrorInfo()); return hrErr; } // We need a Unicode version of the ProgId, but we should avoid allocating memory // since we don't yet know that we have not run out. // Since all our progids are in simple ascii, we can do the simplest possible conversion. // Since we hopefully have at least a little stack to work with, use _alloca. OLECHAR * pszSrc = (OLECHAR *)_alloca((StrLen(pfact->GetProgId()) + 1) * isizeof(OLECHAR)); OLECHAR * pchw = pszSrc; for (const TCHAR * pch = pfact->GetProgId(); *pch; pch++, pchw++) *pchw = *pch; *pchw = 0; if (hrErr == E_OUTOFMEMORY) { // Use the pre-created error info object so we don't have to allocate now. // It already has a description, help file path, and help context ID. // If a further E_OUTOFMEMORY occurs calling SetGUID or SetSource, just ignore it. s_qcerrinfoMem->SetGUID(iid); s_qcerrinfoMem->SetSource(pszSrc); SetErrorInfo(0, s_qerrinfoMem); return hrErr; } // Otherwise we are going to make a new error info object. // Get any message supplied by the Throwable. StrUni stuUserMsg(thr.Message()); // See if a stack dump is available. ThrowableSd * pthrs = dynamic_cast<ThrowableSd *>(&thr); char * pchDump = NULL; if (pthrs) pchDump = const_cast<char *>(pthrs->GetDump()); else if (!stuUserMsg.Length()) { // If we don't have any sort of nice message, treat it as an internal error. DumpStackHere("HandleThrowable caught an error with no description"); pchDump = const_cast<char *>(StackDumper::GetDump()); } if (pchDump) { // We have a stack dump. StrUni stuModName = ModuleEntry::GetModulePathName(); // Do we already have a description? If not make one. if (!stuUserMsg.Length()) { // No, use a default one. StrUni stuHrMsg = ConvertException((DWORD)hrErr); StrUni stuUserMsgFmt; stuUserMsgFmt.Load(kstidInternalError); // Would it be better to strip off the path? stuUserMsg.Format(stuUserMsgFmt, stuHrMsg.Chars(), stuModName.Chars()); } stuDesc.Format(L"%s%s%S\r\n\r\n%s", stuUserMsg.Chars(), ThrowableSd::MoreSep(), pchDump, GetModuleVersion(stuModName.Chars()).Chars()); } else { // We've made sure we have a message already; use it. stuDesc = stuUserMsg; } StrUni stuSource(pszSrc); hr = StackDumper::RecordError(iid, stuDesc, stuSource, thr.HelpId(), GetModuleHelpFilePath()); if (FAILED(hr)) { if (hr == E_OUTOFMEMORY) { Throwable thr2(E_OUTOFMEMORY); return HandleThrowable(thr2, iid, pfact); } // just report the failure to the developer WarnHr(hr); // Hard to know what do do here. It should never happen. For paranoia's sake at least // return the original problem. } return hrErr; }