📜 ⬆️ ⬇️

Introduction to Beautiful Capi, C ++ wrapper creation tool for C ++ libraries

Beautiful Capi is a tool that facilitates the creation of dynamic libraries in C ++ with an external interface in C language. This tool also generates C ++ wrappers for this C interface. Beautiful Capi is written in Python 3.


The main headache of C ++ library developers in the absence of a single ABI standard. Different compilers have different ABI, naming conventions, exception catching schemes, etc. Therefore, C ++ programmers have to take the library source code every time and build it with the correct compiler.


This is good if the library is popular, and for it the good uncle has already uploaded binary files for most C ++ compilers. Again, for most compilers. There are quite a few C ++ compilers, and if you take into account different versions of the same compiler that have an incompatible ABI, then the probability that the library you have already compiled will not work is quite high. Plus, add to this the various compiler settings that affect binary compatibility.


The Beautiful Capi tool offers a recipe for this problem. A dynamic library should have only a C interface outside. The interface should use only those types whose size is strictly fixed, for example, int32_t , uint8_t , etc. Also, do not forget about the convention call functions. The C interface can also be used directly if a C programmer decides to use your library. To use the library by C ++ programmers, there are C ++ wrappers that Beautiful Capi automatically generates. In plans generation of wrappers and for other languages, such as C # (.NET), Java and Python.


The Beautiful Capi tool does not parse C ++ source codes, as does a similar Swig system. You yourself must provide the tool with a description of the public library API. The library's public API is defined in XML format and is a set of descriptions of C ++ classes, methods, simple functions, enumerators, grouped by namespaces. It is planned to add non-XML description formats for the public API.


Another major problem is heap memory manager. The implementation of such a manager used in the C ++ library may differ from the implementation in the client application. As a result, an attempt to free a block of memory allocated in the C ++ library in a client application will result in the crash of this application. The tool guarantees the creation and deletion of each class instance with the help of the C ++ C ++ library manager.


The Beautiful Capi project is open source and licensed under the GNU GPL license. However, this does not interfere with its use in proprietary projects, because Beautiful Capi is, in fact, an external tool, such as git or any other.


Hello World!


Consider the example of the first compiler-independent C ++ library. The HelloWorld namespace has a class called PrinterImpl. The class has a single Show () method that performs the cherished actions.


Actually, the PrinterImpl class itself:


namespace HelloWorld { class PrinterImpl { public: void Show() const { std::cout << "Hello Beautiful World!" << std::endl; } }; } 

For the Beautiful Capi tool, you need to create the following XML file:


 <?xml version="1.0" encoding="utf-8" ?> <hello_world:api xmlns:hello_world="http://gkmsoft.ru/beautifulcapi" project_name="HelloWorld"> <namespace name="HelloWorld"> <class name="Printer" implementation_class_name="HelloWorld::PrinterImpl" implementation_class_header="PrinterImpl.h" lifecycle="copy_semantic"> <constructor name="Default"/> <method name="Show" const="true"/> </class> </namespace> </hello_world:api> 

I hope everything is clear enough here. Despite the fact that our implementation class is called PrinterImpl , we decided that its public name would simply be called Printer . The attributes implementation_class_name and implementation_class_header specify the name of the implementation class and the name of the header file in which its description is available. The lifecycle attribute specifies the script for the life cycle of objects. I will say in advance that at the moment there are three types of semantics of the life cycle: copy_semantic , reference_counted and raw_pointer_semantic . Copy semantics means that the implementation class will be copied each time the corresponding C ++ wrapper class is copied.


Let us show which C functions will be generated. The AutoGenWrap.cpp file generated using the Beautiful Capi tool must be included in the library:


 void* hello_world_printer_default() { return new HelloWorld::PrinterImpl(); } void hello_world_printer_show_const(void* object_pointer) { const HelloWorld::PrinterImpl* self = static_cast<HelloWorld::PrinterImpl*>(object_pointer); self->Show(); } void* hello_world_printer_copy(void* object_pointer) { return new HelloWorld::PrinterImpl(*static_cast<HelloWorld::PrinterImpl*>(object_pointer)); } void hello_world_printer_delete(void* object_pointer) { delete static_cast<HelloWorld::PrinterImpl*>(object_pointer); } 

