Safe handles are the step ahead for programmers who needs to create private memory structures that could be
referenced by a unique value: the handle resembling OS handles behaviour.
The name is involuntary the same as the .NET class for OS handles management, but this library is intended for API programming or, more general, for those products that need to create a safe reference to blocks of data.
This process is tipically deployed using as handle the base address of the memory reference block. While this technique is efficient for simple programs it has many limits when used in complex applications where the memory or data corruption is an issue.
The simple use of memory address have many issues:
- The user have a memory address that can be used to access and eventually corrupt the data voluntarily or unvoluntarily.
- If the handle is destroyed and data released the user that retained a memory pointer can still try to access or reuse the data, leading to memory access exceptions or more dangerous corruption of other data.
- Two subsequent allocations can give back same memory pointers, the effect is that different handles can share the same value while data is different.
- For the reason of latter point the comparison of handles is not practical because they can have same value, but be a completely different data object. The use of a 'unique' key garantee that two successive handles, even if using same table slot, are different (unless up to key field rollover).
- The data is exposed., If the handle manage sensible data no privacy is allowed.
Safe handles uses a table of corrispondence handle->data memory pointer, so the handle is an 'handle' not a
memory address. The size of the table is determined by the number of bits used for entries SAFEHANDLEENTRIESBITS .
You can change it depending on how many entries you want. Max allowed value is 16 bits on a 32 bits machine. On 64 bits systems this limit can be as bigger as 32 bits.
Safe handles can also carry additional information about the type of data if required. A specific field of the handle is dedicated to this job, its size can be user defined.
Safe handles have a 'uniqe' key associated that give supplementar protection against use of invalid handles. The key is an autoincrementing counter that increase its value for each new handle created. Because the counter will rollover when max count is reached the probability that two handles have the same value depends on the number ok key bits. The number of bits used for the key is the remainder of the machine bit size (32 or 64) and the bits already used for the entries index and the type field. More entries and more the bits reserved for the type field narrowest is the key field and less safe is the unique key that repeats more often.
The use of Safe handles give the following advantages:
- Safe data reference with internal consistency check of the handle.
- Obfuscation of real data.
- Protection against data corruption and unwanted access.
- Type checking enforcement (SHANDLE type against a void pointer).
- Handle duplication.
- Debug support.
- Thread access limitation if required.
- Partial garbage collection on thread basis.
How SafeHandles works
A safe handle is an hashing like method to address data blocks, normally generated dinamically, avoiding memory conflicts and data corruption.
Safehandles permits to define a type field that is encoded in the handle allowing distinction of data type just looking at the handle (something like GDI objects).
SafeHandles creates a memory table that stores key/data pairs. The size of the table is user defined. SafeHandles have 3 main parts:
| Unique key | Type | Table Ptr |
The layout above is for a 32bits machine having up to 256 handles (8 bits), 16 types (4 bits) and 20bits of key.
If SAFEHANDLEMEMSAFE symbol is defined the handle will always have the MSBit set to address invalid memory. This prevents the use of handle as memory address triggering a memory exception if used as a memory pointer preventing any erroneous access to the real data.
The key changes for each new handle created,, and wraps around when the max count for key bits is reached. This feature add safety when handles are reused. It permits to make handles comparison and protect code from usage of expired or destroyed handles. The number of bits used for the unique key should be safe enaugh for many applications. However the compiler will issue a message to classify the key enforcement in strong and weak. An handle is considered weak if the number of key bits is less than 8 times the table size.
Withstanding all these characteristics the access to the data is very fast and sanity check is strightforward :-)
To modify the default building of SafeHandles you can define preprocessor symbols values:
- SAFEHANDLEENTRIESBITS Set to the number of bits to be used for table indexing. Max value is 16 for 32bits machines and 32 for 64 bits machines.
- SAFEHANDLETYPESBITS You can reserve bits to identify type of handle in programs where different objects are referenced. By default the library don't use types and type bits=0. Max value 8 for 32bits machines and 16 for 64 bits machines.
- SAFEHANDLEMEMSAFE Define this symbol to 1 to protect from invalid use of handle as memory pointer triggering invalid access. Each generated handle will have the MSBit set. In this case using a safe handle as an address will trigger a memory access exception. The cost is the loss of one bit on unique key.
- SAFEHANDLEDYNMEM Define this variable to 1 to create table in dynamic memory on library initialization. This is the preferred option when working with large tables.
SafeHandles are thread safe, the core code is protected under a critical section to obtain exclusive access to the internal structures.
Errors are handled in mutithreaded way.
As experimental feature SafeHandles can take care of dead data on thread basis scanning the table and automatically removing the handles owned by ended threads.
Some advanced functions are available to interblock handles access between different threads.
When the symbol SAFEHANDLEKILLDEADDATA is defined it enables the control code, running on a private thread,, that continuosly scans the handles table for entries created by dead threads. If the handle is thread_local (flag SH_FLAG_THREADLOCAL set) and the thread that created the handle is no more running the handle and related data is automatically released acting like a kind of garbage collector.
Safe handles can have properties that can be set using the handle flags. Actually there are 3 flags available:
- SH_FLAG_NODUP The handle cannot be duplicated. Trying to duplicate it you'll get a SH_ERR_NOTALLOWED error.
- SH_FLAG_THREADLOCAL The handle is local to the thread that created it. Other threads cannot access it.
- SH_FLAG_INHERITABLE The handle give limited access by threads different from creator (not implemented yet).
Properties can be set on creation using SH_CreateHandleEx, or later using SH_SetHandleFlags function.
Library sources includes a test program to check SafeHandles working.
The package is ready to be compiled with PellesC suite, the use with different compilers is strightforward because the library is in a single source file with two headers, one for library configuration and the other for inclusion of SafeHandles library in user code.
The use of library is documented in the sources and ready for DoxyGen.
This is the first implementation and some issues are already known.
The handling of release of root duplicated handle is slow because the field that points to root handle in child handles have to be upgraded all along. A faster approach, to be implemented if large use of duplication is required, is to replace the hRoot field with a pointer to hRoot in a secondary table. In this case when the root is removed you don't need to scan the whole chain, but simply update the table entry.
Ihave correct some minor and not minor bugs. The new version is here now.