C++ Micro Services: The Resources System
The Resources System

The C++ Micro Services library provides a generic resources system to embed arbitrary files into a module's shared library (the current size limitation is based on the largest source code file size your compiler can handle).

The following features are supported:

  • Embed arbitrary data into shared or static modules or executables.
  • Data is embedded in a compressed format if the size reduction exceeds a configurable threshold.
  • Resources are accessed via a Module instance, providing individual resource lookup and access for each module.
  • Resources are managed in a tree hierarchy, modeling the original child - parent relationship on the file-system.
  • The ModuleResource class provides a high-level API for accessing resource information and traversing the resource tree.
  • The ModuleResourceStream class provides an STL input stream derived class for the seamless usage of embedded resource data in third-party libraries.

Embedding Resources in a Module

Resources are embedded by compiling a source file generated by the usResourceCompiler executable into a module's shared or static library (or into an executable).

If you are using CMake, consider using the provided usFunctionEmbedResources CMake macro which handles the invocation of the usResourceCompiler executable and sets up the correct file dependencies.

Accessing Resources at Runtime

Each module provides access to its embedded resources via the Module class which provides methods returning ModuleResource objects. The ModuleResourceStream class provides a std::istream compatible object to access the resource contents.

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

// Get all loaded modules
std::vector<Module*> modules;
// Check if a module defines a "service-component" property
// and use its value to retrieve an embedded resource containing
// a component description.
for(std::size_t i = 0; i < modules.size(); ++i)
{
Module* const module = modules[i];
std::string componentPath = module->GetProperty("service-component").ToString();
if (!componentPath.empty())
{
ModuleResource componentResource = module->GetResource(componentPath);
if (!componentResource.IsValid() || componentResource.IsDir()) continue;
// Create a std::istream compatible object and parse the
// component description.
ModuleResourceStream resStream(componentResource);
parseComponentDefinition(resStream);
}
}

This example could be enhanced to dynamically react to modules being loaded and unloaded, making use of the popular "extender pattern" from OSGi.

Limitations

Currently, the system has the following limitations:

  • At most one file generated by the usResourceCompiler executable can be compiled into a module's shared library (you can work around this limitation by creating static modules and importing them).
  • The size of embedded resources is limited by the file size your compiler can handle. However, the file size is the sum of the size of all resources embedded into a module plus a small overhead.