Plugin API
Introduction for Core Developers
This section provides guidance for OpenCPN core developers who are creating or extending the plugin API. Unlike other parts of the developer manual that focus on plugin developers using the API, this section covers best practices, design principles, and implementation considerations for those maintaining and evolving the API itself.
The plugin API serves as the contract between OpenCPN core and its plugins. Any changes to this API must be carefully considered to maintain backward compatibility while enabling new features. As a core developer, your decisions about the plugin API will affect both the stability of existing plugins and the capabilities available to future plugin authors.
API Design Principles
Maintaining Stable ABI
-
Changes to the plugin API must preserve both source (API) and binary (ABI) compatibility.
-
Plugins compiled against an older version of the API should continue to work with newer versions of OpenCPN.
API Documentation
-
Document all functions, classes, and parameters.
-
Include usage examples for complex features.
Sharing Content Between Plugins and Core
Create dedicated classes for shared content
-
When plugins and the core application need to share data structures (either unidirectionally or bidirectionally), create a specific class to encapsulate this shared content.
-
Example:
PlugIn_Waypointfor sharing waypoint data between core and plugins. -
These classes provide clear boundaries and interfaces for data exchange.
API Stability and Compatibility
Never modify published classes
-
Once a new OpenCPN version has been published along with a new version of the plugin API, do not:
-
Add new fields to existing classes
-
Remove fields from existing classes
-
Reorder fields within classes
-
Change the meaning or interpretation of existing fields
-
-
Any of these changes could break binary compatibility with existing plugins.
Use virtual destructors in all API classes Very little of the Open * Always include a virtual destructor in base classes:
+
virtual ~PlugIn_Waypoint();
-
This ensures that when a derived object is deleted through a base pointer, the correct (derived) destructor will be called.
-
Without a virtual destructor, deleting a derived class through a base pointer will only call the base class destructor, potentially causing memory leaks.
-
This becomes a critical issue when introducing class inheritance in future releases, even if it seems unnecessary in the initial implementation.
Avoid passing classes by value
-
Do not pass or return API classes by value. Instead, use pointers or references:
// Good bool UpdateSingleWaypoint(const PlugIn_Waypoint& waypoint); // Bad bool UpdateSingleWaypoint(PlugIn_Waypoint waypoint); -
Passing by value may work initially, but becomes a serious problem in future releases when derived classes are added - passing them to a function expecting the base class will cause slicing. This would force developers to create new functions such as
UpdateSingleWaypointV2, causing the plugin API to be bloated. -
A key consideration is that new fields cannot be added to the base class after the plugin API has been published. Changing a class would break ABI compatibility.
-
Example of slicing problem:
void ProcessWaypoint(PlugIn_Waypoint wp) { /* uses only base class members */ } PlugIn_WaypointV2 myPoint; myPoint.scamax = 1000000; // V2-specific field ProcessWaypoint(myPoint); // The scamax value is lost in the function!
API Evolution
Up to API 1.20 (OpenCPN 5.12) all API methods and types was in in global namespace.
Because of this various methods has been applied to make updated symbols unique,
usually by adding suffixes like _ex, _exV2, etc.
At the time of writing all these symbols are frozen and will not be changed.
From API 1.21 all api symbols are in separate namespaces, one for each level. As for 1.21 here is a class like
class HostApi121 : public HostApi {
...
}
All methods, types , etc are inside this class and thus in a separate namespace.
For this reason there is no need to use suffixes and such to separate methods.
HostApi::Foo() is totally different from HostApi122::Foo() anyway.
};
Memory Management
Clear ownership semantics
-
Define clear ownership rules for every object passed through the API
-
Whenever possible use
std::unique_ptr<>orstd::shared_ptr<>smart pointers to clarify ownership for both compiler and human readers- -
If smart pointers cannot be used: document whether the receiver takes ownership of passed objects