Skip to main content

Platform Configuration & Build Process

Description

This tutorial contains information about build process and some platform specific topics and shows how to build an SCHost.dll.

Chapters


Build Process 

Description

The following chapters provide some information about build process aspects.

Sub Chapters



Using CMake for Building Courier resp. Courier Applications 
General Setup

Courier including it's sample applications provides a complete cmake environment for targeting multiple platform setups (compiler, OS, etc.). To start the CMake process either the command-line tool (cmake) or the CMake GUI (cmake-gui) can be used. As source code entry point the location of the application's source root folder has to be set. In case building Courier standalone this entry point is the root folder of Courier (more on this in the next section Building Courier Standalone).

When using the CMake GUI you will encounter notifications (aka. CMake error) to

  1. Define the COURIER_PLATFORM of choice , based on the platforms defined in the CourierPlatformDefs folder (see Platform Definition).
  2. Define the COURIER_PLATFORM_CONFIG, based on the platform configurations supported by the selected platform (e.g. Host, Target, etc.)

When you encounter these notifications, interactively select the platform and the platform configuration of your choice and generate the build tool chain.

To avoid these notifications you may use the command line call for CMake instead, e.g.

Use own Platform Definition

If you want to access your own platform definitions, which are not located in the pre-defined folder CourierPlatformDefs, add the CMake parameter COURIER_EXTENDED_SEARCH_PATHS. Afterwards you can define your home-grown platform definition, e.g.

cmake -G "Visual Studio 12" -DCOURIER_EXTENDED_SEARCH_PATHS="<MyOwnPlatformDefFolder>" -DCOURIER_PLATFORM=<MyHomegrownPlatform> -DCOURIER_PLATFORM_CONFIG=<AnyOfHomegrownPlatformConfigs> -DCMAKE_BUILD_TYPE=Debug "<MyWorkspaceFolder>\cgi_studio_courier\src\Courier"

You can set any of the CMake parameter defined in Courier in the command-line call.

Cross Compilation

CMake also supports cross compilation. The easiest way to handle this via CMake is to define a cross compilation toolchain file. CGI-Studio normally provides a basic set of toolchain files for the supported target cross compilations, which are located in ./cgi_studio_candera/cmake/toolchain-files. In case there is none matching your setup, you can easily create your own toolchain file.

Here is an example:

include(CMakeForceCompiler)

# The name of the target operating system.
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR ARM)

# Which C and C++ compiler to use.
set(CMAKE_C_COMPILER <location to the C cross compiler of choice>)
set(CMAKE_CXX_COMPILER <location to the C++ cross compiler of choice>)

Courier only needs the basic set of cross compilation information in the toolchain file, which you can see in the example above. If you like to see more settings (Candera resp. CGI Application specific settings) in a CGI-Studio toolchain file, you have to know, that these are often prepared to configure target builds of a CGI Application without Courier. Courier itself does this configuration already in the Courier platform definition files (see Platform Definition).

Once the toolchain file is available, cmake can be started for cross compilation (if CMake variable CMAKE_TOOLCHAIN_FILE is set properly) in the output directory of choice.

See here as example a linux build script:

#!/bin/bash

ROOT_DIR="~/work/cgi_studio"
GENERATOR="Unix Makefiles"
TOOLCHAIN_FILE="${ROOT_DIR}/cgi_studio_candera/cmake/toolchain-files/<name of the toolchain file of choice>"
PLATFORM=<Courier platform name to use>
PLATFORM_CONFIG=<Courier platform configuration to use>
APPLICATION_PATH="${ROOT_DIR}/cgi_studio_courier_apps/src/CourierSampleApp"
BUILD_TYPE=Debug

cmake -G "$GENERATOR" -DCMAKE_TOOLCHAIN_FILE=$TOOLCHAIN_FILE -DCOURIER_PLATFORM=$PLATFORM -DCOURIER_PLATFORM_CONFIG=$PLATFORM_CONFIG -DCMAKE_BUILD_TYPE=$BUILD_TYPE $APPLICATION_PATH

make

If the build finishes successful, the target executable can be found in the output directory. In the above example the executable will have the name CourierSampleApp_<PLATFORM>_<PLATFORM_CONFIG>. This executable then has to be loaded together with the target Asset to the device (e.g. via rootfs), for which the application was built.

In case of a Courier demo application ./cgi_studio_courier_apps/src the asset is copied to the binary root folder of the build (where the executable resides), named AssetLibCff.bin.

Back to the sub menu


Selecting Courier Features 

CMake feature switches have been renamed for V3.x releases, see Change Log

CMAKE Features

Since Courier2 (V2.0.0) the interaction framework was enhanced with new substantial functionalities. Three new optional Courier software modules are now available, which are reflected by their pendants - the Courier CMake features:

ENHANCED

While the Courier CMake feature COURIER_ENHANCED_ENABLED was inherent part of Courier1 (V1.x.x), it is now possible to disable it for using Courier without Candera graphics engine. The only additional library, which is needed in such a setup is libFeatStd (actually every CGI Studio software product relies on libFeatStd).

The enhanced mode enabled activates the link to CGI-Studio's graphics engine Candera and enables in Courier the modules Visualization and DataBinding. This enhanced mode reflects the feature-set provided with previous Courier (1.x) versions.

Turn on the Courier CMake feature COURIER_ENHANCED_ENABLED (either via CMake-gui or the CMakeLists.txt file of the respective application) to have this mode available.

IPC

Courier also supports IPC with Courier messages, which is supported for Windows, Linux and Integrity environments. Courier IPC abstracts OS specific mechanisms to transport the serialized message over process borders. This IPC is (currently) limited to be used within one processor. To enable this functionality the CMake feature FEATSTD_IPC_ENABLED has to be set.

