Skip to content

b-com-software-basis/xpcf

Repository files navigation

XPCF Cross-Platform Component Framework

GitHub version LoC

XPCF is a lightweight cross platform component framework (it is similar to the well known COM model).

It is designed to provide dependency injection, with clarity, simplicity and light code base in mind.

It implements the abstractfactory and the factory design patterns (through the ComponentManager and ComponentFactory classes, and through IFactory interface).

It also provides a safe toolkit to avoid common c++ design issues such as :

  • ensure use of one unique shared_ptr type (even on platforms with several STLs : the SRef class is guaranteed to be from one source only through boost binary)
  • ensure correct memory handling (avoid memory leaks)
  • provide parallel helpers such as tasks, fifo, circular buffers, delays..
  • a common configuration design : configuring a component doesn't require to write serialization code

XPCF also provides remoting automation by generating grpc remoting code from XPCF interfaces, using a dedicated DSL (Domain Specific Language) - see XPCF remoting architecture chapter.

Changelog

Learn about the latest improvements.

Definitions

Module

A module consists in a shared library hosting a set of factories to create components/services

Provides:

  • introspection to figure out which components are available in the module

  • separate implementation from interface : creates the concrete implementation and binds it to the IComponentIntrospect interface

  • component creation

  • service instanciation

Component/Service interface

A component/service interface in the component framework context is :

  • based on C++ virtual class

  • uniquely identified by an UUID (128-bit number guaranteed to be unique based on timestamps, HW addresses, random seeds)

  • mandatory derives from IComponentIntrospect (the base interface of all XPCF interfaces)

IComponentIntrospect interface

  • is a special interface

  • does not derive from any other interface

Provides:

  • component introspection to query interfaces implemented by the component

  • functional reference counting

Special Notes about queryInterface :

  • queryInterface from interface A to interface B, must be able to queryInterface back from interface B to interface A

  • queryInterface from interface A to the IComponentIntrospect interface, must equal the queryInterface from interface B to the IComponentIntrospect interface

Components and Services

An implementation of one or more interfaces identified by a Component UUID

NOTE: The introspection interface is the same for both components and services

component

  • a class that can have several instances over the application lifetime

  • each instance is unloaded (destroyed) when no more reference exists upon one of its interfaces

service

  • a component singleton that only has one instance at any given time

  • is never unloaded

Schema

 ___________________________________________________
| Module #1                                      |
| entry point => XPCF_getComponent                  |
|           ______________________________          |
|          | Component #UUIDC1            |         |
|          |        ___________________   |         |
|          | I0 -> | I0 implementation |  |         |
|          |        -------------------   |         |
|          |                              |         |
|          |        ___________________   |         |
|          | I1 -> | I1 implementation |  |         |
|          |        -------------------   |         |
|           ------------------------------          |
|                                                   |
|           ______________________________          |
|          | Component #UUIDC2            |         |
|          |        ___________________   |         |
|          | I0 -> | I0 implementation |  |         |
|          |        -------------------   |         |
|          |                              |         |
|          |        ___________________   |         |
|          | I2 -> | I2 implementation |  |         |
|          |        -------------------   |         |
|           ------------------------------          |
|                                                   |
| entry point => XPCF_getService                    |
|           ______________________________          |
|          | Service #UUIDS1              |         |
|          |        ___________________   |         |
|          | I3 -> | I3 implementation |  |         |
|          |        -------------------   |         |
|           ------------------------------          |
 ---------------------------------------------------

Framework functionalities

This framework provides the following concepts :

  • an "in-code" dependency injection factory to create components and bind them to a specific interface

  • a component abstract factory to load components at runtime from modules (named ModuleManager)

  • increase modularity with better separation of concerns through interface discovery (using component introspection)

  • a centralized description of modules, components and interfaces (with a dedicated configuration file)

  • module introspection to figure out the components contained and the interfaces they implement

  • secure memory management using smart pointers for components (prevents from memory leaks, dangling null pointer)

  • component and service configuration at creation through abstract variant parameters

  • support for both components and services

  • support for Domain Specific Language through the "IFeatures" interface (for instance, a component can declare "opencv" and "cuda" features as it is an opencv/cuda accelerated implementation of a keypointdetector)

Note: Variant parameters for configuration are a set of parameters. Each parameter is a pair of type, value. Supported types include long, unsigned long, double, string or another subset of parameters. Each parameter has an access definition : in, out or inout.

