Medical Imaging Interaction Toolkit  2024.06.00
Medical Imaging Interaction Toolkit
The Resources System

The C++ Micro Services library provides a generic resources system to embed arbitrary files into a module's shared library (the size limitation per resource is 2GB, due to the used ZIP format).

The following features are supported:

  • Embed arbitrary data into shared or static modules or executables.
  • Data is embedded in a compressed format (zip) with a configurable compression level.
  • 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.

The following conventions and limitations apply:

  • Resource entries are stored with case-insensitive names. On case-sensitive file systemes, 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 module 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 a the resource container.

Embedding Resources in a %Module

Resources are embedded into a module's shared or static library (or into an executable) by using the usResourceCompiler executable. It will create a ZIP archive of all input files and can append it to the module file.

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. Otherwise, you also need to make sure that the set of static modules linked into a shared module or executable is also in the input file list of your usResourceCompiler call for that shared module or executable.

Here is a full example creating a module and embedding resource data:

set(module_name "org_me_mymodule")
set(srcs mymodule.cpp)
usFunctionGenerateModuleInit(srcs)
usFunctionGetResourceSource(TARGET mymodule OUT srcs)
add_library(mymodule ${srcs})
target_link_libraries(mymodule CppMicroServices)
set_property(TARGET mymodule APPEND PROPERTY COMPILE_DEFINITIONS US_MODULE_NAME=${module_name})
set_property(TARGET mymodule PROPERTY US_MODULE_NAME ${module_name})
set_property(TARGET mymodule PROPERTY FOLDER "${MITK_ROOT_FOLDER}/CppMicroServices/Snippets")
usFunctionEmbedResources(TARGET mymodule
FILES hello.txt
)

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 = ModuleRegistry::GetLoadedModules();
// 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.

Runtime overhead

The resources system has the following runtime characteristics:

  • During static initialization of a module, it's ZIP archive header data (if available) is parsed and stored in memory.
  • Querying Module or ModuleResource objects for resource information will not extract the embedded resource data and hence only has minimal runtime and memory overhead.
  • Creating a ModuleResourceStream object will allocate memory for the uncompressed resource data and inflate it. The memory will be free'ed after the ModuleResourceStream object is destroyed.