For clarity, all conventions for calling functions and extern "C" keywords have been omitted. As you can see, the implicit argument this is the first argument of type void *.


The hello_world_printer_default function has no arguments, it creates a implementation object in the heap, and returns a pointer to it as a pointer to void. The hello_world_printer_show_const function inside itself simply calls the Show () method. The hello_world_printer_copy function copies the implementation object and returns a pointer to its copy. The hello_world_printer_delete function deletes the implementation object.


C ++ generated wrapper, Printer.h file:


 namespace HelloWorld { class Printer { public: Printer() { SetObject(hello_world_printer_default()); } void Show() const { hello_world_printer_show_const(GetRawPointer()); } Printer(const Printer& other) { if (other.GetRawPointer()) { SetObject(hello_world_printer_copy(other.GetRawPointer())); } else { SetObject(0); } } ~Printer() { if (GetRawPointer()) { hello_world_printer_delete(GetRawPointer()); SetObject(0); } } void* GetRawPointer() const { return mObject; } protected: void SetObject(void* object_pointer) { mObject = object_pointer; } void* mObject; }; } 

Code on the client side:


 #include "HelloWorld.h" int main() { HelloWorld::Printer printer; printer.Show(); return EXIT_SUCCESS; } 

The result of the program:


 Hello Beautiful World! 

True cross-compilation


It is worth noting one feature of creating dynamic libraries using the Microsoft Visual C ++ compiler (and not only) on the Microsoft Windows operating system. By default, the static library some_name.lib is created for any dynamic library some_name.dll , which is already linked to library clients. But the problem is that there are two incompatible format .lib files, one from Microsoft, and the other from the now defunct company Borland. And if we want to use our dynamic library in a client that uses, for example, the MinGW GCC compiler, we will need to use a third-party utility to convert .lib files, or refuse to use .lib files. Fortunately, the Beautiful Capi tool allows you to use a dynamic loader, which allows you to completely abandon the static libraries:


 #include <iostream> #include <cstdlib> #define HELLOWORLD_CAPI_USE_DYNAMIC_LOADER #define HELLOWORLD_CAPI_DEFINE_FUNCTION_POINTERS #include "HelloWorld.h" int main() { try { #ifdef _WIN32 HelloWorld::Initialization module_init("hello_world.dll"); #elif __APPLE__ HelloWorld::Initialization module_init("libhello_world.dylib"); #else HelloWorld::Initialization module_init("libhello_world.so"); #endif HelloWorld::Printer printer; printer.Show(); } catch (const std::exception& exception) { std::cout << "Exception: " << exception.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; } 

If the hello_world.dll dynamic library is not available, an exception of the type std :: runtime_error is thrown with a description of the error.


I have successfully tested this example using the Cygwin Clang compiler, the binary file of the dynamic library was created using the Microsoft Visual C ++ 2015 compiler.


Conclusion


In this short article I did not consider such important aspects as the organization of interception and exception handling, the implementation of interfaces on the client side, support for C ++ templates, and many others that are implemented in the Beautiful Capi tool.


Also, a comparative analysis of the solution based on Beautiful Capi and other tools that can accomplish the task, namely, the Swig wrapping system, Microsoft COM technology and its cross-platform counterparts, was not given.


However, this article provides an overview of the Beautiful Capi tool and the tasks it solves. The number and quality of reviews will encourage the author to write a sequel, or supplement this article.


Links


  1. Beautiful Capi tool
  2. Binary Application Interface, ABI
  3. Free tool for linking Swig programs and libraries

')

Source: https://habr.com/ru/post/340770/


All Articles