For this IPC mechanism the Courier generator was enhanced to generate the IPC infrastructure for the applications, which want to communicate via IPC.

MONITOR

Courier2 introduces an additional CMake feature, which is about monitoring messaging and rendering performance. Both monitoring features can be enabled separately having their distinct Courier CMake feature, called:

- COURIER_MESSAGING_MONITOR_ENABLED
- COURIER_RENDERING_MONITOR_ENABLED

These features are prepared to be connected to the CGI Studio Analyzer tool, if this optional CGI Studio module is available and activated via the respective Courier platform definition file using the CGI CMake flag FEATSTD_MONITOR_ENABLED (V2.x: CGIFEATURE_ENABLE_MONITOR).

Additionally if CGI Studio Analyzer option is not available, the Courier messaging monitor feature provides also a native support, while the rendering monitor is not available natively.

Using CMake-GUI both monitoring features are available only if FEATSTD_MONITOR_ENABLED is set to ON in the respective platform definition file. If FEATSTD_MONITOR_ENABLED is set to OFF, then only the COURIER_MESSAGING_MONITOR_ENABLED is visible.

Back to the sub menu

Building Courier Standalone 

For building Courier in standalone mode simply start CMake pointing to the folder

./cgi_studio_courier/src/Courier/ 

After generating the buildsystem via CMake and building the code using the according build tool chain, the Courier platform dependent library is available. Additionally this solution includes the complete Candera, so it is easy to provide also the Candera libraries within one build step.

If the Courier library is build standalone and linked as external library to an customer application code, Courier has generated header files (e.g. Config.h and Version.h), which are created to the Courier build output directory


<CourierOutputDirectory>/gensrc/Courier
and have to be in the include path of the customer Courier application. To solve this issue the folder
<CourierOutputDirectory>/gensrc
has to be added to the customer application's include paths. (To copy the files to cgi_studio_courier/src/Courier may also be possible, but be aware that these generated files can be highly build configuration dependent!)

Back to the sub menu

Building Courier together with an Application Solution 

When using this option, there will be a dedicated CMakeLists.txt file for the Courier application available. Courier has defined a set of CMake macros which shall ease the CMake configuration in the application.

Take a look at the following template to see what this may look like. The comments in the section explain the various steps in detail.

cmake_minimum_required(VERSION 3.8)
cmake_policy(VERSION 3.8)

# Requested Courier version
set(APP_COURIER_VERSION 3.12.0.0)

# -----------------------------------------------------------------------------
# Enable optional Courier features
set(COURIER_ENHANCED_ENABLED ON)

# Required to resolve root paths of CGI Studio products
set(CGI_ROOT_PATH_HINT "../../.." "../.." "../") # Adapt to specific needs

# -----------------------------------------------------------------------------
# Checks for Couriesr package with given version in given paths
function(FindPackage pkg version path0)
    string(TOLOWER ${pkg} searchString)
    foreach(p ${path0} ${ARGN})
        file(GLOB tmp "${p}/*${searchString}*")
        list(APPEND paths ${tmp})
    endforeach()
    message(STATUS "Searching for ${pkg} ${version} in '${paths}' ...")
    find_package("${pkg}" ${version} REQUIRED PATHS ${paths})
endfunction()

# Search for Courier package
FindPackage(Courier ${APP_COURIER_VERSION} ${CGI_ROOT_PATH_HINT})

# -----------------------------------------------------------------------------
# Set customer specific search path for Courier platform definitions
set(CGI_ROOT_PATH "${CMAKE_CURRENT_LIST_DIR}/../../..") 
set(COURIER_PLATFORMDEFS_DIR "${CGI_ROOT_PATH}/cgi_studio_devices/src")

# If COURIER_PLATFORMDEF_FILE is not set, pre-select one of the available platforms
if (NOT COURIER_PLATFORM)
#    Preselection is disabled, as application can be used for different platforms
#    set(COURIER_PLATFORM "<anyPlatform>")
endif()

# Pre-select platform configuration
if (NOT COURIER_PLATFORM_CONFIG)
#    Preselection is disabled, as SampleApp can be used for different platforms
#    set(COURIER_PLATFORM_CONFIG "<anyPlatformConfig>")
    if (CMAKE_SYSTEM_NAME STREQUAL Windows)
        set(COURIER_PLATFORM_CONFIG "Simulation_Win32_x86")
    endif()
endif()

# -----------------------------------------------------------------------------
# Include Courier build system
include("${Courier_DIR}/cmake/Courier.cmake")

# -----------------------------------------------------------------------------
# Enable solution folder representation in Visual Studio
#CgiEnableSolutionFolderSupport(ON)

# -----------------------------------------------------------------------------
# Set asset locations
if (CGIDEVICE_TARGET_BUILD) 
    set(ASSET_POSTFIX Target)
else()
    set(ASSET_POSTFIX Simulation)
endif()
set(ASSET_SOURCE_LOCATION "${CMAKE_CURRENT_LIST_DIR}/Assets/AssetLibCff_${CGIDEVICE_NAME}_${ASSET_POSTFIX}.bin")
set(ASSET_TARGET_LOCATION ${CMAKE_BINARY_DIR}/AssetLibCff.bin)

