Carmenta Engine C++ API
The Carmenta Engine C++ API consists of:
Header files for all the objects in Carmenta Engine.
Shared libraries (DLLs and import libraries on Windows).
A base class for a GUI control, similar to MapControl classes in the .NET API. The control class is intended to be subclassed as a proper control in whatever GUI framework the application is using. Subclasses for use with Qt 5 on Windows and Linux are also provided.
Documentation showing how the API should be used (this document), plus reference documentation for all the C++ classes.
Samples that show you how to use the C++ API.
A natvis file for Visual Studio 2019 and later. This will simplify debugging in Visual Studio by providing native visualizers for many of the Carmenta Engine C++ types. A copy of this file, CarmentaEngine5.natvis, will always be available in the Carmenta Engine's SDK installation directory. The installer will also create copies in %USERPROFILE%\Documents\Visual Studio 2019\Visualizers and %USERPROFILE%\Documents\Visual Studio 2022\Visualizers if these directories exist, to automatically enable these visualizers in Visual Studio 2019 and 2022.
A project needs to use precompiled headers to fully use native visualizers. Otherwise, only types declared and used in the project will be fully visible.
To build and run C++ applications for Linux on Windows, using WSL, some packages located in the linux folder in the Carmenta Engine SDK directory, typically /mnt/c/Program Files/Carmenta Engine 5 SDK/linux, must be installed.
apt install ./carmenta-engine_*_amd64.deb ./carmenta-engine-dev_*_amd64.deb
Architecture
The C++ API has been carefully designed to expose as little implementation details as possible. The classes only have a single data member - a generic pointer that points to a private implementation class. By also avoiding unnecessary virtual methods, the API can easily be extended in future versions, without breaking source or binary compatibility.
The class hierarchy of the C++ API closely corresponds to the Carmenta Engine object model seen in the other APIs and in Carmenta Studio. All names in the API reside in the Carmenta::Engine C++ namespace.
There are a few different types of classes in the API:
Static classes are classes with only static methods, and are never instantiated. A good example is the Runtime class, which contains static methods for initialization and shutdown of Carmenta Engine.
Reference classes are those that inherit from the EngineObject class. Each instance of these classes internally holds a reference to a Carmenta Engine kernel object. Accessing properties or calling methods affect the kernel object directly.
Reference classes are allocated on the heap, using the new operator. However, they should always be used together with the smart pointer template class Ptr (not available in C#). This template implements reference counting of the API objects, and will automatically delete these when the application releases its references. The application should never explicitly delete any objects. The kernel objects are also reference counted and will also be deleted when all references are released.Collection classes are also reference classes, and hold references to collection instances in the kernel. Adding or removing objects to a collection directly modifies the kernel object.
Value classes all have their own copy of their data, like the Point class.
Callback interfaces. Some classes may perform callbacks back to application code, using callback interfaces, for instance the custom operator and custom dataset classes. The application creates objects which implement the corresponding callback interfaces, and registers these with the API classes. The interfaces aren't actually pure interfaces, but have some basic functionality to support reference counting of the instances.
In addition to the callback interfaces, some classes may define delegates and events, like in the .NET API. A "delegate" is merely a typedef of a function prototype, and an "event" is two methods in the class to register and unregister event handlers.
Carmenta Engine types should in general not be used as base classes. The following types are exceptions to this rule:
All built-in tools, for example StandardTool.
All custom object interfaces.
Using the C++ API
Header files
To access the functions and classes in the C++ API, you must first include the proper header files. Each module in Carmenta Engine has a main header file that includes all other necessary headers for that module. For instance, if your application uses the Core, Operators and DataSets modules, you should include the following headers:
#include <Carmenta/Engine/Core.hpp>
#include <Carmenta/Engine/Operators.hpp>
#include <Carmenta/Engine/DataSets.hpp>
Initialization and cleanup
Before calling any other Carmenta Engine code, your application must first call Runtime.Initialize.
Before terminating the application, you should release all references to any Carmenta Engine objects, and then call Runtime.Shutdown.
Object creation and destruction
Objects in Carmenta Engine can be created from the configuration file or using the C++ API.
Reference objects are created on the heap using the new() operator, and referenced using smart pointers. For each reference class, there is a corresponding typedef for the smart pointer. For instance, to create a new View object, you might do something like this:
DrawablePtr drawable = ...
LayerCollectionPtr layers = ...
ViewPtr myView = new View(drawable, layers);
As long as the smart pointers are used for all reference types, all objects will be deleted automatically; the application should never have to explicitly delete any objects.
Value classes are typically created on the stack, as local variables:
Point p0;
Point p1(10.0, 20.9);
Collections
Collections in the C++ API are reference objects, and should be managed with smart pointers. They hold references to collection instances in the kernel; modifying the API collection affects the kernel object directly. As an example, the following code will add a layer to the layer collection of a view:
myView->layers()->add(aNewLayer);
Since the collection classes are designed to work with private implementation classes, the C++ standard library collections like std::vector cannot be used in the API. However, the Carmenta Engine collection classes provide some extra functionality that make it easy to use the standard collection classes and algorithms with Carmenta Engine:
First, the Carmenta Engine collections provides bi-directional iterators similar to the standard collections. This makes it easy to copy the elements in a Carmenta Engine collection to a standard collection. For example:
// Copy to std::vector
LayerCollectionPtr layers = myView->layers();
std::vector<LayerPtr> stdLayers(layers->begin(), layer->end());
Secondly, all constructors and methods taking collections as input parameters have overloads taking iterator pairs instead which makes it easy to copy the contents of a standard collection into a Carmenta Engine collection. For instance, the example above that creates a View can also be written as:
DrawablePtr drawable = ...
std::list<LayerPtr> layers = ...
ViewPtr myView = new View(drawable, layers.begin(), layers.end());
The iterators also make it simple to traverse the elements in a collection, especially with the C++ 11 for loop syntax. Since the collection is held by a smart pointer, note that you need to dereference it explicitly:
LayerCollectionPtr layers = myView->layers();
// Classic traversal
for (LayerCollection::const_iterator iter = layers->begin(), end = layers->end(); iter != end; ++iter)
{
LayerPtr l = *iter;
l->name(); // Do something with the layer
}
// The same as above, using the much shorter C++ 11 syntax
for (LayerPtr l : *layers)
l->name(); // Do something with the layer
Strings and Unicode
Strings in the C++ API are implemented by the String (not available in C#) class. This class has implicit constructors from const char * and const wchar_t *, which makes them very easy to use. Wherever a string parameter is expected, you can simply pass a char or wchar_t pointer. When string are used as return values, call String.C_str (not available in C#) or String.C_wstr (not available in C#) to get the string contents:
// Set the name of a layer, using wide strings or current code page
LayerPtr layer = ...
std::string name("name");
std::wstring wname("name");
layer.name(name.c_str());
layer.name(wname.c_str());
layer.name("name");
layer.name(L"name");
// Retrieve the name as a wide string or in the current code page
std::string n(layer.name().c_str());
std::wstring wn(layer.name().c_wstr());
Internally all strings are stored in Unicode. The const char * methods converts the Unicode string to or from the current code page of the application.
To use the wchar_t methods with Visual Studio on Windows, you should make sure that the compiler treats wchar_t as a built-in type, rather than a typedef to an unsigned short (Project settings -> Configuration Properties -> C/C++ -> Language -> Treat wchar_t as Built-in type). This is the default. If this is set to a different value you may get linking errors.
Error handling
If something goes wrong when the Carmenta Engine C++ API accesses the Carmenta Engine kernel, an exception is thrown. There is only one exception class, EngineException, with a single message() method that returns the error message.
When using the MapControl, EngineExceptions can occur as a result of MapControl accessing the Carmenta Engine kernel. It is possible to get them through events instead of exceptions by overriding MapControl.OnError (not available in C#).
There are also errors that can occur anytime due to asynchronous code started by the kernel. Read more about these here: Runtime.Error.
Compiling, linking and running
To compile a source file with Visual Studio on Windows using the Carmenta Engine C++ API, you need to add the include path for the Carmenta Engine header files to the Visual Studio project. The Carmenta Engine SDK installer defines an environment variable for this purpose, CARMENTA_ENGINE_5_INCLUDE.
To link the application, you should add the directory where all the import libraries are installed to the library search path in the project. Another environment variable, CARMENTA_ENGINE_5_LIBS64 (64 bit) or CARMENTA_ENGINE_5_LIBS (32 bit), should point to this directory. On Windows, the header files contains pragmas to automatically add the necessary library dependencies, you do not have to add these manually.
Event handling
Event handling in the C++ API works by event handlers calling registered delegate functions for their events. Delegates are simply functions that matches the type definition of the specified event.
To use an event handler, you should write a function that matches the delegate (the type definition) of the event. The type definition usually contains a reference to the object that sent the event and the event arguments, as well as a void pointer containing some user-specified data. This data can be of any type, and should be cast back to its original type in the function.
Since each event handler delegate has a specific type, class functions cannot be used. Instead, use a static class function and pass in the instantiated class object as the data parameter.
For example, to integrate the CreateTool.FeatureCreated event to the class MyClass, first declare the event handler function:
class MyClass
{
// ... class definitions ...
static void CARMENTA_ENGINE_STDCALL myFeatureCreatedHandler(
const Carmenta::Engine::EngineObjectPtr& sender,
const Carmenta::Engine::FeatureCreatedEventArgs& e,
void* data);
}
Somewhere in the class, add the CreateTool.FeatureCreated handler function to the CreateTool:
MemoryDataSetPtr myMemoryDataSet = ...
CreateToolPtr myCreateTool = new Carmenta::Engine::CreateTool(myMemoryDataSet);
myCreateTool->addFeatureCreatedHandler(myFeatureCreatedHandler, this /* will be available in the data parameter */);
In the definition of the event handler function, cast the void pointer back to the original type to access the object:
void CARMENTA_ENGINE_STDCALL MyClass::myFeatureCreatedHandler(
const Carmenta::Engine::EngineObjectPtr&,
const Carmenta::Engine::FeatureCreatedEventArgs&,
void* data)
{
auto myData = static_cast<MyClass*>(data);
// use myData to perform an action
}
Sample projects
There are several C++ samples available from the Programming Samples page.