This code actually appears in nearly all my projects. However, a number of readers have missed this, so I'm putting this code here (there's no download, you can copy-and-paste from this Web page).
This shows a way of interfacing to
::FormatMessage from MFC and using it in an MFC-compatible fashion.
One of the common problems in handling errors is delivering complete, informative information to the end user. All too often, the problem is reported by one of these truly informative message boxes:
It is almost as useful as the one that says:
These are truly egregious examples which are all too common in far too many programs. The problem with this is that the user has no idea what to do in response to this error. Therefore, the user has to call tech support. What does this poor victim tell tech support? The contents of the dialog? You had better hope your tech support people don't know where your cubicle is, or you might meet with an unfortunate "accident" some day.
There is absolutely no excuse for such pitiful examples of error messages. None whatsoever.
Now, this is an informative message:
It says there is a file open error (if I had more than one kind of file, even that line would be more precise), it says what file had the problem, and it says why. The user looks at this, realizes that the file is open in some other program, and closes that program. Or checks that the protections are properly set on the file. But does not feel compelled to call tech support.
How do you get that nice result? The answer is a remarkably few number of lines of code, which is why there is never an excuse for not producing a decent error message.
For example, to produce this message, I did:
BOOL CMyClass::ReadFile(const CString & filename)
DWORD err = ::GetLastError();
msg.Format(fmt, filename, ErrorString(err));
AfxMessageBox(msg, MB_ICONERROR | MB_OK);
... read file here
This takes only five lines of code to do the job right. Therefore, there is never a justification for the completely incompetent message given as the first example.
Note one important point here. The very first line that is executed after the API fails is
::GetLastError. Do not put an
ASSERT before it; do not do anything else; capture that error immediately.
To accomplish this, the
ErrorString function must be written. However, given how trivial this function is to write, and that it only needs to be written once for your entire lifetime, there is little reason not to use it everywhere. To allow for localization, the string "Unknown error code %08x (%d)" is stored in the
STRINGTABLE as the string
I have never understood this phenomenon, but the first operating system that had an integrated error handling system, IBM's TSS/360, in 1968, would add a CRLF at the end of every error message string. This makes no sense. If I want a CRLF, I can add it. Nonetheless, Microsoft has continued this insanity nearly 20 years later. Each message ends with a CRLF. Why? There is no sensible explanation. So, I have to remove the terminal CRLF.
In addition, if you plan to use a message in some context that cannot understand newline sequences, such as a ListBox, you may wish to additionally do a
CString::Replace(_T("\r\n"), _T(" ")) to get rid of gratuitous internal CRLF sequences.
CString ErrorString(DWORD err);
CString ErrorString(DWORD err)
NULL) == 0)
t.Format(fmt, err, LOWORD(err));
Error = t;
LPTSTR p = _tcsrchr(s, _T('\r'));
if(p != NULL)
*p = _T('\0');
Error = s;
A Philosophy of MessageBox/AfxMessageBox Calls
There is no place, in any program I write, in which there are two places that issue the same
MessageBox/AfxMessageBox text. Just by looking at the text of the message, I can immediately tell what line of my program issued it. This is important. This makes it possible to determine the exact line of my program that issued the message.
Sometimes, if I have a generic error handler, I will pass in an extra
CString parameter which appears in the
MessageBox/AfxMessageBox so that the caller of the handler routine can be identified. There is no alternative to making sure that every user-visible error condition has a 1:1 correspondence with the site in the program that issues it.