The first order of business is making the library fail early and often to smoke out errors. I
am accomplishing this by intentionally making the Win32 API brittle. Most of the API
functions return FALSE or NULL when something goes wrong. If these
return values are left unchecked, Windows will try to cover the error and let the developer
muddle through blissfully unaware of any problems.
What I've done is wrap each API call in a method named CheckWin32. Actually,
it's a pair of methods:
template<class T> inline T CheckWin32(T result)
{
if (!result && ::GetLastError())
{
throw Win32Error();
}
return result;
}
inline BOOL CheckWin32(BOOL result)
{
if (::GetLastError())
{
throw Win32Error();
}
return result;
}
// Elsewhere in the library...
void Yadda()
{
HINSTANCE hInstance = CheckWin32(::GetModuleHandle(NULL));
}
The first version of CheckWin32 is a template to check the validity of various types
of return values and pass the values through. If the value is 0, NULL, FALSE, or anything else that
will evaluate to a boolean false (say, an override of operator! in the unlikely event a Win32 API
ever returns a reference to a C++ class), then the second half of the if statment is evaluated.
If ::GetLastError returns non-zero, the function assumes that an error indeed occurred
and it throws an exception. The function makes this call to deal with the few API calls that return
non-zero even when the method was successful.
The second version deals with API functions that return a boolean value that does not necessarily
correlate with the success or failure of the call. This one always checks ::GetLastError
to determine if an error occurred. Of course, this means that the developer will occasionally need
to explicitly set the last error to 0 before calling the function, but this is a small price to
pay for good error checking.
Now that the API calls fail every time they hit an error condition the developer must decide how
to handle the exceptions. I have chosen to not provide exception handling inside the library
for most cases in order to keep clients of the library from becoming lazy. I think it's a good
habit to expose errors and deal with them rather than hide errors and pretend they're benign.
Aren't You Overdoing It A Tad?
Maybe I am. Particularly down in the bowels of Splitter,
HorizontalSplitter, and VerticalSplitter I might be able to lay off
checking every single GDI call. Since I'm not sure, though, I'll keep them in place for now.
At the very least, I'll make a concession to crank back a little bit in release builds, but debug
builds will have all the error checking I can possibly cram into them.
Error Checking To the Rescue
Speaking of splitter classes, I have noticed an elusive exception occurring in the SplitterWindow
sample application that never appeared before I dropped in this error-checking
code. Occasionally when I resize the application window smaller than a particular size,
then slowly make it bigger again, somewhere along the line an exception is being thrown that
has to do with "Access denied." I don't know what it is yet, but I do know that
I may never have found it without obsessive error checking.