
|
Code Corner - Source Code - Resource
Management
Resource Management in an Unmanaged WorldBy Kenny Kerr, January 2002 Summary InformationPrerequisitesThis article assumes you’re familiar with C++ programming. ResourcesMicrosoft Win32 Internet Functions Icon Browser: An Exercise in Resource
Management Bjarne Stroustrup's Homepage
IntroductionAfter writing about resource management in the managed world of .NET, I thought it would be useful to talk a little bit about resource management in unmanaged C++ programming. In my Icon Browser article I illustrated why resource management is still vitally important in a world where the garbage collector reigns. When writing code in C++, resource management is just as important (if not more so), but rather more visible and obvious. Despite popular belief, C++ is designed to avoid direct access to resources such as heap-allocated memory. Stroustrup went to great lengths to ensure that C++ provided a robust language with ample support for resource management. Every day programming should involve using well designed classes that represent well thought-out concepts in your problem domain instead of code riddled with memory allocations and resource handles etc. In this article I use the Win32 Internet functions, commonly known as WinInet, to illustrate a number of resource management issues that must be addressed when writing robust software in C++. WinInet BasicsWinInet is a C-style API, where functionality is exposed through a set of functions and state is managed by passing handles to the various functions. For example, before performing any Internet-related functions you need to call InternetOpen, which returns an HINTERNET handle that you must use in subsequent calls to the WinInet APIs. When you are done using WinInet you must pass the handle to InternetCloseHandle to indicate this and allow WinInet to free any resources that it has held on to for the purposes of serving your requests. The Naïve ApproachFigure 1 illustrates a simple example of using the WinInet APIs to download a web page. Its simple because it assumes ASCII encoding and does not contain any error handling code. It does work however and will help to illustrate the importance of resource management. Figure 1 - Procedural Style Programming
I come across many programmers who think they are programming in an object-oriented fashion if they use libraries such as MFC, STL or ATL. The result is code that uses such libraries but does not build on them. For example they will build a COM server using ATL and all the implementation will go inside the interface methods. They won’t think to first design their component to see whether they can implement some of the concepts found in their problem domain as classes. So what they are actually doing is using procedural programming techniques on top of an object-oriented framework. Nothing could be worse. C was designed around procedural programming. C++ allows you to choose a paradigm in which to program. You can use procedural programming, object-oriented programming, etc. But paradigms don’t mix very well. For example exceptions are a standard part of most C++ based architectures. Procedural programming such as is illustrated in Figure 1 is not resilient in the face of exceptions. Object-oriented programming also has other advantages. Data abstraction helps to reduce the complexity of software design and thus reduces the risk of error. In Figure 1 there is no explicit relationship between statements that allocate resources and statements that free them. This becomes a maintenance nightmare, as it is very easy to accidentally remove a delete statement at the end of a long procedure or forget to put it there in the first place. Classes to the RescueUML is about helping you to visualise a design. It doesn’t have to be terribly complex or time consuming. It just needs to stimulate your thinking about design so that you can spend less time trying different designs in code. After all it takes a lot less time to try a few different designs on a white board than it does to try the designs in C++ header files. When I look at the source code in Figure 1 the following class design springs to mind (see Figure 2). Normally you should strive to model the problem domain and not the underlying technology. But since we're merely looking for a way to encapsulate WinInet function calls, this will suffice. Figure 2 - UML Class Diagram |
{ Session session; HttpConnection connection(session, _T("www.kennyandkarin.com")); HttpRequest request(connection, _T("index.html")); request.SendRequest(); DWORD dwNumberOfBytesAvailable = 0; while (0 < (dwNumberOfBytesAvailable = request.QueryDataAvailable())) { char* pBuffer = new char[dwNumberOfBytesAvailable + 1]; pBuffer[dwNumberOfBytesAvailable] = 0; DWORD dwBytesRead = 0; request.ReadFile(pBuffer, dwNumberOfBytesAvailable); std::cout << pBuffer << std::endl; delete [] pBuffer; } return 0; } |
There is just one more glaring problem with the code in Figure 4: the use of new and delete. Contrary to popular belief you really shouldn’t see these two statements in every day code. They should be tucked away deep in your class libraries. Memory leaks are a frequent source of bugs in applications. Fortunately modern C++ provides the tools to remove the need for explicit new and delete statements most of the time. Enter the Standard C++ Library.
The Standard C++ Library, more commonly referred to as the STL, is a library of containers and algorithms (among other things) for use in everyday code. If you need a dynamically sized array you typically use the vector template class instead of allocating and reallocating array elements on the heap. If you need a string you would use the basic_string template instead of a char* or wchar_t*.
To get around the problem in Figure 4 we can use a vector as an array of char’s. The vector container stores its elements in contiguous memory. This is by design so that you can use it with old code and libraries that expect a traditional C-style array.
{ Session session; HttpConnection connection(session, _T("www.kennyandkarin.com")); HttpRequest request(connection, _T("index.html")); request.SendRequest(); DWORD dwNumberOfBytesAvailable = 0; while (0 < (dwNumberOfBytesAvailable = request.QueryDataAvailable())) { std::vector<char> buffer(dwNumberOfBytesAvailable + 1); DWORD dwBytesRead = 0; request.ReadFile(&*buffer.begin(), dwNumberOfBytesAvailable); std::cout << static_cast<PCTSTR>(&*buffer.begin()) << std::endl; } return 0; } |
Now the code is much more explicit, maintainable and resilient in the face of exceptions! There is still some room for improvement. vector::begin returns an iterator. To get the address of the sequence of elements we need to dereference the iterator (*). This returns a reference to the first element in the sequence. Then we get the address of this element (&). All of this should be tucked away in a member function. But I leave this as an exercise.
While we’re on the topic of mixing C-style APIs with C++ code I thought it would be useful to talk about error handling. This article is not about error handling so I'll be brief. Typical procedural libraries indicate error conditions by using return codes from functions. This has many drawbacks, the most notable to this discussion is that they can be simply ignored as I have illustrated in Figure 1. Another problem is that the developer of the function library has no idea how it will be used by other developers. He or she doesn’t know whether to quit the program when an error occurs or to pop up a message box displaying the error message.
When C++ was still young and exceptions weren’t commonplace among compiler implementations, many library developers chose to continue using the procedural approach for indicating error conditions. You can see this in class libraries where class constructors don’t play much of a role. Instead there will be an “Initialize” method that will return error conditions. The problem is that you cannot return error information from a constructor in the traditional way. But constructors are an important part of object-oriented design.
The solution is to use exceptions. An exception is thrown rather than returned. So it can be used from within constructors. It winds its way back up the call stack until it finds a suitable exception handler. Now the library developer need not worry about how the developer will want to handle errors. He or she simply throws an exception. The other advantage is that you just simple can’t ignore exceptions. If an error occurs an exception will be thrown and if you don’t catch it your application will crash!
In light of all of this I created a simple exception class to be used for raising error conditions. Most of the WinInet APIs indicate failure by returning some error code or by returning FALSE. I can use GetLastError and FormatMessage respectively to retrieve the error code and its associated friendly text message. If you were lazy you could just copy and paste the these API calls after every WinInet function call. Encapsulating them inside a single class make maintenance a breeze and you get a handy exception class to boot.
It’s not hard to write resource management code in C++. It just takes a bit of thought before you start coding. It can be as simple as drawing a UML class diagram on a piece of paper. Larger applications may need more formal design and may require the use of tools such as Rational Rose. Whatever the degree of complexity, spending some time on design before hand pays off immensely down the road when you need to maintain software. It also helps to know what C++ can offer in terms of resource management. I hope this short article has helped you to see the possibilities.