This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.

MIND

Win32 Q&A
New C++ Classes for Better Resource Management in Windows
Jeffrey Richter
Code for this article: Win320400.exe (34KB)
 
T

o cure resource cleanup headaches and to make buffer allocation a little more efficient, I've created two C++ classes that I'm finding more and more valuable in my everyday Windows®-based programming projects: CEnsureCleanup and CAutoBuf. These classes make my code more robust, easier to write, and easier to maintain. I think you'll find them useful, too.

The Ensure Cleanup Template C++ Class


      Certainly, forgetting to close or release a resource is one of the most common programming bugs that developers face. To guarantee that a resource is properly cleaned up when its use goes out of scope, I created the CEnsureCleanup template C++ class (see Figure 1). Because it's a template class, CEnsureCleanup can help you clean up almost any type of object you can imagine.
      I have already defined specific instances of the CEnsureCleanup class for the most common Windows data types (see Figure 2). For most applications, the C++ classes that I've defined will be sufficient, but you can easily create a version of this templated class for additional data types. Defining a new type requires three pieces of information:

  • The type of the object (such as HANDLE, HLOCAL, HGLOBAL, HKEY, SC_HANDLE, PVOID, HMODULE, and so on).
  • The address of the function used to clean up the object (CloseHandle, LocalFree, GlobalFree, RegCloseKey, UnmapViewOfFile, FreeLibrary, and so on).
  • A value indicating that the object is invalid (usually NULL or 0, occasionally INVALID_HANDLE_VALUE or -1).

      For example, to clean up a kernel object, the data type is a HANDLE, the function to clean up the object is CloseHandle, and an invalid handle is identified with NULL. Similarly, to clean up a dynamically loaded DLL, the data type is an HMODULE, the function to clean up the resources is FreeLibrary, and an invalid object is identified with NULL. You can see all the different cleanup classes in Figure 2.
      Using CEnsureCleanup couldn't be easier. All you have to do is use an instance of the class wherever you'd usually use the normal data type. For example, instead of writing

 

  HANDLE hfile = CreateFile(...);
  
•••
CloseHandle(hfile);

 

you'd now write:

 

  CEnsureCloseFile hfile = CreateFile(...);
  
•••
// NOTE: the call to CloseHandle is no longer necessary.

 

With the C++ class data type, you never have to call CloseHandle to release the resource. When the C++ object goes out of scope, the destructor is called and CloseHandle will be called for you automatically. In fact, you're now guaranteed that the object will be cleaned up even if you return from the middle of a function or if an exception is raised.
      By the way, if you're concerned about overhead, don't be. I checked the assembly language produced by the optimizing compiler when using these classes, and the code produced is just as efficient as if you had explicitly called the cleanup functions yourself. This is because the CEnsureCleanup C++ class only uses inline functions. I can't believe that I've been programming for all these years without these classes. Now I use them for almost all my C++ programming.

The Auto Buffer Template C++ Class


      Many Windows functions fill buffers that you must allocate. Before calling the function, you often have no idea of the proper buffer size. So for many of these functions, you must call the function once to get the required buffer size and allocate the buffer. Then you can call the function a second time to actually get the data into the buffer. This, of course, is quite inconvenient. Plus, the buffer requirement may change between calls to the function since Windows is a preemptive multithreading operating system. In this case, your second call to the function will fail. It's unlikely that this failure will occur, but a well-written application should certainly take this possibility into consideration and handle the situation with style and grace.
      For example, Figure 3 contains the code you need to properly retrieve a registry value using RegQueryValueEx. The following code performs the same functionality, but makes use of the CAutoBuf class (shown in Figure 4) to allocate the buffer.


  CAutoBuf<PTSTR, sizeof(TCHAR)> pszValue;
  
pszValue = 1;
LONG lErr;
do {
lErr = RegQueryValueEx(hkey, TEXT("SomeValueName"),
NULL, NULL, pszValue, pszValue);
} while (lErr == ERROR_MORE_DATA);
if (lErr != ERROR_SUCCESS){
// Error: Proper handling not shown
}

 

The CAutoBuf class has the added feature of automatically freeing the buffer when the object goes out of scope.
      The CAutoBuf C++ class makes it a cinch to work with functions that require buffers. A CAutoBuf object allocates a data buffer whose size is determined automatically by whatever the Windows function tells it.
      The CAutoBuf C++ class is a template class, so it supports buffers holding data of any type. To use the class, simply declare an instance by indicating the type of data you want it to hold. Here is an example of a CAutoBuf that should contain a QUERY_SERVICE_CONFIG structure (which is variable in length):

 

  CAutoBuf<QUERY_SERVICE_CONFIG> pServiceConfig;
  

 

To fill this buffer, make a call to QueryServiceConfig as follows:

 

  BOOL fOk;
  
fOk = QueryServiceConfig(
hService, // hService (SC_HANDLE)
pServiceConfig, // pServiceConfig (QUERY_SERVICE_CONFIG*)
pServiceConfig, // cbBufferSize (DWORD)
pServiceConfig)); // pcbBytesNeeded (PDWORD)

 

You'll notice that I'm passing the pServiceConfig object for three of the parameters. This works because the CAutoBuf class exposes three cast methods on the objects:

  • A cast method that returns the address of the data buffer
  • A DWORD cast method that returns the size of the data buffer in bytes
  • A PDWORD cast method that returns the address of a DWORD member variable that will be filled in with the required size of the buffer

      If the size passed to cbBufferSize is too small, then QueryServiceConfig returns FALSE, and a subsequent call to GetLastError returns ERROR_INSUFFICIENT_BUFFER. In this case, all you have to do is call QueryServiceConfig again, and the buffer will automatically adjust its size to the value returned in the pcbBytesNeeded parameter. This time, QueryServiceConfig should successfully fill the buffer and return nonzero.
      However, it's still possible that QueryServiceConfig might fail because another thread may have changed the services status information. So a well-written application should call QueryServiceConfig repeatedly until it succeeds. To make this easy, use the GROWUNTIL macro shown in Figure 4. This macro calls the desired function in a loop until the function succeeds, or until the function fails with an error other than ERROR_MORE_DATA or ERROR_INSUFFICIENT_BUFFER. Here is an example of how to use the GROWUNTIL macro:

 

  BOOL fOk;
  
GROWUNTIL(FALSE,
fOk = QueryServiceConfig(
hService, // hService (SC_HANDLE)
pServiceConfig, // pServiceConfig (QUERY_SERVICE_CONFIG*)
pServiceConfig, // cbBufferSize (DWORD)
pServiceConfig)); // pcbBytesNeeded (DWORD*)

 

Conclusion


      So get going and clean up your resources you use efficiently with CEnsureCleanup, and make buffer allocation a snap with CAutoBuf. These are two classes you'll be glad you explored.

Jeffrey Richter is the author of Programming Applications for Microsoft Windows, Fourth Edition (Microsoft Press, 1999). He is a consultant specializing in Windows application programming/design. Reach him at www.JeffreyRichter.com.

From the April 2000 issue of MSDN Magazine.