cppmicroservices - Man Page

CppMicroServices Documentation

The Resource System

The C++ Micro Services library provides a generic resource system that allows you to:

The features and limitations of the resource system are described in more detail in the following sections.

Embedding Resources in a Bundle

Resources are embedded into a bundle’s shared or static library (or into an executable) by using the usResourceCompiler3 executable. It will create a ZIP archive of all input files and can append it to the bundle file with a configurable compression level. See usResourceCompiler3 for the command line reference.

Accessing Resources at Runtime

Each bundle provides individual resource lookup and access to its embedded resources via the Bundle class which provides methods returning BundleResource objects. The BundleResource class provides a high-level API for accessing resource information and traversing the resource tree.

The BundleResourceStream class provides a std::istream compatible object for the seamless usage of embedded resource data in third-party libraries.

Resources are managed in a tree hierarchy, modeling the original parent-child relationship on the file-system.

The following example shows how to retrieve a resource from each currently installed bundle whose path is specified by a bundle property:

    // Check if a bundle defines a "service-component" property
    // and use its value to retrieve an embedded resource containing
    // a component description.
    for (auto const& bundle : bundleCtx.GetBundles())
    {
        if (bundle.GetState() == Bundle::STATE_UNINSTALLED)
            continue;
        auto headers = bundle.GetHeaders();
        auto iter = headers.find("service-component");
        std::string componentPath = (iter == headers.end()) ? std::string() : iter->second.ToString();
        if (!componentPath.empty())
        {
            BundleResource componentResource = bundle.GetResource(componentPath);
            if (!componentResource.IsValid() || componentResource.IsDir())
                continue;

            // Create a std::istream compatible object and parse the
            // component description.
            BundleResourceStream resStream(componentResource);
            parseComponentDefinition(resStream);
        }
    }

This example could be enhanced to dynamically react to bundles being started and stopped, making use of the popular extender pattern from OSGi.

Runtime Overhead

The resource system has the following runtime characteristics:

  • During bundle install, the bundle’s ZIP archive header data (if available) is parsed and stored in memory.
  • Querying Bundle or BundleResource objects for resource information will not extract the embedded resource data and hence only has minimal runtime and memory overhead.
  • Creating a BundleResourceStream object will allocate memory for the uncompressed resource data and inflate it. The memory will be free’ed after the BundleResourceStream object is destroyed.

Conventions and Limitations

  • Resources have a size limitation of 2GB due to the use of the ZIP format.
  • Resource entries are stored with case-insensitive names. On case-sensitive file systems, adding resources with the same name but different capitalization will lead to an error.
  • Looking up resources by name at runtime is case sensitive.
  • The CppMicroServices library will search for a valid zip file inside a shared library, starting from the end of the file. If other zip files are embedded in the bundle as well (e.g. as an additional resource embedded via the Windows RC compiler or using other techniques), it will stop at the first valid zip file and use it as the resource container.

Bundle Properties

A C++ Micro Services Bundle provides meta-data in the form of so-called properties about itself. Properties are key - value pairs where the key is of type std::string and the value of type Any. The following properties are always set by the C++ Micro Services library and cannot be altered by the bundle author:

Bundle authors must always add the following property to their bundle’s manifest.json file:

C++ Micro Services will not install any bundle which doesn’t contain a valid ‘bundle.symbolic_name’ property in its manifest.json file.

Bundle authors can add custom properties by providing a manifest.json file, embedded as a top-level resource into the bundle (see The Resource System). The root value of the JSON file must be a JSON object. An example manifest.json file would be:

{
  "bundle.symbolic_name" : "my bundle",
  "bundle.version" : "1.0.2",
  "bundle.description" : "This bundle provides an awesome service",
  "authors" : [ "John Doe", "Douglas Reynolds", "Daniel Cannady" ],
  "rating" : 5
}

All JSON member names of the root object will be available as property keys in the bundle containing the manifest.json file. The C++ Micro Services library specifies the following standard keys for re-use in manifest.json files:

NOTE:

Some of the properties mentioned above may also be accessed via dedicated methods in the Bundle class, e.g. GetSymbolicName() or GetVersion().