# -----------------------------------------------------------------------------
# Courier application solution (incl. build of SCHost.dll)
CourierSolution("CourierSampleApp")

    # -------------------------------------------------------------------------
    # Generate application's widget library
    CourierAddProjectFiles(
        AppCDL.h
        AppCDL.cpp
        AppCDL.xcdl
        Constants.cpp
        Constants.h
        ComponentMessageReceiverThread.h
        CustomComponentId.h
        ExtCommOutputHandler.h
        ExtCommOutputHandler.cpp
        ListItemDataPresenter.h
        ListItemDataPresenter.cpp
        RenderStatisticsOverlayWrapper.h
        RenderStatisticsOverlayWrapper.cpp
        SampleApp.cpp
        SampleApp.h
        SampleAppControllerComponent.cpp
        SampleAppControllerComponent.h
        SampleExtCommOutputHandler.cpp
        SampleExtCommOutputHandler.h
        SampleAppLogRealm.cpp
        SampleAppLogRealm.h
        SampleAppMsgs.cpp
        SampleAppMsgs.h
        SampleAppMsgs.xcmdl
        SampleAppViewController.cpp
        SampleAppViewController.h
        SampleAppViewControllerFactory.cpp
        SampleAppViewControllerFactory.h
        SampleAppViewFactory.cpp
        SampleAppViewFactory.h
        SampleTypeConverter.cpp
    )

    # Add screen broker related files
    if(CGIDEVICE_ENABLE_SCREENBROKER)
        CourierAddProjectFiles(
            ScreenBrokerClient.cpp
            ScreenBrokerClient.h
        )
    endif()

    # Bind widgets
    CgiAddProject(Widgets)

    # Add subfolder sources
    CourierAddListFile("DataModel/DataModel.cmake")
    CourierAddListFile("AppPlatform/AppPlatform.cmake")

    # Add Memory Pool specific files
    if(FEATSTD_MEMORYPOOL_ENABLED)
        CourierAddProjectFiles(
            MemoryPoolConfig.cpp
        )
        CourierAddListFile("MemoryPoolUtil/MemoryPoolUtil.cmake")
    endif()

    # Add IPC resources
    if(FEATSTD_IPC_ENABLED)
        CourierAddProjectFiles(
            IpcCDL.cpp
            IpcCDL.h
            IpcCDL.xcdl
            IpcMsgs.cpp
            IpcMsgs.h
        )
        CourierAddListFile("Ipc/Shared/Shared.cmake")
    endif()

set(CGIAPP_TYPE_NATIVE_DLL "NativeDll")
set(CGIAPP_TYPE_NATIVE_EXE "NativeExe")
set(CGIAPP_TYPES ${CGIAPP_TYPE_NATIVE_EXE} ${CGIAPP_TYPE_NATIVE_DLL})
set(CGIAPP_BUILD_TYPE ${CGIAPP_TYPE_NATIVE_EXE} CACHE STRING "Binary type of the built application, one of: ${CGIAPP_TYPES}")
set_property(CACHE CGIAPP_BUILD_TYPE PROPERTY STRINGS ${CGIAPP_TYPES})

    if (${CGIAPP_BUILD_TYPE} STREQUAL ${CGIAPP_TYPE_NATIVE_DLL})
        CgiSetSolutionFolderName("CourierApplication")
        CourierAddDynamicLibrary(CourierSampleAppLib)
        CgiCreateOutputName(PRV_APP_TARGET_NAME CourierSampleAppLib)
    else()
        CgiSetSolutionFolderName("CourierApplication")
        CourierAddStaticLibrary(CourierSampleAppLib)
        # add application top level source files
        CourierAddProjectFiles(
            Main.cpp
        )
        CourierAddExecutable(CourierSampleApp TARGET_NAME COURIER_APPLICATION_TARGET)
        CgiCreateOutputName(PRV_APP_TARGET_NAME CourierSampleApp)
    endif()

    # -------------------------------------------------------------------------
    # Include target specific build targets
    # -----------------------------------------------------------------------------
    if (CMAKE_SYSTEM_NAME STREQUAL Integrity)
        set(COURIER_KERNEL_TARGET_DIR "${COURIER_PLATFORMDEFS_DIR}/${CGIDEVICE_NAME}/CourierPlatformDefs/${COURIER_PLATFORM_CONFIG}/KernelTargets")
        include("${COURIER_KERNEL_TARGET_DIR}/KernelTargets.cmake")
    endif()

    # -------------------------------------------------------------------------
    # Generate application's SCHost dynamic link library project for scene composer
    CourierAddSCHostLibrary()

    CgiClearSolutionFolderName()
CourierSolutionEnd()

#! [COURIER_XCDLGeneration]
# -----------------------------------------------------------------------------
# Generate h/cpp sources from XCDL files
#   Attention: As soon as the generation output dir (3rd parameter) is changed,
#              the courier messages cannot be generated any more!
#              SampleAppMsgs.xcmdl has to be adapted to that effect.
if (FEATSTD_IPC_ENABLED)
    CourierGenerateFromXcdl(CourierSampleAppLib ${CMAKE_SOURCE_DIR}/IpcCDL.xcdl ${CMAKE_SOURCE_DIR} -i ${COURIER_SRC_DIR} -gm -gi -gfa -v)
endif()

CourierGenerateFromXcdl(CourierSampleAppLib ${CMAKE_SOURCE_DIR}/AppCDL.xcdl ${CMAKE_SOURCE_DIR} -i ${COURIER_SRC_DIR} -gm -gd -gfa -v)
#! [COURIER_XCDLGeneration]

