Handle Management - Complete Solution






4.43/5 (8 votes)
A utility class to manage the mapping of objects to handles.
Introduction
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.
The Problem
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.
The Solution
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.
REQUIREMENTS
- Must be thread safe. (Single process is OK)
- Must be fast.
- Must allow lookup by
HANDLE
and reverse lookup ofHANDLE
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.
WISH LIST
- Provide diagnostic information on handle usage, object types, locks etc.
- Be able to work in MFC and non MFC environment.
Basic Usage
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:
GetHandle
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
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
sType
.RemoveObject
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 tohHanlde
is of typesType
. 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.RemapHandle
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 typesType
. 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.LockHandle
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
LockHandle
/UnlockHandle
methodology. A single thread can call this function multiple times on a single handle safely.UnlockHandle
bool UnlockHandle(HMHANDLE hHandle)
Releases a lock previously obtained by calling
LockHandle
. This function must be called once for each call toLockHandle
. Failing to callUnlockHandle
will result in handles remaining locked and will cause a dead-lock condition if another thread attempts to lock the handle.IsHandleLocked
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.LockManager
bool LockManager()
Blocks access to the handle manager for all threads except the calling thread until the
UnlockManager
function is called. Be sure to callUnlockManager
for each call toLockManager
. 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 callLockHandle
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.UnlockManager
Unblocks access to the handle manager. Should be called one time for each call to LockManager
.
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:
GetTotalCurrentHandles
long GetTotalCurrentHandles()
Returns the number of handles currently being managed by the handle manager.
AttachUsageData
void AttachUsageData(CHandleUsage* pData)
Attaches a
CHandleUsage
object to theCHandleManager
class, and if theHM_INCLUDE_DIAGNOSTICS
diagnostics option is enabled, will cause theCHandleManager
class to begin maintaining detailed statistics on the handles it allocates, releases and locks.DetachUsageData
CHandleUsage* DetachUsageData()
Detaches the
CHandleUsage
object attached with theAttachUsageData
function. This function should be called prior to destroying theCHandleUsage
object and prior to calling any of theCHandleUsage
member functions directly.CollectCurrentUsageData
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 HMHANDLE
to CHandleEntry
values. The object map is a map of HMOBJECTTYPE
to CHandleEntry
values.
One of the members of the CHandleEntry
class is a short
named m_sType
. This is a value which is application defined and is used to match object types at runtime. When you call GetHandle
, RemoveObject
, or 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. GetHandle
, GetObject
and 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_MIN_HANDLE_VALUE
and HM_MAX_HANDLE_VALUE
. By default, these are defined as 1000 and LONG_MAX
-1. The 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.
Each time 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 HM_MAX_HANDLE_VALUE
.
Performance Data
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.
The 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 CollectCurrentUsageData
.
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:
some.h file
static CHandleManager m_HandleManager;
some.cpp file CHandleManager CSomeClass::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:
// to get a handle associated with an object HMHANDLE hHandle = m_HandleManager.GetHandle(pSomeObject, MYTYPE_CMyClass); ASSERT(HM_VALID(hHandle)); // to get the object associated with a handle // first, lock a handle. if (m_HandleManager.LockHandle(hHandle)) { CMyClass* pObject = NULL; short sReturn = m_HandleManager.GetObject(hHandle, (HMOBJECTTYPEREF)pObject, MYTYPE_CMyClass); if (sReturn == HM_NO_ERROR) { } m_HandleManager.UnlockHandle(hHandle); }
sType?
You may be wondering what the significance of the sType
value passed into the GetHandle
, GetObject
, RemoveHandle
, 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:
CMyClass1
#define HM_CMyClass1 1
CMyClass2
#define HM_CMyClass2 2
CMyClass3
#define HM_CMyClass3 3
...
Weak Locking
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 LockHandle
/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:
m_lWaitingForLockCount
A long
value indicating the number of threads waiting to obtain a lock on the handle.m_dwThreadWithLock
A DWORD
value indicating the thread ID of the thread owning the lock on the handle. This value is obtained by callingGetCurrentThreadId
.m_lLockCount
A long
value indicating the number of times the thread owning the lock has calledLockHandle
. This allows a single thread to callLockHandle
andUnlockHandle
multiple times.m_crsLock
A 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.
When 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.
When 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.
Locking Options
Currently, this class only supports one mode of locking. This is blocked access to handles. See the section above on Locking and CRITICAL_SECTION
s 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.
Memory Usage
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.
// EXAMPLE: // NO LOCKING SUPPORT // Concurrent handle count=10000 // Memory w/diagnostics = 220,000 bytes // Memory w/o diagnostics = 100,000 bytes // // WITH LOCKING SUPPORT // Concurrent handle count=10000 // Handles with locks=1000 (10%) // Memory w/diagnostics = 300,000 bytes // Memory w/o diagnostics = 180,000 bytes
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%.)
Memory Fragmentation
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.
Customization
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.
HM_INCLUDE_DIAGNOSTICS
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.
HM_INCLUDE_LOCK_SUPPORT
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.
HM_USE_STL_CONTAINERS
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 STLstd::map
class for its internal map objects.If this
#define
is not remarked out, the class should have no dependencies on MFC.Note: My tests indicate that the MFC
CMap
class outperforms thestd::map
class for my common handle uses.The standard .h file disables use of STL containers in preference of MFC containers.
HM_DEFAULT_HASH_TABLE_SIZE
If MFC
CMap
objects are being used (see above#define
), we initialize theCMap
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.
HM_MAX_FREE_HANDLE_LOG_SIZE
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.
HMHANDLE
This #define
statement maps directly to along
. This is the default (low-level) type of handle values. If you wish, you can easily change this value to aULONG
to increase the number of available sequential handle values. If you do change this toULONG
(or any other type), be sure to change theHM_MAX_HANDLE_VALUE
#define
accordingly (see below).HM_MIN_HANDLE_VALUE
This #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.HM_MAX_HANDLE_VALUE
This #define
sets the highest valid value for a handle. Typically, this should be the MAX value associated with the type used forHMHANDLE
. Typically this isLONG_MAX-1
.HMOBJECTTYPE
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, astruct
, or memory allocated withnew
,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 tovoid*
. To change it to a custom base class, set the#define
toCMyClass*
.HMOBJECTTYPEREF
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#define
toCMyClass*&
.
Support Features
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; if (HM_VALID(hHandle)) { // TODO: Do something }
Error Codes
When calling the GetObject
, RemoveObject
and 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.
HM_NO_ERROR
The function call was successful. HM_TYPE_MISMATCH
The actual type of the object mapped to the handle does not match the type you specified. HM_OBJECT_IS_NULL
The object pointer you specified is NULL
. All object pointers must be non-NULL
.HM_INVALID_TYPE
Currently unused. HM_EXCEPTION
An exception occurred within the function call. HM_MEMORY
The function was unable to allocate memory for the requested operation. HM_INVALID_HANDLE
The handle specified is not a valid handle. HM_INTERNAL_ERROR
An unexpected logic error has occurred. HM_NOT_MAPPED_TO_HANDLE
The object specified has not been mapped to a handle. HM_NO_MORE_HANDLES
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.HM_LOCKED
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).
Special Notes
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.
Updates
May 5, 2002 |
Thanks to soptest for identifying an issue with the soptest also mentioned that the # of |