ATTENTION:

Despite JSON being a case-sensitive format, C++ Micro Services stores bundle properties as case-insensitive to accommodate LDAP queries using LDAPFilter and LDAPProp. Either keep the JSON case-insensitive or standardize on a convention to ensure queries return the correct results.

When parsing the manifest.json file, the JSON types are mapped to C++ types and stored in instances of the Any class. The mapping is as follows:

JSONC++ (Any)
objectstd::map
arraystd::vector
stringstd::string
numberint or double
truebool
falsebool
nullAny()

Service Hooks

The CppMicroServices library implements the Service Hook Service Specification Version 1.1 from OSGi Core Release 5 for C++. Below is a summary of the concept - consult the OSGi specifications for more details.

Service hooks provide mechanisms for bundle writers to closely interact with the CppMicroServices service registry. These mechanisms are not intended for use by application bundles but rather by bundles in need of hooking into the service registry and modifying the behaviour of application bundles.

Some example use cases for service hooks include:

Event Listener Hook

A bundle can intercept events being delivered to other bundles by registering a ServiceEventListenerHook object as a service. The CppMicroServices library will send all service events to all the registered hooks using the reversed ordering of their ServiceReference objects.

Note that event listener hooks are called after the event was created, but before it is filtered by the optional filter expression of the service listeners. Therefore, an event listener hook receives all SERVICE_REGISTERED, SERVICE_MODIFIED, SERVICE_UNREGISTERING, and SERVICE_MODIFIED_ENDMATCH events regardless of the presence of a service listener filter. It may then remove bundles or specific service listeners from the ServiceEventListenerHook::ShrinkableMapType object passed to the ServiceEventListenerHook::Event() method to hide service events.

Implementers of the Event Listener Hook must ensure that bundles continue to see a consistent set of service events.

Find Hook

Find Hook objects registered using the ServiceFindHook interface will be called when bundles look up service references via the BundleContext::GetServiceReference() or BundleContext::GetServiceReferences() methods. The order in which the CppMicroServices library calls the find hooks is the reverse operator< ordering of their ServiceReference objects. The hooks may remove service references from the ShrinkableVector object passed to the ServiceFindHook::Find() method to hide services from specific bundles.

Listener Hook

The CppMicroServices API provides information about the registration, unregistration, and modification of services. However, it does not directly allow the introspection of bundles to get information about what services a bundle is waiting for.

Bundles may need to wait for a service to arrive (via a registered service listener) before performing their functions. Listener Hooks provide a mechanism to get informed about all existing, newly registered, and removed service listeners.

A Listener Hook object registered using the ServiceListenerHook interface will be notified about service listeners by being passed ServiceListenerHook::ListenerInfo objects. Each ListenerInfo object is related to the registration / unregistration cycle of a specific service listener. That is, registering the same service listener again (even with a different filter) will automatically unregister the previous registration and the newly registered service listener is related to a different ListenerInfo object. ListenerInfo objects can be stored in unordered containers and compared with each other- for example, to match ServiceListenerHook::Added() and ServiceListenerHook::Removed() calls.

The Listener Hooks are called synchronously in the same order of their registration. However, in rare cases the removal of a service listener may be reported before its corresponding addition. To handle this case, the ListenerInfo::IsRemoved() method is provided which can be used in the ServiceListenerHook::Added() method to detect a delivery that is out of order. A simple strategy is to ignore removed events without corresponding added events and ignore added events where the ListenerInfo object is already removed:

class MyServiceListenerHook : public ServiceListenerHook
{
  private:
    class Tracked
    {
        // Do some work during construction and destruction
    };

    std::unordered_map<ListenerInfo, Tracked> tracked;

  public:
    void
    Added(std::vector<ListenerInfo> const& listeners)
    {
        for (std::vector<ListenerInfo>::const_iterator iter = listeners.begin(), endIter = listeners.end();
             iter != endIter;
             ++iter)
        {
            // Lock the tracked object for thread-safe access

            if (iter->IsRemoved())
                return;
            tracked.insert(std::make_pair(*iter, Tracked()));
        }
    }

