Constant-time, String-less, Runtime GetType in C++
A clReflect type is represented by an object of type clcpp::Type stored in the generated database and loaded on startup. At the heart of clReflect are two functions for retrieving type information at runtime:
const clcpp::Type* type = clcpp::GetType<MyType>(); unsigned int type_hash = clcpp::GetTypeNameHash<MyType>();
In a typical C++ Reflection API, such functions can be slow, requiring database searches or the embedding of string data in the executable file. This somewhat discourages their use and can discard a lot of runtime benefits of reflection.
clReflect has a couple of key advantages here:
- The implementations of GetType and GetTypeNameHash are constant-time. You can verify this for yourself by looking at their source in the main header file; all they do is return a single pointer and a single integer with no lookup.
- There are no strings used or embedded in the generated executable. New implementations of GetType and GetTypeNameHash are generated by your compiler, along with a 4-byte static variable when used with a new type. The mapping is then done at compile-time using the template parameter.
- You only need to forward declare a type in order to get a pointer to its reflected type object. There is no need to include the header file which defines it.
The type pointer returned is bound to the database you loaded in the current module and the hash is not bound to any database.
These functions are specified as no-inline so that they are embedded in your module where clExport can pickup and store their addresses. These are recorded in the exported database and are then inspected at runtime when the database is loaded.
Each type has its own implementation of the functions and thus the static variables that they use. The database loader partially disassembles the function implementations, finds the address of the variables that they use and patches them with whatever values are stored in the database.
When the GetType and GetTypeNameHash functions are patched, a search is performed for specific mov instructions, and when found, the value at the address calculated from the instruction is read. If the value equals a known identifier, this is the location to patch.
This works on all supported platforms, however some platforms use Address Space Layout Randomisation. The process still works here as long as a base address is provided during database load (the default implementation handles this case for you).