This is the second article in a new series about C++ for Windows. Last time I defined the approach for dealing with run-time errors. The next topic that must be addressed is the way in which objects defined by the Windows API are handled, so to speak.
The Windows API exposes a dizzying array of resources, devices, algorithms, and other primitives and abstractions. For the most part all of these are treated as objects at some level of the system. Many are kernel objects, meaning they are defined by a block of memory inside the kernel and exposed only indirectly to applications in user-mode. Others live entirely in your application’s address space but usually have some indirect association with one or more kernel objects. Regardless of how and where they are implemented they tend to be exposed to developers either as handles or COM-style interface pointers. In this article I’m going to discuss handles. I’ll deal with interface pointers in an upcoming article.
Think of a handle as an opaque value that uniquely identifies an object within a process. Although handles are usually defined as pointers you can’t manipulate them directly. You certainly shouldn’t try to dereference or increment them. The Windows API provides functions for creating and manipulating the objects behind the handles. I will discuss various types of objects in future articles but for the purpose of introducing objects and handles I’m going to use the relatively simple event kernel object to illustrate how objects and handles are managed.
Events are typically used to signal to a waiting thread that it can resume execution. Since it is a kernel object, an event includes a security descriptor and usage count. The security descriptor describes who is and is not allowed access to the object as well as what operations those users are allowed to perform. The functions that create kernel objects accept a security descriptor but you can simply pass a null pointer for this argument and default security will be applied. The usage count tells the kernel, who actually owns the object, how many outstanding handles refer to the object. In this way the kernel can ensure that a particular object is not destroyed until all of the processes that had access to it have stopped using it.
An event object also includes two Boolean values indicating whether the event is automatically or manually reset as well as whether it is currently signaled. When an auto-reset event is signaled the kernel allows exactly one waiting thread to resume and automatically resets the event. If no threads are waiting on the event then it remains signaled until such time as a thread attempts to wait on it. When a manual-reset event is signaled the kernel allows any and all waiting threads to resume. The event remains signaled until it is manually reset.
Here is how you might create an event using the CreateEvent function:
HANDLE h = CreateEvent(nullptr, // default security
true, // manual reset
false, // initially not signaled
nullptr); // not named
check(nullptr != h);
If the function fails it returns a null pointer in which case you can call GetLastError for more information. The CloseHandle function instructs the kernel that you are finished with the event. Assuming that there are no other open handles the kernel is free to destroy it.
An event is signaled using the SetEvent function.
It can also be manually reset using the ResetEvent function.
Finally, a thread can wait for an event to become signaled using the WaitForSingleObject function. This function returns WAIT_OBJECT_0 if the event is signaled or WAIT_TIMEOUT if the specified timeout elapses before the event is signaled. Technically it can also return WAIT_ABANDONED but that only applies to mutex kernel objects that are shared across processes. The following wait_for_signal helper function comes in handy when waiting for an event to signal.
bool wait_for_signal(HANDLE event,
DWORD timeout = INFINITE)
auto result = WaitForSingleObject(event,
if (WAIT_OBJECT_0 == result)
else if (WAIT_TIMEOUT == result)
If you want to wait as long as it takes then simply use it as follows.
Alternatively you may want to simply know whether an event is signaled. In that case just use a zero timeout.
bool signaled = wait_for_signal(h, 0);
Keep in mind that if the handle refers to an auto-reset event then wait_for_signal will cause the event to be reset if it happens to be signaled.
And that’s it for today. Next time I’ll show you how you can safely manage handles in C++ whether on the stack or within containers while getting the compiler to do all of the work for you. Isn’t that what it’s for?