To solve any software task, we decompose it into stages, iterations, approaches, etc. It is also true for the development of complex engineering products. Let us take a look at the key steps.
The basic requirements for a C3D Labs kernel wrapper are:
- Maintainability: any changes to the kernel should be automatically reflected in the wrapper
- Scalability: the wrapper should be expandable
- Cross-platform capability: the wrapper should support as many compilers and operating systems as the C3D kernel does.
The first two requirements should be met quickly and concurrently.
Another software development challenge is lowering the learning curve. For this, we can develop product documentation with tons of examples, provide advanced tech support, or make the software product compatible with more than one programming language. This approach expands the number of potential users.
Note that a C# wrapper (C++/CLI) is already available. The wrapper is compatible with Windows OS only. Also, its support is limited: there is no automated workflow for patches and improvements. This is why we have reinvented the kernel wrapper development strategy.
We have decided to switch to C-style wrappers. Such wrappers are compatible with both C and C++ applications. It also serves as an intermediate layer to create higher-level wrappers, for example, for C# using P/Invoke. It also enables building the kernel for both Windows and Linux.
Let us take a look at the development of a C-style wrapper for the C3D Labs geometric kernel library. The key steps are:
- Creating a C-style wrapper (a prototype without automation just to test its viability)
- C++ header files parsing to detect the language entities
- Developing wrapper generation rules
- Writing a C-wrapper generator
- Using the generator to build and assemble the kernel wrapper
In the first step, a C-style wrapper is developed with a limited set of files just to test the viability and to create the rules for the wrapper generator.
The second step involves the clang library. It parses the header file into an AST (abstract syntax tree) representation. The clang representation is saved as Python data structures (for the wrapper generator written in Python).
In the third stage, we build a set of rules for handling the C++ data structures.
The fourth step uses the parser output and the generator rules.
The generator works as follows:
- Loads a C++ header file
- Runs a parser and builds a data structure representation as Python entities
- Creates a template: a file with special functions for the library object (class, data structure) construction/destruction, and parent class access functions.
- Enumerating the entities provided by the parser, the generator processes the code according to the rules and generates the functions.
There are some limitations:
- functions with default values are converted into functions with explicit arguments
- for template classes, the generator selects specific implementations used in the API of the wrapped library
- all methods and functions overridden in the input header file are generated with their arguments listed in the wrapper function name.
In the fifth step, the generator builds the wrapper from the header files. Next, the resulting files and the implementation of the template classes are assembled into a single project to build the wrapper library. This step uses the CMake files to configure the build.
The result is a wrapper library that can be used to develop a higher wrapper layer (C#, Python), or as a standalone product. It simplifies the maintenance and updates since the wrapper is generated from the modified kernel code and automatically includes all the changes.
We are going to automate the C-style wrapper building process (i.e., as a new main library release is published), generate a C# wrapper based on the C-style wrapper, and publish the wrappers for Linux and Windows.
Software Engineer, С3D Labs