# -----------------------------------------------------------------------------
# Copy device specific asset to working dir (with target name AssetLibCff.bin) 
if (EXISTS "${ASSET_SOURCE_LOCATION}")
    if (${CGIAPP_BUILD_TYPE} STREQUAL ${CGIAPP_TYPE_NATIVE_DLL})
        CourierLog0("Asset file found at ${ASSET_SOURCE_LOCATION}. Copy to binary dir manually!")
    else()
        CourierLog0("Asset file found at ${ASSET_SOURCE_LOCATION}. Copying to binary dir")
        add_custom_command(TARGET ${COURIER_APPLICATION_TARGET}
                           PRE_LINK
                           COMMENT "Copy AssetLibCff.bin"
                           COMMAND ${CMAKE_COMMAND} -E copy_if_different ${ASSET_SOURCE_LOCATION} ${ASSET_TARGET_LOCATION}
                           VERBATIM)
    endif()
else()
    CourierLog0("No asset file found at ${ASSET_SOURCE_LOCATION}. Cannot copy to ${ASSET_TARGET_LOCATION}")
endif()

Solution Folder

An interesting option (for Visual Studio only) is the solution folder support, which groups projects to logical groups (aka. solution folder). Though Courier implements the solution folders internally, the option itself is turned off by default (solution folders are not supported by Visual Studio Express edition installations and may cause problems using Express Editions). It can easily be enabled by calling the Courier CMake macro CgiEnableSolutionFolderSupport().

Alternatively setting the environment variable CGI_STUDIO_ENABLE_SOLUTION_FOLDERS to value 1 enables the solution folder support without changing cmake files. With CGI_STUDIO_ENABLE_SOLUTION_FOLDERS variable solution folder support can be enabled per user / workstation.

After CGI_STUDIO_ENABLE_SOLUTION_FOLDERS has been defined, CMake needs to be restarted and the solution be re-generated.

If various application libraries shall be grouped, then the scoping macros CgiSetSolutionFolderName() and CgiClearSolutionFolderName() can be used. The example above already makes use of the options mentioned here.

Back to the sub menu

Building SCHost.dll 

How to build an SCHost.dll for the CGI SceneComposer is described in this chapter.

It is possible to build an additional SCHost.dll for SceneComposer (containing the widgets) by adding the following simple cmake macro to the of the application CMakeLists.txt:

    CourierAddSCHostLibrary()

right after the entry

    CourierAddExecutable(<solutionName>)

It is important to separate the application project in a widget library and an application executable. A simple guideline for the separation is to put only the main entry point (e.g. main.cpp) into the executable and the rest of the application code into the application (widget) library. You can also use a different separation if you so desire, but please make sure that all widget dependent code resides in the application widget library.

As mentioned above the application widget library must be self-contained as it shall be able it to the SCHost.lib without any link error. Just take the CMakeList.txt of the CourierSampleApp as a reference (see Platform Configuration & Build Process).

Having the macro CourierAddSCHostLibrary() applied in the applications CMakeLists.txt file you will find a new CMAKE option in the Courier section named COURIER_ADD_SCHOST_PROJECT. Enable this option, configure until there is no configuration line marked red and generate the solution.

SCHost generation is only applicable (at least tested) with the CMake generators for Visual Studio versions that are mentioned in the release notes.

When opening your Visual Studio project you see an additional CourierSCHost solution folder, which contains the project CourierSCHostDll. This DLL project links the applications widget project (e.g. CourierSampleAppLib inside CourierSampleApp) and the pre-built static Courier SCHost library (provided at cgi_studio_courier/lib folder).

If you also apply the correct SceneComposer binary path in the CMAKE configuration (before generating the project), see CMAKE option CGIAPP_SCENE_COMPOSER_PATH, the built DLL is automatically renamed to SCHost.dll and copied into the SceneComposer binary folder. If this path is not set, the built DLL CourierSCHostDll_<COURIER_PLATFORM>_<COURIER_PLATFORM_CONFIG>.dll has to be renamed to SCHost.dll and copied to the SceneComposer binary folder manually.

For details on developing widgets please also refer to Candera documentation.

Building SCHost.dll when using Memory Pools

As memory pools are introduced in FeatStd V1.2.0 resp. V1.1.0.3, and an SCHost builds using this feature is not binary compatible to a build not using memory pools, a new restriction is added for building SCHost.dll:

  • In the solution for building SCHost.dll, the feature FEATSTD_MEMORYPOOL_ENABLED must not be set!

As an example, the Courier SampleApp platform configuration has been extended to provide two different host simulation configurations:

  • Simulation_Win32_x86: Disabled memory pool feature, option COURIER_ADD_SCHOST_PROJECT available. Use this one to integrate your widgets to SceneComposer.
  • Simulation_Win32_x86_MP: Enabled memory pool feature, but no option to add the Courier SCHost project available. Use this one to integrate and test memory pool configurations on host simulation as preparation for the target.

Back to the sub menu


Adding External Widgets 

In the last major update it is now possible to add additional external widgets (e.g. from a widget repository) to the application. For this reason the widgets of CourierSampleApp are collected in a separate sub-project enhancing the application with the possibility to add additional external defined widget libraries to be linked to the application.

The usage is basically the same like it is already available in CGIApplication (without Courier).

This means a CMake file variable (CGIAPP_ADDITIONAL_WIDGET_PATH) was added pointing to a file, which contains all the additional widget paths. If the paths in this file are valid and the second CMake flag CGIAPP_ENABLE_ADDITIONAL_WIDGETS is checked, than all widgets found in these folders resp. subfolders are added as separate projects to the solution. The format of defining these widgets follow the same structure as it is already used in CGIApplication resp. the sample widget repository cgi_studio_widgets.

The CourierSampleApp got a new folder named Widgets, which includes the basic CMake infrastructure for this new feature. Additionally the already used (and adapted) widgets (SampleWidget and SpeedWidget) and the base classes (CgiWidget2D & CgiWidget3D) are moved to this new Widgets folder.