    void
    Removed(std::vector<ListenerInfo> const& listeners)
    {
        for (std::vector<ListenerInfo>::const_iterator iter = listeners.begin(), endIter = listeners.end();
             iter != endIter;
             ++iter)
        {
            // Lock the tracked object for thread-safe access

            // If we got a corresponding "Added" event before, the Tracked
            // destructor will do some cleanup...
            tracked.erase(*iter);
        }
    }
};

Architectural Notes

Ordinary Services

All service hooks are treated as ordinary services. If the CppMicroServices library uses them, their Service References will show that the CppMicroServices bundles are using them, and if a hook is a Service Factory, then the actual instance will be properly created.

The only speciality of the service hooks is that the CppMicroServices library does not use them for the hooks themselves. That is, the Service Event and Service Find Hooks cannot be used to hide the services from the CppMicroServices library.

Ordering

The hooks are very sensitive to ordering because they interact directly with the service registry. In general, implementers of the hooks must be aware that other bundles can be started before or after the bundle which provides the hooks. To ensure early registration of the hooks, they should be registered within the BundleActivator::Start() method of the program executable.

Multi Threading

All hooks must be thread-safe because the hooks can be called at any time. All hook methods must be re-entrant, as they can be entered at any time and in rare cases in the wrong order. The CppMicroServices library calls all hook methods synchronously, but the calls might be triggered from any user thread interacting with the CppMicroServices API. The CppMicroServices API can be called from any of the hook methods, but implementers must be careful to not hold any lock while calling CppMicroServices methods.

Static Bundles

The normal and most flexible way to add a CppMicroServices bundle to an application is to compile it into a shared library using the BundleContext::InstallBundles() function at runtime.

However, bundles can be linked statically to your application or shared library. This makes the deployment of your application less error-prone and in the case of a complete static build, also minimizes its binary size and start-up time. However, in order to add new functionality to your application, you must rebuild and redistribute it.

Creating Static Bundles

Static bundles are written just like shared bundles - there are no differences in the usage of the CppMicroServices API or the provided preprocessor macros.

Using Static Bundles

Static bundles can be used (imported) in shared or other static libraries, or in the executable itself. For every static bundle you would like to import, you need to add a call to CPPMICROSERVICES_IMPORT_BUNDLE or to CPPMICROSERVICES_INITIALIZE_STATIC_BUNDLE (if the bundle does not provide an activator) in the source code of the importing library.

NOTE:

While you can link static bundles to other static bundles, you will still need to import all of the static bundles into the final executable to ensure proper initialization.

The two main usage scenarios- using a shared or static CppMicroServices library- are explained in the sections below.

Using a Shared CppMicroServices Library

Building the CppMicroServices library as a shared library allows you to import static bundles into other shared or static bundles, or into the executable.

Example code for importing MyStaticBundle1 into another library or executable

#include "cppmicroservices/BundleImport.h"

CPPMICROSERVICES_IMPORT_BUNDLE(MyStaticBundle1)

Using a Static CppMicroServices Library

The CppMicroServices library can be built as a static library. In that case, creating shared bundles is not supported. If you create shared bundles that link a static version of the CppMicroServices library, the runtime behavior is undefined.

In this usage scenario, every bundle will be statically built and linked to an executable:

Static bundles and CppMicroServices library

#include "cppmicroservices/BundleImport.h"

#ifndef US_BUILD_SHARED_LIBS
CPPMICROSERVICES_INITIALIZE_STATIC_BUNDLE(system_bundle)
CPPMICROSERVICES_IMPORT_BUNDLE(MyStaticBundle2)
CPPMICROSERVICES_INITIALIZE_STATIC_BUNDLE(main)
#endif

Note that the first CPPMICROSERVICES_IMPORT_BUNDLE call imports the static CppMicroServices library. Next, the MyStaticBundle2 bundle is imported and finally, the executable itself is initialized (this is necessary if the executable itself is a C++ Micro Services bundle).

Author

CppMicroServices Team

Info

Feb 05, 2025 3.8.5 C++ Micro Services