IMPORTANT UPDATE. READ BEFORE READING ARTICLEIt was my fault that there were some misunderstandings about these publications. So I decided to add this warning.
In this series of articles, I wanted to put more emphasis on the development history of a certain open source library, regardless of the specific cpprt. A story from writing source codes (with an emphasis on some interesting things that people find interesting to read in general, regardless of the library itself), to the formation of a repository (with a CMake lesson) and library promotion (where part of the promotion implies publishing this series of articles). Such a training demo project for people who were thinking of posting their open source, but were either afraid or did not know how.
')
Of course, I would not mind if the library somehow came to life and there is a minimal amount of library advertising in the articles (I tried to hide it under spoilers). But still, the objectives of this cycle I considered more educational and, I hope, applicable in general, without regard to my library.
Please take this into account when reading a series of articles.
This article is the third and final in a cycle on the development of the
cpp runtime library, which adds the ability to add some meta-information about C ++ classes and continue to use this information during program execution.
This article describes how the user can interact with the library and demonstrates the clean result of the work described in the first two articles of the cycle.
Links to all articles of the cycle
Link to library repository
What is stated in this article is true for commit
e9c34bb .
Project structure- Documentation ---
readme.md
Readme file It was conceived as a file, from which the familiarization with the project begins.
README.cmake
A file that tells how to build a library with CMake and / or additional assembly goals that come with it.
/ docs
Folder with the documentation ... Alas, the only thing in it now is - a link to this series of articles. I made a mistake, postponed the documentation for last, and I didn’t have the heart to do it after three weeks of working seven days a week on this series of articles.
- cpprt library files ---
/ include
A programming interface for accessing the cpprt library API.
/ src
The folder with the source code of the library.
- Build for popular systems ---
/ build
Ready projects for some popular toolchains and IDE.
/ lib
Prebuilt library builds for some popular compilers.
- Additional projects ---
/ examples
Folder with source code for examples ... At the moment, for one simple example demonstrating the principles of using the library.
/ tools
Folder with source code for tools supplied with the library.
- Build system ---
/ build
An empty folder in which it is recommended to build the library and / or additional assembly goals.
CMakeLists.txt
CMake configuration file.
- Other files ---
LICENCE.txt
The license file under which the project is distributed (MIT license).
copying.txt
The file with the template header for the source code files, which describes the license information.
Build a library and related materials
You can read about how the build system was created for the project in the second article of the cycle (
link ).
The recommended method of assembling the library itself and the accompanying materials involves the use of
CMake . If you do not have CMake installed, you can download it
from here .
To build via the command line, follow these steps:
1. Create somewhere where you want a folder within which you will be assembling. Go to this folder:
mkdir {your-build-folder}
cd {your-build-folder}
{your-build-folder} is the folder in which you will be building.
2. Call the command to generate the configuration:
cmake -G "{generator-name}" {options} {cpprt-folder-path}
{generator-name} is the name of the generator for your cloud.
{options} - a list of options for specifying what you want to collect.
More about optionsRegardless of the options selected, in the generated configs (IDE files) there will always be at least one build target — for the static build of the cpprt library itself. In order to generate configs for building additional accompanying projects, you can use the following options (the default value of the option is specified first):
-DBUILD_ALL = {OFF / ON}
If this option is set to ON, configurations for all accompanying projects delivered together by the library will be generated. Moreover, if the options for the assembly of specific assembly purposes.
-DBUILD_TOOLS = {OFF / ON}
If this option is set to ON, configurations will be generated for assembling the tools supplied with the library. At the moment there is one tool - console (more on it will be
further ).
-DBUILD_EXAMPLES = {OFF / ON}
If this option is set to ON, then configurations will be generated for assembling the examples supplied with the library. At the moment there is one example (simple_example) which describes the classic example for OOP example of the hierarchy of classes of animals.
Additional options:
-DCONSOLE_WITH_EXAMPLE = {ON / OFF}
If this option is set to ON (the default setting is set), then the source code of the console tool will contain files describing the test class hierarchies.
{cpprt-folder-path} is the folder that is the root of the cpprt clone repository.
After calling the {your-build-folder} folder, configuration files for your toolchain (or for your IDE) are generated. There is no need to configure anything further. It is enough just to start building the necessary assembly targets (projects for IDE).
Library features
Using the API provided by the cpprt library, a class can be
registered . If a class is registered, then the following opportunities open for it:
1. Creating a class object during program execution based on the class name in string representation.
2. Access to information on the registered heirs and ancestors of the class (to the extent the information transmitted during registration).
3. The ability to obtain information about the abstractness of classes.
4. The ability to get the string name of the class through the methods of its objects.
Below is a user program interface for obtaining meta-information about classes. Here private members of a class, implementations of methods and some other husk are omitted. Only the interface that interacts with the metadata of the registered classes:
Examples of using the API to interact with metadata:
1. Creating an object by the string name of its class:
ICPPRTManagedClass *theTestClassObject = cppRuntime().createObject(“TestClass”);
2. Printout of information about the abstractness of a class according to a pointer to an object of the base class:
void printObjectClassInformation(ICPPRTManagedClass *inObject) { const CPPRTRuntime::ClassData &theClassData = *inObject->getClassDataRT(); std::cout << “Class ” << theClassData.fullName() << “ is ” << theClassData.isInterface() ? “abstract” : “concrete” << std::endl; }
3. Listing of the names of all classes inherited from the class TestClass. In this case, the successors of the heirs are selected recursively, abstract classes are filtered, and the meta-information for the TestClass class itself is not added to the resulting list:
std::vector<CPPRTClassData *> theChildData; cppRuntime().observeChildren(TestClass::gClassData, theChildData, ObservingFlagWithoutInterface | ObservingFlagRecursive | ObservingFlagIgnoreBase); for (size_t theIndex = 0, theSize = theChildData.size(); theIndex < theSize; ++theIndex) { std::cout << theChildData[theIndex]->fullName() << std::endl; }
4. Listing the names of all classes without ancestors:
std::vector<CPPRTClassData *> theBasesData; cppRuntime().observeChildren(NULL, theBasesData); for (size_t theIndex = 0, theSize = theBasesData.size(); theIndex < theSize; ++theIndex) { std::cout << theBasesData [theIndex]->fullName() << std::endl; }
Class registration
Now let's look at what is needed to register classes:
1. The class to be registered must have an ICPPRTManagedClass class in its ancestors; it is enough that the base class of the user hierarchy inherits the ICPPRTManagedClass. Example:
2. A registered class must have methods and fields that are used by the cpprt library for work. In order not to write them manually, it is better to use macros for registration. There are two groups of macros: to declare the required fields and methods, and to implement.
Declaration CPPRT_DECLARATION - macro for the declaration. Takes one argument - the name of the class that is registered (not the string with the name, but the name itself). The code in which the macro is expanded ends with the protected access specifier. This is done to more easily include a macro in the existing code, because if you use a macro at the beginning of a class declaration, access for all subsequent declared members of the class will remain the same as without using a macro (as in C ++, before declaring another access specifier, the declared members classes have protected access). Example:
Implementation . The main part of the class meta-information is transmitted as part of the implementation macro. These macros are divided into groups depending on the number of base classes.
CPPRT_CLASS_IMPLEMENTATION_BASE_ {N} (C, B1, ..., BN)
CPPRT_INTERFACE_IMPLEMENTATION_BASE_ {N} (C, B1, ..., BN)
N is the number of base classes.
C - the full name of the class for which the implementation is described.
B1 ...
BN are the full names of the base classes. These classes must be registered under cpprt.
The macro with CLASS is used for registering classes whose objects can be created, and with INTERFACE for registering abstract classes. Example:
Actually, everything. The things described are sufficient for registering a class within cpprt. You can see a live example within the repository (the build target is
simple_example , the -DBUILD_EXAMPLES = {OFF / ON} option to generate a project via CMake).
Here, too, I will give an example for registering a more complex class hierarchy, the class inheritance hierarchy for some typical shooter:
Player.h class Player { private: IWeapon *_weapon;
Testdrive.cpp int main() { Player thePlayer; thePlayer1.setWeapon("Abakan"); return 0; }
Other features
In the framework of cpprt, it is not necessary to register all classes and you can omit all the heirs for the classes. This can be useful, for example, to hide part of the service interface in class inheritance. For example:
Console tool
Together with the library, in addition to the example, the tool comes in the delivery console (console). It provides several commands for creating objects by the string name of their classes (objects are stored in a test array), as well as for viewing inheritance hierarchies for registered classes. A short description of the commands supported by the console:
help
Command that, when called, prints out short information about all commands available to the console.
print (-c) ({full-class-name})
If this command is called without arguments, then it prints a list of all the objects created at the moment with an indication of their classes.
If the -c flag (optional flag) is passed, but the class name is not passed (optional parameter {full-class-name}), then this call prints the trees of the inherited classes for all registered classes that do not have base classes (root classes ).
If, in addition to the -c flag, the {full-class-name} parameter is given, which specifies the class name, then this command prints the tree of the registered heir classes for the class with the passed name.
create {class-name} {object-name}
A command that creates an object of a registered class {class-name} named {object-name} in a test array of objects.
delete {object-name}
A command that removes an object from a test array of objects by its name.
exit
The command to exit the console. Ends the execution of the console program.
Perhaps, I told everything about the possibility of using the library. If you want to know how all this is implemented - most of the techniques and tricks that were used in the library, I described in detail in the first article of the cycle (
link ). Her reading is enough to understand the principles of the library.
Future plans
1. Implement the ability to bypass the inheritance tree, not only from ancestors to heirs, but also vice versa.
Prototype std::vector<ClassData *> theParentsData; cppRuntime().observeParents(TestClass::gClassData, theParentsData, ObservingFlagWithoutInterface | ObservingFlagRecursive);
This feature is implemented elementarily within the framework of the current implementation - you need to make internal-methods to bypass the heirs of the template, with the Boolean argument ObserveChildren, depending on which true / false value to select the metadata of the heirs / base classes for the transferred metadata.
2. Establish normal data encapsulation for library classes. With the current library design, many unnecessary types and methods stick out (for example, the same CPPRTRuntime class) that clutter up the library user interface.
3. Make iterators to be able to bypass the meta-information tree for the classes. This will save data usage when recursively traversing the tree of heirs and allows the user to walk around the class inheritance tree without violating encapsulation.
Prototype CPPRTRuntime::iterator theIterator = cppRuntime().iteratorForClassData(TestClass::gClassData);
Alternatively, the iterator can use the visitor design pattern (for example, as done in
clang , here is a
great example of use ). This approach is generally often used to bypass tree data structures.
4. Make it possible to add user information to the classes - so that you can filter the classes according to this data upon request.
4. Make template support. This is a very nontrivial task. You need to think well about whether you really need (most likely, need) and how you can describe the API for using template-specific classes.
5. To think about the possibility of working with class metadata at runtime. For example, registering a class at runtime, or vice versa - deleting classes from the list of registered ones.
Prototype cppRuntime().createClassData(“MyRTClass”, new CPPRTRuntime::Fabric<MyRTClass>() ); . . .
In principle, this functionality is available now, but it’s rather a design bug than a feature ... In general, this feature is probably dangerous and you should think well if it is needed at all.
6.
Place for your suggestions . Suggest what else might be useful. Let's discuss.
The conclusion of the article and the conclusion of the cycle
On this, perhaps, you can put a bullet. I told everything I wanted about the cpprt library. I hope, dear reader, you have learned something useful from this series of articles. I would be glad even more if you right now took some library gathering dust without work on your home repository, designed it as it should and published it. Tell people about it in some of your articles!
Well, it will also be nice if someone, interested, will contribute to the development of the cpprt library.
About the mysterious main projectSo, it’s interesting what the “main project” is? .. If you are reading this, then the plan is a success.
The main project is a library that allows you to use data about the state of an object, transmitted for serialization, in order to generate meta-information about a given object and further use of this meta-information.
I note that this approach is not new - it is often used for game projects, and even some of the cpprt analogs mentioned in the first article allow the user to set such information for the class fields at the time of class registration. I will list here the implemented and planned features of the project, because of which, in my opinion, they should be dealt with:
1. The library does not only collect metadata about the states of objects, but the collected data is also used to generate a GUI through which you can influence the states of objects. Due to this, it is enough for the user of the library to describe the save and load methods for the object in order to get the
GUI object inspector automatically generated for it. At the moment, the GUI is made on Qt, but the code was designed with an eye to supporting different GUIs (including the user's GUI, which can be connected by implementing the interface). The plans to the bottom of the opportunity to try, opening up due to such a look at generating a GUI.
2. I am always a fan of the concept of middleware. I was impressed by the opportunity to find common features in a number of tools that have the same purpose, to bring this common thing into the interface, and then slip different tools under this interface, having the opportunity to choose the interface implementation ... So, it was conceived how to implement the concept of middleware. Due to this, I want to achieve maximum ease of integration by the library user into his project, up to the transfer of the user type system to the library template classes.
At the moment, the library is written to the state of the basic working prototype, but it requires expanding and deepening the functionality, and also requires bringing the project structure into a public form (adding documentation, examples, tests, tools, etc.).
I was hoping to cut this library myself, but at some point I realized that I was sewing up ... Join us, become co-founders of the project. If you are interested in any degree of everything that is written here - write in a personal, I will tell you more.
Let's bring together the library in a proper form, publish it, make a series of articles for PR ... You never know - it will suddenly take off, and someday we will remember how we laid together the beginning of a new milestone for creating a GUI within the C ++ language?
Thank you for reading all this! I hope I managed to say at least something useful!
The author thanks readers in advance for pointing out errors in the article, constructive advice and suggestions.
CaptionsEditing article: Sergey Semenyakin
Perezalil picture from bmp to jpg at the direction of
a1ien_n3t . Thanks to him.