The WidgetSet itself was removed from the CourierSampleApp source code, and will be replaced with a generated one (based on all the widgets linked to the project), which is located in ${CMAKE_BINARY_DIR}/gensrc/CanderaGen/WidgetSet.cpp.

Migration Info of "Binding of External Widget Libraries"

Migration info of the "Binding of external widget libraries" feature to any other (customer) Courier/CIT application (called in the following <myProject>):

  1. Copy Folder CourierSampleApp/Widgets to <myProject>/Widgets (optional: remove CourierSampleApp's widgets: SampleWidget and SpeedWidget).
  2. Move project (local) owned widgets (if already available) to <myProject>//Widgets.
  3. Remove project (local) owned widget base classes (CgiWidget2D & CgiWidget3D) and WidgetSet.cpp.
  4. Adapt the file AdditionalWidgetsPaths.txt to your needs or let the CMake variable CGIAPP_ENABLE_ADDITIONAL_WIDGETS point to a custom provided additional widgets file.
  5. Optional: adapt <myProject>//Widgets/CMakeLists.txt, if the additional widgets project name shall be changed, resp. the local widgets search path shall be adapted.
  6. To build the new SCHost.dll, don't forget to check the Courier CMake flag COURIER_ADD_SCHOST_PROJECT.
  7. Compile and build the solution (or at least the project CourierSCHostDll_<myProject>_<myProject>).
  8. If the CMake variable CGISTUDIO_SCENE_COMPOSER_PATH was set (or found correctly) the newly built CourierSCHostDll.dll is automatically copied to the previous mentioned SceneComposer path (with name SCHost.dll).
  9. Start SceneComposer and enjoy the newly added widgets.

Back to the sub menu

Back to the menu


Platform Configuration 

Description

Some platform specific topics are described here.

Sub Chapters



Platform Definition 

A Courier platform definition describes the complete setup for a dedicated platform. This comprises the implementation of the various platform interfaces defined in Courier::Platform but also stores the Candera configuration and the characteristics of the target build environment (like OS, Compiler, CPU, etc.).

Courier pre-defines a few sets of platform definitions, which are located in the folder

./cgi_studio_courier/src/CourierPlatformDefs/<PlatformName>/

and Courier may use one of them, which is selected via CMake. See Platform Configuration & Build Process.

If there is a need to adapt a platform definition, like different Candera setup, additional platform configurations, compiler settings, etc. simply duplicate and modify or create a completely new platform definition folder, which has a cmake file with the name <AnyPlatformName>Def.cmake. If the new platform definition doesn't reside in the CourierPlatformDefs folder a simple Courier CMake commando (COURIER_EXTENDED_SEARCH_PATHS), applied accordingly to your Courier application project, will help to find it.


Platform Definition File

The platform definition file holds the complete setup for the platform, as the example Courier's Platform shows:

Config macros

Config macros are used to source re-useable configuration out of any concrete platform definition. Currently they are used to define compiler specific sets for the platform definitions, e.g. for MSVC, gcc, etc. Generally these config macros are included at the beginning of the platform definition file.

Take a look at the config macros for MSVC setup:


General Platform setup

The basic platform setup begins with the macro CourierPlatformDef setting the platform name and a short description about this platform. Various settings are included in the platform definition block, like the choices of toolchains, various platform configurations (e.g. for Host, Target, etc.), and general properties (valid for all platform configurations).

Platform configuration

A platform definition is additionally separated one or more sections of platform configurations. Usually a host (e.g. for simulation on Win32) and a target configuration are defined. Though they are typically named Host and Target, the names of these platform configurations are changeable.

Platform Implementation

Besides the platform definition file, also the implementation of the Courier::Platform interfaces is essential (see also OS Abstraction), which can be found in the subfolders of the various platform definition. For each platform configuration which is defined in the platform definition file, a subfolder with the name of the platform configuration has to exist.

E.g. if there are the several platform configurations, it has to look like this:

<AnyPlatformDefsFolder>/<PlatformName>/Integrity_ARM
<AnyPlatformDefsFolder>/<PlatformName>/Linux_ARM
<AnyPlatformDefsFolder>/<PlatformName>/Simulation_Win32_x86
<AnyPlatformDefsFolder>/<PlatformName>Def.cmake

In this platform configuration folders, the realizations of the Courier::Platform interfaces are located. There are at least two possibilities how this can be done:

  1. Directly code in the platform configuration folder the implementation of the Courier::Platform interface of choice and name the corresponding source and header file like Courier::Platform interface file.
  2. Simply put forwards to a concrete implementation (located anywhere, e.g. in Courier/Platform/Impl) of the platform interface in a header file, named like the corresponding platform interface file. E.g.

In order to get the (concrete or forwarded) platform implementation linked to the according Courier::Platform interface a namespace forward has to be done at the end of the header file of the real implementation as listed here:

namespace Courier { namespace Platform { namespace Impl {
    typedef Courier::Platform::Os::Generic::GenericMessageQueue MessageQueue;
}}}

The only important thing with this namespace forward is the target namespace, which has to be Courier::Platform::Impl::<PlatformInterface>!

Back to the sub menu


External Communication 
External Communication Component

The external communication component (Courier::ExtCommComponent) is served as interface to the "outside world". One of Courier's basic concept are the Components, which are active entities communicating via messages among each other. Besides the core components, which represent the model view controller pattern, the external communication component incorporates this concept.

External communication in other words means interfacing the Courier world with information sent from and to non Courier modules. An ExtCommComponent plays the role of a gateway in this matter.

Like all other components, the ExtCommComponent is identified by an ID, the Component ID, which itself has to be unique per instance. Other than the dedicated Courier components, like ModelComponent, ViewComponent, ControllerComponent, a.s.o. the ExtCommComponent can be instantiated multiple times by providing this unique ID in its constructor interface.

Additionally the ExtCommComponent has an interface to attach/detach multiple external input handler and an interface to set one external output handler. The reason to have only one output handler per ExtCommComponent is that Courier messaging internally has no additional routing information for routing outgoing messages than the message tag "external". So Courier cannot differentiate between different types of "external" messages and sends all "external" Courier messages to all external output handler in the system.

    public:
        ExtCommComponent(ComponentId cid);

        virtual ~ExtCommComponent();

        void Attach(ExtCommInputHandler * extCommInputHandler);

        void Detach(ExtCommInputHandler * extCommInputHandler);

        IExtCommOutputHandler * SetExtCommOutputHandler(IExtCommOutputHandler * extCommOutputHandler);

An ExtCommComponent may also be dedicated to external output handling respectively input handling only (just not attach an external input handler respectively set an external output handler).

A characteristic of Courier components is that they run in a (application defined) thread context defined by the component message receiver (Courier::ComponentMessageReceiver) they are attached to. In case of the ExtCommComponent this means, that all attached external input handler are running in this very same thread context. The external output handler assigned to an ExtCommComponent also runs in the same thread context receiving Courier messages marked with the message tag "external".

In case an external input/output handler is blocking on its execution, the application designer is well advised to create a separate thread context (for listening from respectively distributing data to external modules), in order not to interfere any other external input/output handler or even component attached to the same ComponentMessageReceiver. An example for this can be found in the implementation of the Courier::Platform::ExtComm::PosixTerminalInputHandler.

External Input Handler

An external input handler is an object, which listens on an external communication channel. If an event on this channel is received, which shall be transported to the Courier system, the external input handler has to transform this message into an appropriate Courier message and post it into the Courier system. Courier provides a base (interface) class, which depicts the interface of an external input handler Courier is able to attach to an ExtCommComponent, this class is called Courier::ExtCommInputHandler.

An external input handler has to fulfil one simple interface, called Listen().

        static bool Post(Message * msg);

        virtual void Listen() = 0;

The static method Post() shall be used in the derived class of ExtCommInputHandler for posting Courier messages to the Courier system. It is also possible to simply use the method Post(), which is provided by the dedicated message itself. But using the Courier::ExtCommOutputHandler::Post() method, provides the possibility to monitor the events coming from external sources in a well-defined manner. The method Listen(), which has to be implemented in the respective external input handler, has to provide all the logic about extracting an event from an external source and transform it into a Courier message.

The method Listen() is, as already mentioned above, running in the thread context of the component message receiver, where its ExtCommComponent is attached to. This method is triggered every loop cycle of the underlying thread, so a "forever"-loop must not be used in the implementation of the method Listen(). Running in the context of the component message receiver is also the reason not to use any blocking calls in this method.

External Output Handler

External output handler are simply defined by an interface in Courier, called Courier::IExtCommOutputHandler.

        virtual bool OnMessage(const Message & msg) = 0;

These output handler are application specific because they are simply used to transform a Courier message, which has to be tagged with "external", into a whatever corresponding external event.


Once an external output handler is implemented, for whatever reasons, it can be attached to any available ExtCommComponent in the system. Simply be aware of the above mentioned restrictions on blocking external input handler attached to an ExtCommComponent.

The above message example also defines a dedicated subscriber (component) for the external message, which makes definitely sense for using the optimized routing path. The subscriber named "External" reflects the pre-defined Courier::ComponentType::ExternalCommunicator. This pre-defined component ID is still available in Courier, though it is not anymore assigned to an ExtCommComponent by Courier automatically. Reason is that the ExtCommComponent may be instantiated multiple times having now a Courier::ComponentId in its constructor. But the application can still re-use this pre-defined component ID or simply create its own customer specific component ID. Using a custom component ID means also that this custom component ID has to be used in the subscriber list of the message definition, if needed.

 

The blocking call limitations for external output handler are the same as for the external input handlers. Means if an external output handler uses blocking calls the corresponding ExtCommComponent resp. the underlying ComponentMessageReceiver are blocked too, which would result in not processing the messages in an appropriate manner. In this case a separate thread context is needed to resolve this issue.


How to Implement External Communication

External communication is in most cases dependent on the application requirements, as the application defines which events shall be received from and sent to the external modules. Additionally the external communication may differ on which platform configuration it is applied. E.g. on Win32 the window event handling will make use of the Win32 Messaging System, while on a Linux X11 environment the window events are represented by X11 event handling. Additionally it may be required, that besides window event handling another input source provides events, which shall be forwarded to the Courier application (e.g. CAN, SPI, TCP/IP, Serial, etc.). For this reason is shall be possible to setup different external input handler or even external communication components, running in separate message receiver (thread contexts). Courier itself doesn't has a hard link to these application specific needs, but provides a framework to easily adopt to these needs. The translation from external event to Courier message and vice versa will basically always be part of the application, though Courier provides building blocks, which at least can provide a default transformation.

These building blocks, which are currently available, focus on external input handling. Courier simply provides this implementation, which are not linked into the Courier library but can be added to any application, that wants to make use of them.

Available pre-implemented External Input Handler

Courier already provides a small set of external input handler, which can be used either as building blocks in an applications platform specific code or as templates to implemented custom input handlers.

These building blocks are not linked to the Courier library but may be linked into any application library (or executable). This enables the possibility to completely exchange external input handler for any need.

Currently following external input handler building blocks/templates are available in Courier source folder (see cgi_studio_courier/src/Courier/Platform/Impl/ExtComm/):

  • Courier::Platform::ExtComm:Generic::GenericConsoleInputHander
  • Courier::Platform::ExtComm:Posix::PosixTerminalInputHander
  • Courier::Platform::ExtComm:Win32::Win32WindowInputHandler
  • Courier::Platform::ExtComm:Wayland::WaylandInputHandler

While external input handler can be implemented in many variations, fulfilling the base class interface from Courier::ExtCommInputHandler, the mentioned Courier external input handler building blocks follow basically the same pattern. Every external input handler has a method Init() and if needed a method Dispose(), called at startup respectively shutdown phase of the applications platform environment. For transformation of an incoming event, these external input handler use a hook interface providing the (platform) specific event object. This (platform) specific hook interface can be used to implement the application specific event to Courier message transformation and assigned to the very external input handler.

Currently all external input handlers provided by Courier (as application building blocks) are "non-blocking"! This has the benefit, that they will work in a single threaded application setup (e.g. all components running in the main thread).

Using a non blocking external input handler has unfortunately a big drawback. When attached to an ExtCommComponent, which itself is running in a ComponentMessageReceiver together with other components, this ExtCommComponent will signal to be permanently executed (no wait). This may cause a boost in CPU usage respectively performance issues (e.g. for rendering)! To solve this problem the external input handler execution has to be throttled by either the Component throttling mechanism (available since Courier V2.1) or by simple add a wait state within the execution loop where the ComponentMessageReceiver of the respective ExtCommComponent is running.

Another solution to solve performance problems of "non-blocking" external input handler is to make them "blocking". Having a blocking external input handler implicates changes in the application's thread layout, which at least leads into having the external input handler run in its very own thread context. In Courier terms this means, maximum one external input handler is attached to an ExtCommComponent, which itself must be the only Component running in a ComponentMessageReveiver instance, that in turn is processed in a separate thread context. Though this seems a little restrictive it is the normal way how (external) input events are queried - by listen (and wait) on their respective input event queue in a separate thread. As this is the better approach for external input handling this topic is under review and most probably external input handler building blocks in blocking manner (wait and listen) will be supported.

An example for a Wayland input handler:

    class WaylandInputHandler : public ExtCommInputHandler {
        typedef ExtCommInputHandler Base;
    public:
        WaylandInputHandler();

        virtual ~WaylandInputHandler() {}

        bool Init(void * display, void * data, WaylandEventHook * eventHook = 0);

        WaylandEventHook * SetEventHook(WaylandEventHook * eventHook);

        virtual void Listen();

        const WaylandContext & GetContext() const { return mContext; }

    private:
        WaylandContext mContext;
    };

or an example of a POSIX terminal input handler,

    class PosixTerminalInputHandler : public ExtCommInputHandler {
        typedef ExtCommInputHandler Base;
    public:
        class Hook {
        public:
            virtual Message * OnEvent(Int event) = 0;
        };

        PosixTerminalInputHandler();

        bool Init(const Char * serialPortPath, bool mapStdIn, bool mapStdOut, bool mapStdErr, Hook * hook = 0);

        void Dispose();

        Hook * SetEventHook(Hook * hook);

        virtual void Listen();

        void OnEvent(Int event);

        void SetTerminalListenerName(const Char * name);

        const Char * GetTerminalListenerName() const;

    private:
        SerialPort mSerialPort;
        Hook * mHook;
        PosixTerminalListener mTerminalListener;
        CriticalSection mCs;
        bool mSerialPortOpened;
        bool mStdInMapped;
        bool mStdOutMapped;
        bool mStdErrMapped;
    };

which uses a separate thread for listening to terminal inputs:

    class PosixTerminalListener : public Thread {
    public:
        PosixTerminalListener();

        virtual ~PosixTerminalListener();

        bool Init(PosixTerminalInputHandler * inputHandler);

        void Terminate();

    protected:
        virtual Int ThreadFn();

    private:
        PosixTerminalInputHandler * mInputHandler;
        bool mTerminate;
    };

In order to get a building block linked to an application, simply add the sources to the application's build path. If Courier CMake system is used it looks like this:

CourierAddProjectFiles(
    ABSOLUTE_FILE_PATHS "."
    SOURCE_GROUP AppPlatform/${COURIER_PLATFORM_CONFIG}
    ${COURIER_SRC_DIR}/Courier/Platform/Impl/ExtComm/Win32/Win32WindowInputHandler.cpp
    ${COURIER_SRC_DIR}/Courier/Platform/Impl/ExtComm/Win32/Win32WindowInputHandler.h
)
Using Courier Provided External Input Handler

All external input handler building blocks rely basically on the same pattern: providing a hook interface which is used if available. If no hook object is applied to the respective external input handler, a default handling (if possible) may take place (e.g. sending key messages with keycode, when receiving a mappable key event). This means an application may re-use these building blocks without changing the code but simply applying a hook for events it wants to handle or overwrite (if the default handling is inappropriate).

See here an example of a hook implementation:



This window event hook then has to be linked to an external input handler

// Configure external communication input handling
lRc = lRc && mWin32WindowInputHandler.Init(handle, &mWindowInputEventHook);
COURIER_DEBUG_ASSERT(lRc);

and the external input handler has to be attached to an external communication component.

// Attach external input handler to external communication component
mExtCommComponent.Attach(&mWin32WindowInputHandler);
Using External Output Handler

As already mentioned more than one external input handler may be attached to one ExtCommComponent. Additionally to external input handlers an external output handler may be assigned to the ExtCommComponent. See here a sample of a very simple external output handler:



This external output handler may also be assigned to the same ExtCommComponent (but can also be assigned to a different one).



The external communication component which an external output handler is assigned to is the one which shall be assigned in an Courier "external" tagged message's subscriber list. If an "external" tagged message has no subscriber list it is assumed to be "broadcasted", that is this message is sent to all ExtCommComponents having an external output handler attached.

Starting an External Communication Component

We already learned that the ExtCommComponent has to be instantiated with an unique component ID, which can either be the one available from Courier::ComponentType, called ExternalCommunicator or any custom defined component ID.


Once an external communication component has its external input and/or output handler attached (Using Courier Provided External Input Handler resp. Using External Output Handler), it is ready to be linked to the Courier application.

Next step is to attach the ExtCommComponent to a ComponentMessageReceiver, which provides (beside other things like message queue, etc.) the thread context in which the ExtCommComponent is supposed to run. A few ExtCommComponents may have special requirements, like an ExtCommComponent for window event handling,

    // Attach platform dependent window event handler 
    mMsgReceiver.Attach(mAppEnvironment->GetWindowEventExtCommComponent());

others may run in a separate thread context.

    // Start external communicator in separate thread
    ComponentMessageReceiverThread lExternalCommunicatorThread;
    lExternalCommunicatorThread.SetName("ExtComm");
    lExternalCommunicatorThread.Attach(mExtCommComponent);
    lExternalCommunicatorThread.Activate();
    rc = rc && lExternalCommunicatorThread.Run();

The above piece of code uses the class ComponentMessageReceiverThread. This class is not part of Courier but simply a helper class to let a ComponentMessageReceiver run in a separate thread. Here is the complete implementation of this basic helper:


After attaching the components to its appropriate message receiver, all message receiver have to be activated, which basically means, that they are registered to the Courier internal message router. The message router now know all the message receivers (respectively their message queues) in the system and is able to forward the messages, based on their characteristics (distribution strategy, tags, etc.). Sending the Courier::StartupMsg, which shall always be the very first message to be sent, informs all components about the system startup.


Afterwards let the message receiver being processed in the "main" thread context, if not already done via a ComponentMessageReceiverThread (see code sample above).


Graceful Termination of External Communication Component

Cleaning up has to be done to the external communication component to terminate a program gracefully. Maybe the most important is to call the external input handlers Dispose() method, if one exists, which may restore respectively free external resources.

For example in the implementation of the PosixTerminalInputHandler, the Dispose() method restores the terminal settings, which were changed in initialization phase.



The above code fragments perform detach of the external input handler applied on system shutdown and reset the assigned external output handler.

The application shall always care to cleanup and restore all the resources (like threads, external resources) it has used during its execution to avoid a mess up of the platform.

E.g. gracefully shutdown of component message receiver threads after Courier::ShutdownMsg was issued:


Back to the sub menu


Message Factories 

Usually Courier objects are created/deleted using the macros COURIER_NEW() and COURIER_DELETE().

For customization reasons Courier::Message instances must be handled in a different way.
Therefore a platform specific interface (Courier::Platform::MessageFactory) was introduced:


For convenience reasons the macro COURIER_MESSAGE_NEW() was defined.

There are two different ways of Message allocation: static and dynamic. This is reflected by either using DynamicMessageFactory or StaticMessageFactory.


DynamicMessageFactory

Courier::Platform::Messaging::DynamicMessageFactory allows the creation of an unlimited number of Message instances using the Courier memory management.
See Candera::MemoryManagement::PlatformHeap for details.

StaticMessageFactory

Courier::Platform::Messaging::StaticMessageFactory preallocates memory for a certain number of Message instances using Courier::StaticObjectBuffer. The number of instances must be requested by using Courier::StaticObjectBuffer_Private::BufferData specialization. Please note that the specialization has to be done either in global namespace or in namespace Courier (prefered).


For convenience reasons the macro COURIER_MESSAGE_CONFIGURATION() was defined:

#include "MessageFactoryConfiguration.h"

namespace AppPlatform {
    void LoadMessageFactoryConfiguration()
    {
    }

} // namespace AppPlatform

namespace Courier {
    COURIER_MESSAGE_CONFIGURATION( ::Courier::Internal::ListEventMsg, 10);

// ------------------------------------------------------------------------
    COURIER_MESSAGE_CONFIGURATION( ::Courier::UpdateModelMsg, 10);

// ------------------------------------------------------------------------
    COURIER_MESSAGE_CONFIGURATION( ::Courier::StartupMsg, 1);

See CourierSampleApp/AppPlatform/MessageFactoryConfiguration.cpp for details.

For configuring the usage of StaticMessageFactory please see chapter Platform Implementation.

If CourierGenerator.exe is used to generate MessageFactoryConfiguration.cpp the configuration has to be done in XML (MessageFactoryConfiguration.xcmdl).

Workaround: Due to the fact that some linkers throw away unreferenced static declarations, MessageFactoryConfiguration.[h|cpp] has to implement a method which has to be called before the first Message creation. In SampleApp this is done in AppEnvironment::Setup().

 


Back to the sub menu


OS Abstraction 

Courier provides ready-to-use implementations of Courier::Platform interfaces based on different OS, environments, etc., which can be used as a construction kit for any platform setup. These sources are located in the folder

./cgi_studio_courier/src/Courier/Platform/Impl/Os

Various operating systems implementations are available, like Win32, Posix, Integrity, etc. and Generic, which basically implement the interface based on other interfaces (e.g. Mutex, CriticalSection, AtomicOp, etc.) or have stub implementations for atomic platform interfaces (means they must be implemented for the target OS), like Semaphore, Thread, Ticks, etc.

For more details of interfaces, which have to be provided for the OS abstraction, refer to API definition of Courier::Platform.

Back to the sub menu

Back to the menu