Aliases

  • an \<aliases\> section can be declared in any xpcf-registry or xpcf-configuration node to declare named alias to uuids for an interface, a component or a module.
  • aliases can be used in <factory>...</factory> or <properties>...</properties> sections to refer indifferently to components, interfaces either through their uuids or alias. Note: an alias name must be unique in a ComponentManager context.
  • autoalias mode: makes an alias between an interface/component name and its uuid. Provides the ability to use the alias in place of the uuid in factory bindings and in properties configuration (only supported with new <properties> semantic). autoalias creates an alias when the first occurence of a name is met (particularly important for interfaces). This mode is enabled when autoAlias xml attribute is set to "true" in <xpcf-registry autoAlias="..."> or <xpcf-configuration autoAlias="..."> Note:
  • when a name is encountered twice, the second occurence is ignored (it should always be the same alias which is true for interfaces)
  • any ambiguity must be resolved with explicit aliasing through an xml <aliases> node
  • a potential evolution will be to "namespace" the component name with the module name to reduce naming conflicts

Component dependency injection

  • compile-time injection is provided through IInjectable interface
  • local components can be used during component injection once registered to XPCF using bindLocal or bind methods (components defined in the library's own code or in libraries linked, but not from dynamically loaded modules). The bind method accepts a factory method to create the concrete component instance -> ComponentBase implements IInjectable and provides injection capabilities to every xpcf components
  • recursive dependency injection and configuration is supported
  • injection errors are handled with InjectionException, InjectableNotFoundException and InjectableDeclarationException exceptions
  • declarative injection (configuration time injection) is provided in registry files through <factory>...</factory> declarations (refer to ````doc/xpcf-registry-sample.xml``` for the semantic)
  • autobind mode is always enabled. While parsing the <module> nodes, it automatically "wires" (binds) components to their declared interfaces. autobind also works upon module introspection made at compile time using IComponentManager::loadModules or IComponentManager::loadModuleMetadata. Note: as autobind is a default behavior, only redundant/specific binds must be declared in <factory ... /> node. Otherwise, default binds are created from components definitions. Note: Autobinding ignores IComponentIntrospect, IConfigurable and IInjectable XPCF interfaces. Autobinding handles wiring from user's interfaces to components.

IComponentManager provides:

  • global "inject" methods helper
  • dependency injection through default binding while parsing the <module> nodes, or through <factory><bindings>... declarations.
  • structured injection. See the semantic in xpcf-registry-sample.xml: node <injects>.
  • named binding injection ability either from configuration (using <factory>...</factory> node) or at compile time
  • injection from bindings upon createComponent from component type trait or uuid
  • concrete component instance resolution from its interface ( or service ) type traits or uuid (and optional name) Upon creation of the concrete instance injection of the component injectables is made when needed through "resolve" methods, and each injectable is configured. Finally the concrete instance is configured and returned to the caller.

XPCF Factories

From 2.5.0, XPCF now supports several creation contexts through the "IFactory" concept. The IComponentManager interface provides access to its inner Factory. From this factory, it is possible to create new factories. New factories are created with a context mode within :

  • Empty: creates a new and empty factory
  • Cloned: creates a factory with a copy of bindings existing in the original factory. Later bindings, configuration ... will only be valid within this cloned factory.
  • Shared: creates a factory that shares the bindings, configuration definition. Only component instances are local to the factory. Any later bind or configuration update will be share between the new and the original factory.

XPCF remoting architecture

A new feature in xpcf 2.5.0 version is the ability to get remote versions of components almost "out of the box".

The remoting functionality works for any xpcf interface that :

  • uses datastructures that declare one of the serialization methods exposed through xpcf specifications (one of the methods provided in xpcf/remoting/ISerializable.h) - in 2.5.0 only boost serialization is handled, upcoming minor releases of xpcf will provide support for the other methods
  • uses base c++ types only

Note : pointers are not handled as they can handle arrays ... hence a pointer will need a size, but no presumptions to detect such cases can be done. Anyway, interfaces represent contracts between a user and a service, hence interfaces should be self explanatory. In this context, relying on pointersd doesn't explicit the expected behavior, hence interfaces should rely on structured types either std ones (string, vectors ...) or user defined ones (struct, classes).

xpcf remoting DSL

xpcf provides a Domain specific language through the use of c++ user defined attributes to ease the generation, those attributes can be used on interfaces or on methods of an interface, depending on the attribute.

DSL description

Attribute Argument type Apply to Semantic
[[xpcf::ignore]] none class or method level specify that the corresponding class/method must be ignored while generating remoting code
[[xpcf::clientUUID("uuid_string")]] string class level specify the xpcf remoting client UUID
[[xpcf::serverUUID("uuid_string")]] string class level specify the xpcf remoting server UUID
[[grpc::noCompression)]] none class or method level disable grpc compression for a whole interface or for a method (compression code generation is not made for the targeted item)
[[grpc::server_streaming]], [[grpc::client_streaming]] or [[grpc::streaming]] none method level
[[grpc::request("requestMessageName")]] string method level optionally set grpc request message name
[[grpc::response("responseMessageName")]] string method level optionally set grpc response message name
[[grpc::rpcName("rpcname")]] string method level optionally set grpc method rpc name

Xpcf remoting tools

xpcf provides several tools to ease the remoting of components :

  1. xpcf_grpc_gen : parses the interfaces and generates the needed remoting code for each inteface in a project.

    • From an interface, the tool creates :
      • grpc request and response messages with each input/output type translated to grpc types
      • a grpc service containing rpc : one for each method of the interface
      • a proxy component that inherits from the interface. This component transforms the interface input/output types to grpc request/response and calls the grpc service and rpc method corresponding with the method interface. This component is configured with a channel url (address and port of the server hosting the concrete code) and a grpc credential.
      • a server component inheriting from IGrpcService. It also implements the grpc::service class, and the concrete xpcf component is injected to this component. Each rpc method of the grpc::service class is implemented and translates grpc request/response to the xpcf component input/output data types, and calls the xpcf component corresponding method
    • From the project the tool creates :
      • a Qt creator development project to compile all the generated code in an xpcf module
      • a client and a server xpcf xml configuration file
  2. xpcf_grpc_server : it is a xpcf application that hosts any IGrpcService enabled component.

To come: xpcf_grpc_linter : this tool will be used at compilation time to validate the xpcf remoting DSL.

XPCF Qt Creator wizards

To help developers create efficiently XPCF based applications, several Qt Creator wizards are provided. Wizards are located in the XPCF release in xpcf/version/wizards, or in the xpcf github repository in the wizards folder. To install the wizards, first install Qt Creator, then launch install.bat (windows platform) or install.sh (linux or macosX platforms).

XPCF Module wizard

This wizard creates a complete Qt Project to build a shared library containing a xpcf module. It contains a skeleton module_main.cpp file with the main XPCF entry point. It depends on qmake rules (from the builddefs-qmake github repository, installed with the remaken package manager tool from remaken github repository ).

XPCF Interface wizard

This wizard creates a xpcf interface header file. This file is the public API. Any component interested to provide this interface functionality will use this header file. The developer only need to add its methods to the interface.

XPCF Component wizard

This wizard creates a xpcf component header and source file. The developer only need to override and implement the methods defined in the interfaces provided by the component. The component header file doesn't need to be exposed. Only the interfaces implemented are part of the API.

XPCF Application wizard

This wizard creates a complete Qt Project to build an application that use xpcf module and components.

XPCF configuration file format

XPCF components can be found, configured and injected with a xml configuration file.

###Node tree The supported xml structure is (in each xml node, "..." symbolizes attributes):

<xpcf-registry>    
    <module ... > <!-- module declaration -->   
        <component ... > <!-- component declaration --> 
            <interface ... />  <!-- interface declaration --> 
        </component>
        <!-- ... other component declarations -->            
    </module>    

    <aliases>   
        <alias ... /> 
        <!-- ... other alias declarations -->   
    </aliases>   

    <factory>
        <!-- default bindings -->   
        <bindings> 
            <bind ... />
            <!-- ... other binding declarations -->
        </bindings>
        <!-- custom bindings : explicit injection for a particular component class -->       
        <injects> 
            <inject ... >
                <bind ... />
                <!-- ... other binding declarations -->
            </inject>
            <!-- ... other inject declarations -->
        </injects> 
    </factory>   
    
    <properties>   <!-- properties declaration -->
        <configure ... > <!-- component configuration declaration --> 
            <property ... /> <!-- single value property declaration -->
            <property ...> <!-- multi value property (vector) declaration -->
                <value>...</value>
                <!-- ... other value declarations -->
            </property> 
            <!-- ... other property declarations -->               
        </configure>      
        <!-- ... other component configuration declarations -->              
    </properties>   
</xpcf-registry>

Node description

Section (parent node) Node Attributes Semantic
document root xpcf-registry autoAlias = [true, false]
-> set whether XPCF must automatically create aliases while parsing the file between :
component UUID <-> component name
interface UUID <-> interface name
This node declares an xpcf registry file, containing both components and modules structure and their properties
document root xpcf-configuration This node declares an xpcf configuration file only, containing components properties
xpcf-registry module uuid = module is referenced with an uuid. It must be declared here.
name = name of the module used
path = path to the module used (can contain environment variables)
description = the module function description
declares an xpcf module
module component uuid = component is referenced with an uuid. It must be declared here.
name = name of the component used
description = the component description
declares an xpcf component inside a module
component interface uuid = interface is referenced with an uuid. It must be declared here.
name = name of the interface used
description = the interface description
declares an xpcf interface implemented by a component
xpcf-registry / xpcf-configuration aliases declares an aliases section. When autoAlias = true in <xpcf-registry>, an alias is created for each component/interface in <module> node when the first occurence of a name is met. Hence the <aliases> section must be used to resolve any name ambiguity, or to simplify a non intuitive name.
aliases alias name = name of the alias
type=[component, interface]
uuid = the uuid of the component/interface targeted by the alias
declares an alias. Alias can be ...
xpcf-registry / xpcf-configuration factory declares the factory section
factory bindings declares the bindings section - this section is needed only to overload autobinds made while parsing the <module> node
bindings bind interface = interface alias or uuid
to = component alias or uuid
[optional] name = name of the binding
[optional] range = [all, default, named, withparents, explicit] the binding range
[optional] scope = [transient, singleton] the binding scope: singleton ensure there will be only one instance of the binded component in this factory context. Transient scope ensure a component is created for each resolution made within the factory context.
[optional] properties = name - the properties name to use to initialize the binded component
declares a default or named bind between an interface and a component
factory injects declares the injects section - used for structured (also called planned) injection for specific component class
injects inject to = component alias or uuid declares an injection pattern for a specific component class
inject bind interface = interface alias or uuid
to = component alias or uuid
[optional] name = name of the binding
[optional] range = [all, default, named, withparents, explicit] the binding range
[optional] scope = [transient, singleton] the binding scope: singleton ensure there will be only one instance of the binded component in this factory context. Transient scope ensure a component is created for each resolution made within the factory context.
[optional] properties = name - the properties name to use to initialize the binded component
declares a specific bind (component context bind) between an interface and a component for the specific component class declared in <inject>
xpcf-registry / xpcf-configuration properties declares the components properties section. This section defines component parameters values for configurable components.
properties configure [optional] uuid = the component uuid or
component = a component alias name
declares the configuration for a specific component
configure property name = property name
type = [int, uint, long, ulong, string, wstring, float, double, structure]
value = property value (must be a valid value for the type chosen)
value can also be declared as sub-nodes for vector properties
declares a component property
property value declares a component property value

Note :

Aliasing order : an alias declared in the section is added to the list of existing aliases, or replace any existing alias with the same name. section overload autoAlias already declared.

Binding order :

  • autobind : during the parsing of the <module> nodes, a bind is added the first time an interface is met. If the same interface is also declared for another component, the first bind is maintained. where a [interface, component] pair is met.
  • factory bindings : when an explicit bind is defined, it replaces any previous autobind made while parsing the <module> nodes.

Sample configuration file

<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<xpcf-registry autoAlias="true">
    <module uuid="{3b899ff0-e098-4218-bdaa-82abdec22960}" name="xpcfModule" description="an sample module with some components" path="$REMAKEN_RULES_ROOT/xpcfModule/2.3.1/lib/x86_64/shared">
	    <component uuid="ae9d10bb-4bd1-462b-8c01-43f1815c6ae0" name="feature1Component - not configurable" description="feature1Component is not Configurable, it implements I0 and I1 interfaces">
	        <interface uuid="125f2007-1bf9-421d-9367-fbdc1210d006" name="XPCF::IComponentIntrospect" description="Component introspection interface."/>
	        <interface uuid="46333fd5-9eeb-4c9a-9912-b7df96ccf5fc" name="I0" description="provides I0 features"/>
	        <interface uuid="3bc8f5ea-ee24-473e-8afd-4f5b1b21c018" name="I1" description="provides I1 features"/>
	    </component>
	    <component uuid="63ff193d-93e6-4ede-9947-22f864ac843f" name="feature2Component - configurable" description="feature2Component implements IConfigurable, I2 and I1 interfaces">
	        <interface uuid="125f2007-1bf9-421d-9367-fbdc1210d006" name="XPCF::IComponentIntrospect" description="Component introspection interface."/>
	        <interface uuid="98dba14f-6ef9-462e-a387-34756b4cba80" name="XPCF::IConfigurable" description="Provides generic configuration ability to any component through metadata parameters"/>
	        <interface uuid="3bc8f5ea-ee24-473e-8afd-4f5b1b21c018" name="I1" description="provides I1 features"/>
	        <interface uuid="41348765-1017-47a7-ab9f-6b59d39e4b95" name="I2" description="provides I2 features"/>
	    </component>
	    <component uuid="{7cc64f89-9a81-4fc8-9695-fde123fae225}" name="technicalComponent - not configurable" description="component provides technical feature to functional components">
	        <interface uuid="125f2007-1bf9-421d-9367-fbdc1210d006" name="XPCF::IComponentIntrospect" description="Component introspection interface."/>
	        <interface uuid="41348765-1017-47a7-ab9f-6b59d39e4b95" name="I2" description="provides I2 features"/>
	        <interface uuid="6279bb1d-69b5-42bd-9da1-743c8922bcb9" name="I3" description="provides I3 features"/>
	    </component>
    </module>
    <aliases>
            <alias name="feature1Component" type="component" uuid="ae9d10bb-4bd1-462b-8c01-43f1815c6ae0"/>
            <alias name="feature2Component" type="component" uuid="63ff193d-93e6-4ede-9947-22f864ac843f"/>
            <alias name="technicalComponent" type="component" uuid="7cc64f89-9a81-4fc8-9695-fde123fae225"/>
    </aliases>
    <factory>
        <!-- default bindings -->
        <bindings>
            <bind interface="I1" to="feature1Component" />
            <bind interface="3bc8f5ea-ee24-473e-8afd-4f5b1b21c018" to="ae9d10bb-4bd1-462b-8c01-43f1815c6ae0" />
            <bind name="feature2Component_I1" interface="I1" to="feature2Component"/>
        </bindings>
        <!-- custom bindings : explicit injection for a particular component class -->
        <injects>
            <inject to="feature1Component">
                    <bind interface="I2" to="technicalComponent" />
            </inject>
        </injects>
    </factory>
    <properties>
        <configure component="feature2Component">
                <property name="parameter1" type="double" value="2.0"/>
                <property name="vector_parameter1" type="double" access="ro">
                    <value>0.2</value>
                    <value>0.5</value>
                </property>
                <property name="structured_parameter"  type = "structure">
                    <property name="cols" type="long" value="2"/>
                    <property name="rows" type="long" value="2"/><!-- rowmajormode only-->
                    <property name="values" type="double">
                        <value>1.1</value>
                        <value>0.7</value>
                        <value>0.3</value>
                        <value>2.7</value>
                    </property>
                </property>
        </configure>
    </properties>
</xpcf-registry>

How to build XPCF

In order to build XPCF:

  • Install conan
  • Install QT to get QT Creator
  • Clone xpcf repository with submodules (git clone --recursive https://github.com/b-com-software-basis/xpcf.git). If you have already cloned the repository but not the submodules, run git submodule update --init --recursive within your xpcf repository folder.
  • Open xpcf.pro file in QT Creator and configure the project with the compilation kit of your OS (gcc on Linux, clang on mac os X, msvc 2017 for windows)
  • Run qmake - this can take a while as it should compile boost from conan.
  • Add an install target for make in "compilation steps" from the "projects" tab in Qt Creator

You can now build xpcf using Build inside Qt Creator.

The compilation result will be located in $HOME/.remaken/packages/[platform-compiler-version]/xpcf/XPCF\_VERSION (%USERPROFILE%\\.remaken\packages\\[platform-compiler-version]\xpcf\XPCF\_VERSION on windows)

Note: To automatically include and link with xpcf headers and library, use the pkgconfig file bcom-xpcf.pc located at the root of the above folder.

Creating your first component

Components are hosted in modules (i.e. shared libraries with special hook methods). Hence prior to the creation of components, start by creating a module.

To create an XPCF module, interface or component it is recommended to use the QT Creator wizards located in wizards/qtcreator folder in xpcf repository.

To install the wizards, please refer to INSTALL.txt.

About

XPCF stands for Cross-Platform Component Framework. It provides base functionalities to handle component interfaces and method introspection, dependency injection and initial configuration.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 4

  •  
  •  
  •  
  •