다음을 통해 공유



July 2011

Volume 26 Number 07

Windows with C++ - C++ and the Windows API

By Kenny Kerr | July 2011

Kenny KerrThe Windows API presents a challenge to the C++ developer. The various libraries that make up the API are, for the most part, exposed either as C-style functions and handles or COM-style interfaces. Neither of these is very convenient to work with and requires some level of encapsulation or indirection.

The challenge for the C++ developer is to determine the level of encapsulation. Developers who grew up with libraries like MFC and ATL may be inclined to wrap everything up as classes and member functions, because that’s the pattern exhibited by the C++ libraries they’ve relied on for so long. Other developers may scoff at any sort of encapsulation and just use the raw functions, handles and interfaces directly. Arguably these other developers aren’t really C++ developers, but simply C developers with identity issues. I believe there’s a more natural middle ground for the contemporary C++ developer.

As I restart my column here at MSDN Magazine, I’ll show you how you can use C++0x, or C++ 2011 as it will likely be named, along with the Windows API to lift the art of native Windows software development out of the dark ages. For the next few months I’m going to take you through an extended tour of the Windows Thread Pool API. Follow along and you’ll discover how to write amazingly scalable applications without the need for fancy new languages and complicated or costly runtimes. All you’ll need is the excellent Visual C++ compiler, the Windows API and a desire to master your craft.

As with all good projects, some groundwork is needed to get off to a good start. How, then, am I going to “wrap” the Windows API? Rather than bog down every subsequent column with these details, I’m going to spell out my recommended approach in this column and simply build on this going forward. I’ll leave the issue of COM-style interfaces for the time being, as that won’t be needed for the next few columns.

The Windows API consists of many libraries that expose a set of C-style functions and one or more opaque pointers called handles. These handles usually represent a library or system resource. Functions are provided to create, manipulate and release the resources using handles. As an example, the CreateEvent function creates an event object, returning a handle to the event object. To release the handle and tell the system you’re done using the event object, simply pass the handle to the CloseHandle function. If there are no other outstanding handles to the same event object, the system will destroy it:

auto h = CreateEvent( ... );
CloseHandle(h);

New to C++

If you’re new to C++ 2011, I should point out that the auto keyword tells the compiler to deduce the type of variable from the initialization expression. This is useful when you don’t know the type of an expression, as is often the case in metaprogramming, or when you just want to save some keystrokes.

But you should almost never write code like this. Undoubtedly, the single most valuable feature C++ offers is that of the class. Templates are cool, the Standard Template Library (STL) is magical, but without the class nothing else in C++ makes sense. The class is what makes C++ programs succinct and reliable. I’m not talking about virtual functions and inheritance and other fancy features. I’m just talking about a constructor and a destructor. Often that’s all you need, and guess what? It doesn’t cost you anything. In practice, you need to be aware of the overhead imposed by exception handling, and I’ll address that at the end of this column.

To tame the Windows API and make it accessible to modern C++ developers, a class that encapsulates a handle is needed. Yes, your favorite C++ library may already have a handle wrapper, but was it designed from the ground up for C++ 2011? Can you reliably store these handles in an STL container and pass them around your program without losing track of who owns them?

The C++ class is the perfect abstraction for handles. Note I didn’t say “objects.” Remember that the handle is the object’s representative within your program, and is most often not the object itself. The handle is what needs shepherding—not the object. It may sometimes be convenient to have a one-to-one relationship between a Windows API object and a C++ class, but that’s a separate issue.

Even though handles are typically opaque, there are still different types of handles and, often, subtle semantic differences that necessitate a class template to adequately wrap handles in a general way. Template parameters are needed to specify the handle type and the specific characteristics or traits of the handle.

In C++, a traits class is commonly used to provide information about a given type. In this way I can write a single class template for handles and provide different traits classes for the different types of handles in the Windows API. A handle’s traits class also needs to define how a handle is released so that the handle class template can automatically release it if needed. As such, here’s a traits class for event handles:

struct handle_traits
{
  static HANDLE invalid() throw()
  {
    return nullptr;
  }
  static void close(HANDLE value) throw()
  {
    CloseHandle(value);
  }
};

Because many libraries in the Windows API share these semantics, they can be used for more than just event objects. As you can see, the traits class consists only of static member functions. The result is that the compiler can easily inline the code and no overhead is introduced, while providing a great deal of flexibility for metaprogramming.

The invalid function returns the value of an invalid handle. This is usually a nullptr, a new keyword in C++ 2011 representing a null pointer value. Unlike traditional alternatives, nullptr is strongly typed so that it works well with templates and function overloading. There are cases where an invalid handle is defined as something other than nullptr, so the inclusion of the invalid function in the traits class exists for that. The close function encapsulates the mechanism by which the handle is closed or released.

