Handles are a common part of programming on the MS Windows platform. We use handles for processes, threads, windows, GDI resources, etc. Handles are important because they provide a way for application developers to manipulate objects owned by the operating system or by other processes. If you have ever written a custom control, started a process or thread, or created an event object, you have worked with handles.
When working with multi-threaded and even multi-process applications, it can be useful to use this same mechanism to control access to objects in memory. Using handles instead of actual memory pointers can improve the durability of a system by preventing access to non-owned memory and helping to insure that no memory is leaked from the system.
In 2001, I needed to develop several NT Services for my company's product line. When I was designing these services, I identified numerous goals which they needed to meet. My goals were ambitious but attainable. The first three goals I list are typically a requirement for all NT Service type applications.
- High performance
- Highly durable
- Easy to maintain
- etc. (Many more product specific goals.)
These services can launch hundreds of threads and these threads need to share numerous resources many of which are not determined until runtime. As with all multi-threaded apps, I was concerned with avoiding dead-lock conditions or inefficiency problems. Also, I needed to keep the code simple so that it could be easily maintained and updated.
After some research, I decided to use a
HANDLE-object mapping system to map my run-time created objects to logical handles. This would allow me to abstract much of the housekeeping involved with synchronization and thereby simplify the code. By using handles, I could more easily obtain my second and third goals. Using handles, however, does add a layer of code which must be efficient so it does not interfere with the high performance goal.
In its simplest form, handle management consists of a map of handle values to memory locations. However, as with many problems, the simplest solution is often too simple. This simple case doesn't really make the code more maintainable and offers only limited durability. Therefore, I needed to develop a solution which would implement support for all the features needed to attain my goals.
Being a C++ developer, the obvious solution is to wrap handle management into a self contained class. By wrapping handle management into a class, I can reuse the class across all my projects as needed. Also, it is easier to hide the complexity of the solution from the rest of the application. My first step was to determine what the requirements of a handle management class would be.
- Must be thread safe. (Single process is OK)
- Must be fast.
- Must allow lookup by
HANDLE and reverse lookup of
HANDLE by object.
- Must consume as little memory as possible.
- Must support mapping all the various object types I need.
- Must enforce type compatibility rules for objects mapped to handles.
- Must be safe to run for long periods of time.
- Must produce unique handle values (an upper limit is acceptable but must be rare).
- Must support re-mapping of objects to handles.
- Must be easily customized to each application.
- Must support locking of handles and their associated objects.
- Provide diagnostic information on handle usage, object types, locks etc.
- Be able to work in MFC and non MFC environment.
This class exposes a complete interface for generating, maintaining and using handles. Because this class is so important to my applications, I took special care to insure that the code was well commented. You should be able to read the code fairly easily.
This class can be used "out-of-the-box" with no changes at all. In an ideal situation, you will probably want to customize it though a set of
#define statements provided for this purpose. Customizing the class is ideal because you can better control type-safety and a number of other options. I discuss these
#define statements later in this article.
There are nine functions exposed by this class as public methods. These are:
HMHANDLE GetHandle(HMOBJECTTYPE pObj, short sType)
Retrieves a handle value for the specified object. If a handle has not previously been assigned to the specified object, a handle will be generated and the new handle will be returned.
GetObject(HMHANDLE hHandle, HMOBJECTTYPEREF pObj, short sType)
Retrieves an object based on the handle specified. This function will insure that the object mapped internally is of the type you are requesting by
short RemoveObject(HMHANDLE hHandle, short sType)
Removes an object from the handle manager based on the
hHandle specified. This function verifies that the object mapped to
hHanlde is of type
sType. Objects cannot be removed if they are currently locked by a non-calling thread or another thread is waiting to obtain a lock on the object.
short RemapHandle(HMHANDLE hHandle, HMOBJECTTYPE pObj, short sType)
Remaps a handle so that it is mapped to the object specified by
pObj and is of type
sType. Objects cannot be remapped if they are currently locked by a non-calling thread or another thread is waiting to obtain a lock on the object.
bool LockHandle(HMHANDLE hHandle)
Obtains a lock on the handle specified and its mapped object. If the handle is already locked by another thread, this function will block until the other thread releases its lock. This function does not guarantee that another thread won't change the underlying object unless the code using this class follows the
UnlockHandle methodology. A single thread can call this function multiple times on a single handle safely.
bool UnlockHandle(HMHANDLE hHandle)
Releases a lock previously obtained by calling
LockHandle. This function must be called once for each call to
LockHandle. Failing to call
UnlockHandle will result in handles remaining locked and will cause a dead-lock condition if another thread attempts to lock the handle.
bool IsHandleLocked(HMHANDLE hHandle)
Determines whether or not a specified handle is currently locked by another thread. If the thread holding the lock calls this function, this function will return
false. The result of this function is not guaranteed to be valid as it is possible for a thread to acquire a lock between this function ending and the calling function acting upon the return value.
Blocks access to the handle manager for all threads except the calling thread until the
UnlockManager function is called. Be sure to call
UnlockManager for each call to
LockManager. This function is implemented to support some specific requirements I have in my system. Warning: Using this function can be dangerous especially if you subsequently call
LockHandle which might block your thread but would not release its hold on the manager. This would create a deadlock because the thread locking the desired handle would never be able to release it.
|Unblocks access to the handle manager. Should be called one time for each call to |
In addition to these nine exposed public methods, this class exposes five additional methods which provide support for maintaining usage statistics and determining the number of handles currently being managed. These are:
Returns the number of handles currently being managed by the handle manager.
void AttachUsageData(CHandleUsage* pData)
CHandleUsage object to the
CHandleManager class, and if the
HM_INCLUDE_DIAGNOSTICS diagnostics option is enabled, will cause the
CHandleManager class to begin maintaining detailed statistics on the handles it allocates, releases and locks.
CHandleUsage object attached with the
AttachUsageData function. This function should be called prior to destroying the
CHandleUsage object and prior to calling any of the
CHandleUsage member functions directly.
void CollectCurrentUsageData(CHandleUsage* pData)
Collects statistics on the currently managed handles and updates the
CHandleUsage class with that information.
How it Works
By this time, you are probably wondering how this class does what it does. This class defines a number of support classes. The most important of these is the
CHandleEntry class. Internally, the
CHandleManager class maintains two maps: a handle map and an object map. Whenever a handle is assigned to an object, an entry is made in each of these maps. The handle map is a map of
CHandleEntry values. The object map is a map of
One of the members of the
CHandleEntry class is a
m_sType. This is a value which is application defined and is used to match object types at runtime. When you call
GetObject, you pass in this value and it is maintained throughout the life of the handle. This value is cross-checked on each call subsequent to the first
GetHandle call where the
CHandleEntry object is created and a new
HMHANDLE value is assigned to the object.
RemoveObject will only succeed if the handle is mapped to the type specified in the call.
Whenever a call is made to
GetHandle and the object is not already associated with a handle, the class creates a
CHandleEntry class and populates it with the data supplied on the object along with a newly generated handle value. Then the
GetHandle function inserts an entry into the handle map and the object map objects. Subsequent calls to
GetHandle for a given object can be quickly resolved by performing a lookup on the object map. When the
GetObject function is called to resolve an object from a handle, the
GetObject function uses the handle map to lookup the associated
CHandleEntry, and from there it retrieves the object. This implementation requires more memory overall but provides better performance on lookups and object resolution requests.
Locking support provides a uniform method for locking objects maintained by the handle manager. Locking support does not provide absolute thread safety because in order for it to work, all threads accessing objects maintained by the handle manager must first lock the handle prior to accessing/modifying it. For my needs, this was an acceptable situation because all my code was designed to work with this class specifically.
CHandleManager defines another class (
CHandleEntryLockData) to assist with locking support. Each
CHandleEntry object can contain a pointer to an allocated
CHandleEntryLockData object. This class maintains several variables needed to insure that one and only one thread can lock a handle at a time. The most important member of this class is a
CRITICAL_SECTION object which is used to serialize access to the handle. The
CHandleEntryLockData object is only allocated when a lock is requested, and is deleted when the lock is released and no other threads are waiting on the handle. This reduces overall memory usage drastically.
How Handle Values are Generated
The base implementation of the
CHandleManager class defines a
HMHANDLE as a
long. The valid range for
HMHANDLE values is defined by the
HM_MAX_HANDLE_VALUE. By default, these are defined as 1000 and
CHandleManager class maintains a
HMHANDLE value (
m_lNextHandle) internally which represents the last generated handle value. This value is initialized to
HM_MIN_HANDLE_VALUE at class construction.
GetNextHandleValue is called, the class increments
m_lNextHandle until it reaches
HM_MAX_HANDLE_VALUE. When this happens, the class does one of two things. It either looks for the first unused handle value by starting from
HN_MIN_HANDLE_VALUE and incrementing it until it finds an unused handle, or if the
m_FreeHandleLog set contains free handle values, it uses a value from this set and then removes it before continuing. It is possible that the
m_FreeHandleLog set will be empty which is why the manual search mechanism is provided. Theoretically, this situation will never occur because it is unlikely that anyone will maintain two billion handles concurrently. The first situation, however, is possible as it is possible that two billion handles will be created and released over time.
When a handle is released, the released handle value is added to the
m_FreeHandleLog set until the
m_FreeHandleLog reaches a size of
HM_MAX_FREE_HANDLE_LOG_SIZE which defaults to 1000. By maintaining this set, we can quickly obtain unused handle values once the
m_lNextHandle value has reached
In addition to basic handle and locking support, the
CHandleManager class supports a standard mechanism for obtaining detailed usage statistics on the manager. These statistics include the # of handles generated, # released, # locked, as well as detailed information by the various object types. This information can be very useful in diagnosing the overall performance of an app as well as in identifying potential problems. It is also helpful information to have at runtime to monitor an application.
CHandleManager class defines another class (
CHandleUsage) which can be created externally from the
CHandleManager and then attached to a
CHandleManager object. While it is attached to the
CHandleManager class, it cannot be accessed externally but it maintains statistics on the various elements it supports. At any time, you can
Detach this object from the handle manager and optionally obtain current handle statistics by calling
For more detailed information on the
CHandleUsage class, see its implementation.
Using this Class in Your Application
First, you need to add the CHandleManager.h and CHandleManager.cpp files to your project.
Next, you need to decide how you want to use the class. One simple way to use the class is to instantiate a static instance of the class somewhere globally in your app. See example:
static CHandleManager m_HandleManager;
With this method, you can use one handle manager object throughout your app. Also, you can instantiate numerous copies of the class for various parts of your app. This is up to you and is determined by your design goals.
Once you have instantiated a copy of this class, you can begin using it immediately. See the examples below:
HMHANDLE hHandle = m_HandleManager.GetHandle(pSomeObject, MYTYPE_CMyClass);
CMyClass* pObject = NULL;
short sReturn = m_HandleManager.GetObject(hHandle,
if (sReturn == HM_NO_ERROR)
You may be wondering what the significance of the
sType value passed into the
RemapHandle functions is. This value is an application defined value which can be any valid
short value. This value can always be set to zero if you want, but ideally you will define a specific
short value for each type of object you want this handle manager to manage for you. By defining your own types, the class can insure type safety for you. For example, if you have three class objects you intend to have managed by this class, you will define three corresponding values for them to be mapped into the handle manager. See table below:
#define HM_CMyClass1 1
#define HM_CMyClass2 2
#define HM_CMyClass3 3
You may have noticed that the locking support provided by this class is pretty weak. Actually, the locking support is as strong as the code which uses this class. If your code uses the
UnlockHandle calls appropriately, you can safely assume that the object managed by a handle is locked. The reason I chose to use weak locking support was to allow for maximum flexibility in my use of this class. Some of my classes already had their own locking support in them and this additional locking was unnecessary.
Locking and CRITICAL_SECTION Objects
I am providing a more in-depth description of how locking support is implemented to answer any questions you might have about how it works.
Locking support is implemented by associating a
CHandleEntryLockData object with a
CHandleEntry object. When a lock is requested on a handle, the class first checks to see if a
CHandleEntryLockData object data has already been allocated for the handle. If no
CHandleEntryLockData object has been allocated, a new
CHandleEntryLockData object is created and associated with the
CHandleEntry object. The
CHandleEntryLockData class contains four member variables. These are defined here:
long value indicating the number of threads waiting to obtain a lock on the handle.
DWORD value indicating the thread ID of the thread owning the lock on the handle. This value is obtained by calling
long value indicating the number of times the thread owning the lock has called
LockHandle. This allows a single thread to call
UnlockHandle multiple times.
CRITICAL_SECTION object used to actually lock the handle and block threads while it is locked by another thread.
At this point, I should mention that function calls on the
CHandleManager class are serialized using a
CRITICAL_SECTION. This means that only one thread can be in a function body for this class at a time.
LockHandle is called, the handle manager class increments the
m_lWaitingForLockCount member variable of the
CHandleEntryLockData structure and then leaves the
CHandleManager critical section. It then calls
EnterCriticalSection on the
CRITICAL_SECTION member of the
CHandleEntryLockData object. (This will block the calling thread until it can enter the critical section and obtain a lock on the handle. Also, because I call
LeaveCriticalSection on the
CHandleManager critical section, other threads can continue to call the class and work with other handles and objects.) When the
EnterCriticalSection call returns, the function re-enters the
CHandleManager critical section and then decrements the
m_lWaitingForLockCount. Then it sets the
m_dwThreadWithLock member of the
CHandleEntryLockData object to
::GetCurrentThreadId(). Then it increments the
m_lLockCount value and returns from the function.
UnLockHandle is called, the
m_lLockCount value is decremented. If
m_lLockCount is zero then
UnLockHandle checks the
m_lWaitingForLockCount to determine if any threads are waiting to obtain a lock on the handle. If no threads are waiting,
LeaveCriticalSection is called, then the
CHandleEntryLockData object is destroyed. If there are threads waiting to obtain a lock on the handle,
LeaveCriticalSection is called, but the
CHandleEntryLockData object is not destroyed.
This mechanism simplifies development on other portions of the code because it minimizes the amount of threading related code I have to write elsewhere. By blocking the calling thread, I do not have to implement some other blocking mechanism at the thread code level. This mechanism has expedited my development cycle quite a bit.
By now, you may be wondering why I bothered to describe all of this. A question has been posed regarding the number of
CRITICAL_SECTION objects the handle manager can/will create. Basically, the handle manager will create one
CHandleEntryLockData object for each handle which is locked. (The
CHandleEntryLockData contains a
CRITICAL_SECTION member variable.) Under normal conditions, a
CRITICAL_SECTION object is just a structure like any other. All you need is enough memory to be able to use it. This means that the only limit on how many
CRITICAL_SECTION objects a process can create is limited only by the amount of available memory. Things get a little more complicated, however, when there is a lot of contention for a
CRITICAL_SECTION. When this happens, the system will create a semaphore to synchronize access to the
CRITICAL_SECTION. It does this to insure optimum performance and prevent any thread from being starved (I think this is right.). This is where a problem can occur. A process can only create up to 65,536 handles. A semaphore object is assigned a handle. This means that under heavy contention a semaphore is created for a lock. It is possible to run out of handles. (One thing I am not sure of is if the 65K limit is concurrent or total. From what I can tell, it is concurrent.)
The good news is, though, that it is highly unlikely that you will ever reach this limit.
CHandleEntryLockData objects are usually very short lived objects. They are only allocated for the length of time a lock is maintained. Also, since at any time there are only a relatively small number of locks being held, there are usually only a small number of
CHandleEntryLockData objects allocated. In my applications, I have never encountered a situation where more than 1000 handles were locked concurrently. Your application will have different requirements but even if you have 1000 threads running concurrently (something I wouldn't recommend) and each of those threads holds locks on 10 handles, you will have only 10000
CRITICAL_SECTION objects. Even if there was high contention for 50% of these, you would only have 5000 handles allocated concurrently for semaphores.
Locking and Deadlocks
There are drawbacks to the locking mechanism implemented in this class. One of these is that a deadlock condition is possible if two threads are attempting to lock the same handle concurrently. Example: Thread A locks handle 1, Thread B locks handle 2, then Thread A attempts to lock handle 2 and thread B attempts to lock handle 1. If this occurs, both threads will deadlock because they will forever wait for the other thread to release its lock. This situation is no different from what would typically happen in a multi-threaded application when two threads attempt this sequence of events (excluding code to deal with this situation). Another drawback is that maintenance programmers may find it difficult to understand the synchronization mechanism as it is not a typical solution (from what I have seen).
Having said that, as with all multi-threaded apps, it is important to properly design your app to catch, at the development/architectural level, issues like these and deal with them. It may be appropriate (depending on your situation) to only allocate handles for high-level objects in an object-hierarchy, not lower level ones. For example: Class
A contains three objects of Class
B. Only Class
A should be allocated a handle. If one of the Class
B objects is needed, a lock on Class
A should be obtained instead of a lock on the Class
B objects. In most situations, this will eliminate the potential deadlock condition because the condition in which two threads require the same object is limited.
In the future, I may implement support for a new locking model that would still block on calls to
LockHandle, but it would temporarily release the locks held by the blocked thread. This is not really a solution because although it would remove the deadlock condition, it would introduce a different class of issues and add complexity to the code. What it does provide, however, is more options in how to deal with the complexities of multi-threaded apps which is the primary goal of this class.
Currently, this class only supports one mode of locking. This is blocked access to handles. See the section above on Locking and
CRITICAL_SECTIONs for more detail on this. In the future, it would be possible to add a non-blocking mode of locking if needed. I have no plans to do this right now, but I may in the future. If anyone else is interested in taking on this task, please feel free.
You may be wondering how much memory this class utilizes. I have included fairly detailed comments in the code regarding the amount of memory needed by the class. The amount of memory needed is largely dependent on the number of handles allocated and the options enabled when the class was compiled. Below is an except from the HandleManger.h file.
As you can see from the examples, locking and diagnostic support increase the memory requirements of the class. A minimum build of the class will require just 100K for 10,000 concurrently allocated handles. Diagnostic support increases this to 220K. Locking support with no diagnostics requires 180K, and locking and diagnostic support requires 300K for that same 10,000 handles. That means as much as 200% memory overhead for locking and diagnostic support.
In my applications, I enable diagnostic and locking support. I don't have exact statistics (pretty good ones, though), but I would estimate that in the most heavily utilized service in which I use this class, I allocate up to 20,000 concurrent handles. This puts memory usage at its peak at around 600K assuming 10% of handles are locked at any given time. (In reality, the percentage of locks is probably closer to 5%.)
This issue has never been a problem for me, but I feel I should at least mention it here. (In one installation, one of the services utilizing this class has been running for over 60 days.) As I stated earlier in this article, this class allocates various objects at runtime. Some of these objects are allocated and then freed quite frequently. Over time, this could lead to memory fragmentation and degrade system performance. In the future, I plan to partially deal with this by pre-allocating these objects in large sets, and then instead of destroying the objects, returning them to this pre-allocated pool.
I do not fully understand the impact this will have on the system as I am not an expert on how Windows allocates and manages memory. I would love to hear from someone who understands this better, though.
One of the design goals of this class was that it could be compile-time customized to include or exclude various options to optimize feature/performance/memory usage as needed. These compile-time options are controlled by numerous
#define statements contained in the CHandleManager.h file. Below is a list of the
#define statements provided, what they mean and how you can customize them.
Determines whether or not diagnostic usage data is available. To disable usage data support, remark out this
#define statement in the .h file.
The standard .h file includes support for usage statistics.
Determines whether or not lock support is available. To disable lock support, remark out this
#define statement in the .h file.
The standard .h file includes support for locking.
Determines whether STL containers are to be used in-place of MFC containers. Basically, the class can use either an MFC
CMap class or a STL
std::map class for its internal map objects.
#define is not remarked out, the class should have no dependencies on MFC.
Note: My tests indicate that the MFC
CMap class outperforms the
std::map class for my common handle uses.
The standard .h file disables use of STL containers in preference of MFC containers.
CMap objects are being used (see above
#define), we initialize the
CMap object's hash table to a particular size. This value should be a prime number at least the estimated size of the maximum # of expected handles.
The standard .h file defines this value as 9973. My tests indicated that this value produces excellent performance results.
As mentioned earlier in this article, this class maintains a
std::set of released handle values. This set is maintained to optimize performance on retrieving handle values when all sequential handle values have been used up. To disable release-handle-logging, set this
#define to 0.
The standard .h file defines this value as 1000.
#define statement maps directly to a
long. This is the default (low-level) type of handle values. If you wish, you can easily change this value to a
ULONG to increase the number of available sequential handle values. If you do change this to
ULONG (or any other type), be sure to change the
#define accordingly (see below).
#define sets the lowest valid value for a handle. I reserve the first 1000 values for error/status values. This means that handle values should start at 1000. You can customize this value to as little as 11, but be warned, I may add additional status/error return values in the future.
#define sets the highest valid value for a handle. Typically, this should be the MAX value associated with the type used for
HMHANDLE. Typically this is
|The objects mapped to handles by the handle manager must be of a specific run-time type to work. The default .h file defines this object type as a |
void pointer. This means that any memory address whether it points to a C++ class, a
struct, or memory allocated with
malloc, etc., can be associated with a handle value. This may not be desirable for all implementations as it requires explicit type casting of object types to get a working value. By default, this
#define is set to
void*. To change it to a custom base class, set the
|Similar to above, but a reference to a pointer to an object. Typically defined as |
void*&. To change it to a custom base class, set the
A preprocessor macro named
HM_VALID is provided to simplify the validation of handle values in your applications. It is defined as
#define HM_VALID(hHandle) ((hHandle >= HM_MIN_HANDLE_VALUE && hHandle <= HM_MAX_HANDLE_VALUE)). You can use this macro in your code as you would use an
ASSERT macro. See example below:
HMHANDLE hHandle = SomeHandle;
When calling the
RemapHandle functions, you should expect
short return type. This value should be one of the
#define'd error return codes. (See table below.) Also, the
GetHandle function returns a
HMHANDLE type and can be one of the below codes as well as a valid handle value. You should check the return value for these functions to insure your call succeeded.
|The function call was successful.|
|The actual type of the object mapped to the handle does not match the type you specified.|
|The object pointer you specified is |
NULL. All object pointers must be non-
|An exception occurred within the function call.|
|The function was unable to allocate memory for the requested operation.|
|The handle specified is not a valid handle.|
|An unexpected logic error has occurred.|
|The object specified has not been mapped to a handle.|
|There are no more handles available to be assigned. This will occur if you call |
GetHandle and all handle values are being used. This is an unlikely condition.
|The handle you requested is currently locked by another thread. You cannot perform the requested operation until the lock is released.|
Future Improvements and/or Enhancements
There are numerous enhancements I would like to implement in this class:
- Utilize a Read/Write locking mechanism to allow multiple threads to concurrently request a handle and object values from the system. Write locking is only needed when creating and removing handle entries. Also, some code would have to be changed such as lock increments, etc.
- Add lock times to the statistics data to better understand how often objects are locked and for how long. I think this would include a minimum lock time, maximum lock time, and an average or mean lock time (or both).
- Implement support for automatically timing out handle objects. This would allow for periodic release of unused handles and their associated objects. This would need to support destroying the associated object as well as the internal data structures. Some mechanism would need to be created to enable the manager to destroy the associated objects.
- Implement support for out-of-memory persistence of handles and their associated objects. This would allow for data to be removed from physical memory and stored in a database or other long-term persistence medium. This mechanism would need to allow for these objects to be available even if the containing program is stopped and restarted.
- Implement a better (I think?) strategy for allocating the internal objects. Since tens of thousands of these objects are created during the life of the class, I think a potential memory fragmentation issue could be avoided. This may also impact performance. I will need to test the implementation to ensure that it works correctly and does not degrade performance. It should actually improve performance if implemented correctly.
- Implement an alternative locking strategy. (See the sections on locking support above, for more details).
I stripped this class from a running program. I had to make some minor changes to it to remove dependencies on my company's products. In the process of doing this, I may have introduced a bug unintentionally. I apologize in advance.
|May 5, 2002
Thanks to soptest for identifying an issue with the
UnlockHandle functions not returning
true correctly. When I stripped the code from a working app, I removed several lines of code including some from these functions. One was
bReturn = true;. This problem has been corrected.
soptest also mentioned that the # of
CRITICAL_SECTION objects the class creates was too many. I have added a section to the article (see above) on this topic to better explain the way the class handles these objects and why it is implemented this way.