Given the outline of the traits class, I can go ahead and start defining the handle class template, as shown in Figure 1.

Figure 1 The Handle Class Template

template <typename Type, typename Traits>
class unique_handle
{
  unique_handle(unique_handle const &);
  unique_handle & operator=(unique_handle const &);
  void close() throw()
  {
    if (*this)
    {
      Traits::close(m_value);
    }
  }
  Type m_value;
public:
  explicit unique_handle(Type value = Traits::invalid()) throw() :
    m_value(value)
  {
  }
  ~unique_handle() throw()
  {
    close();
  }

I’ve named it unique_handle because it’s similar in spirit to the standard unique_ptr class template. Many libraries also use identical handle types and semantics, so it makes sense to provide a typedef for the most commonly used case, simply called handle:

typedef unique_handle<HANDLE, handle_traits> handle;

I can now create an event object and “handle” it as follows:

handle h(CreateEvent( ... ));

I’ve declared the copy constructor and copy assignment operator as private and left them unimplemented. This prevents the compiler from automatically generating them, as they’re rarely appropriate for handles. The Windows API allows certain types of handles to be copied, but this is a very different concept from C++ copy semantics.

The constructor’s value parameter relies on the traits class to provide a default value. The destructor calls the private close member function, which in turn relies on the traits class to close the handle if needed. In this way I have a stack-friendly and exception-safe handle.

But I’m not done yet. The close member function relies on the presence of a Boolean conversion to determine whether the handle needs to be closed. Although C++ 2011 introduces explicit conversion functions, this is not yet available in Visual C++, so I use a common approach to Boolean conversion to avoid the dreaded implicit conversions that the compiler otherwise permits:

private:
  struct boolean_struct { int member; };
  typedef int boolean_struct::* boolean_type;
  bool operator==(unique_handle const &);
  bool operator!=(unique_handle const &);
public:
  operator boolean_type() const throw()
  {
    return Traits::invalid() != m_value ? &boolean_struct::member : nullptr;
  }

This means I can now simply test whether I have a valid handle, but without allowing dangerous conversions to go unnoticed:

unique_handle<SOCKET, socket_traits> socket;
unique_handle<HANDLE, handle_traits> event;
if (socket && event) {} // Are both valid?
if (!event) {} // Is event invalid?
int i = socket; // Compiler error!
if (socket == event) {} // Compiler error!

Using the more obvious operator bool would’ve allowed those last two errors to go unnoticed. This does, however, allow one socket to be compared with another—hence the need to either explicitly implement the equality operators or declare them as private and leave them unimplemented.

The way a unique_handle owns a handle is analogous to the way the standard unique_ptr class template owns an object and manages that object through a pointer. It then makes sense to provide the familiar get, reset and release member functions to manage the underlying handle. The get function is easy:

Type get() const throw()
{
  return m_value;
}

The reset function is a bit more work, but builds on what I’ve already discussed:

bool reset(Type value = Traits::invalid()) throw()
{
  if (m_value != value)
  {
    close();
    m_value = value;
  }
  return *this;
}

I’ve taken the liberty of changing the reset function slightly from the pattern provided by unique_ptr by returning a bool value indicating whether or not the object has been reset with a valid handle. This comes in handy with error handling, to which I’ll return in a moment. The release function should now be obvious:

Type release() throw()
{
  auto value = m_value;
  m_value = Traits::invalid();
  return value;
}

Copy vs. Move

The final touch is to consider copy versus move semantics. Because I’ve already banned copy semantics for handles, it makes sense to allow move semantics. This becomes essential if you want to store handles in STL containers. These containers have traditionally relied on copy semantics, but with the introduction of C++ 2011, move semantics are supported.

Without going into a lengthy description of move semantics and rvalue references, the idea is to allow the value of an object to pass from one object to another in a way that’s predictable for the developer and coherent for library authors and compilers.

Prior to C++ 2011, developers had to resort to all kinds of complicated tricks to avoid the excessive fondness that the language—and by extension the STL—has for copying objects. The compiler would often create a copy of an object, then immediately destroy the original. With move semantics the developer can declare that an object will no longer be used and its value moved elsewhere, often with as little as a pointer swap.

In some cases the developer needs to be explicit and indicate this; but more often than not the compiler can take advantage of move-aware objects and perform insanely efficient optimizations that were never possible before. The good news is that enabling move semantics for your own classes is straightforward. Just as copying relies on a copy constructor and a copy assignment operator, move semantics relies on a move constructor and a move assignment operator:

unique_handle(unique_handle && other) throw() :
  m_value(other.release())
{
}
unique_handle & operator=(unique_handle && other) throw()
{
  reset(other.release());
  return *this;
}

The rvalue Reference

C++ 2011 introduces a new kind of reference, called an rvalue reference. It’s declared using &&; this is what’s being used in the unique_handle members in the preceding code. Although similar to references of old, now called lvalue references, the new rvalue references exhibit somewhat different rules when it comes to initialization and overload resolution. For now, I’ll leave it at that (I’ll return to this topic later). The main benefit at this stage of a handle with move semantics is that you can correctly and efficiently store handles in STL containers.

Error Handling

That’s it for the unique_handle class template. The final topic this month—and to prepare for the columns ahead—is error handling. We could debate endlessly about the pros and cons of exceptions versus error codes, but if you want to embrace the standard C++ libraries you’ll just have to get used to exceptions. Of course, the Windows API uses error codes, so a compromise is needed.

My approach to error handling is to do as little as possible, and write exception-safe code but avoid catching exceptions. If there are no exception handlers, Windows will automatically generate an error report that includes a minidump of the crash that you can debug postmortem. Throw exceptions only when unexpected runtime errors occur and handle everything else with error codes. When an exception is thrown, you know it’s either a bug in your code or some catastrophe that’s befallen the computer.

The example I like to give is that of accessing the Windows Registry. Failing to write a value to the Registry is usually a symptom of a bigger problem that will be hard to handle sensibly in your program. This should result in an exception. Failing to read a value from the Registry, however, should be anticipated and handled gracefully. This shouldn’t result in an exception, but return a bool or enum value indicating whether or why the value couldn’t be read.

The Windows API is not particularly consistent with its error handling; that’s the result of an API that’s evolved over many years. For the most part, the errors are returned either as BOOL or HRESULT values. There are some others, which I tend to handle explicitly by comparing the return value against documented values.

If I decide a given function call must succeed for my program to continue functioning reliably, I use one of the functions listed in Figure 2 to check the return value.

Figure 2 Checking Return Value

inline void check_bool(BOOL result)
{
  if (!result)
  {
    throw check_failed(GetLastError());
  }
}
inline void check_bool(bool result)
{
  if (!result)
  {
    throw check_failed(GetLastError());
  }
}
inline void check_hr(HRESULT result)
{
  if (S_OK != result)
  {
    throw check_failed(result);
  }
}
template <typename T>
void check(T expected, T actual)
{
  if (expected != actual)
  {
    throw check_failed(0);
  }
}

There are two things worth mentioning about these functions. The first is that the check_bool function is overloaded so that you can also check the validity of a handle object, which rightly does not allow implicit conversion to BOOL. The second is the check_hr function, which explicitly compares against S_OK rather than using the more common SUCCEEDED macro. This avoids silently accepting other dubious success codes such as S_FALSE, which is almost never what the developer expects.

My first attempt at writing these check functions was a set of overloads. But as I used them in various projects, I realized that the Windows API simply defines far too many result types and macros, so that creating a set of overloads that would work for all of them is simply not possible. Hence the decorated function names. I found a few cases where errors were not being caught due to unexpected overload resolution. The check_failed type being thrown is quite simple:

struct check_failed
{
  explicit check_failed(long result) :
    error(result)
  {
  }
  long error;
};

I could decorate it with all kinds of fancy features, like adding support for error messages, but what’s the point? I include the error value so that I can easily pick it out when performing an autopsy on a crashed application. Beyond that, it’s just going to get in the way.

Given these check functions, I can create an event object and signal it, throwing an exception if something goes wrong:

handle h(CreateEvent( ... ));
check_bool(h);
check_bool(SetEvent(h.get()));

Exception Handling

The other issue with exception handling concerns efficiency. Again, developers are divided, but more often than not because they hold some presupposition not based in reality.

The cost of exception handling arises in two areas. The first is throwing exceptions. This tends to be slower than using error codes, and is one of the reasons you should only throw exceptions when a fatal error occurs. If all goes well, you’ll never pay this price.

The second, and more common, cause of performance problems has to do with the runtime overhead of ensuring that the appropriate destructors are called, in the unlikely event an exception is thrown. Code is needed to keep track of which destructors need to be executed; of course, this also increases the size of the stack, which in large code bases can significantly affect performance. Note that you pay this cost whether or not an exception is actually thrown, so minimizing this is essential to ensure good performance.

That means ensuring that the compiler has a good idea of what functions can potentially throw exceptions. If the compiler can prove that there won’t be any exceptions from certain functions, it can optimize the code it generates to define and manage the stack. This is why I decorated the entire handle class template and traits class member functions with the exception specification. Although deprecated in C++ 2011, it’s an important platform-specific optimization.

That’s it for this month. You now have one of the key ingredients for writing reliable programs using the Windows API. Join me next month as I begin to explore the Windows Thread Pool API.        


Kenny Kerr is a software craftsman with a passion for native Windows development. Reach him at kennykerr.ca.