Skip to content

Loading Bindings And Libraries

Diogo Pedrosa edited this page Jan 16, 2024 · 1 revision

Loading Bindings And Libraries (D-Bus and SOME/IP)

Table of contents

Introduction
Generate code and create an adapted CMake file
SOME/IP and D-Bus in the same application

Introduction

In the simple introductory examples for CommonAPI C++ we created shared runtime libaries and shared libraries for vsomeip and libdbus. But the applications themselves (HelloWorldClient and HelloWorldService) contain in one executable not only the application code but also the binding specific generated code. But this is not exactly that what we would do for larger projects with many interfaces because:

  • If one interface is implemented in several services on the target, the generated code for this interface would also exist several times.
  • It is not very flexible; the decision for D-Bus or SOME/IP is made in the CMake file: If we decide to change from D-Bus to SOME/IP we need to write another CMake file and to re-compile everything.

In the following we will see how we can achieve an improvement in both aspects. First we have to change our CMake file in order to build libraries of the generated code. Second we must change the CommonAPI C++ configuration to be able to communicate via D-Bus or SOME/IP, depending on this configuration. Before we go further, a few explanatory words about the fundamental principle how CommonAPI C++ works.

Let's have a look at the following picture.

CommonAPI C++ Libraries

If we create shared libraries (so-files in Linux) which implement some functionality of our application then we have to specify this library in our build system (e.g. link_directories in CMake) and the the dynamic runtime linker tries to load this library when the application is started. CommonAPI C++ is so constructed that the application has to link against the CommonAPI runtime library and against the generated CommonAPI C++ code but not against any binding specific code. If the binding-specific generated code (glue-code, see libcapigen1.so, ... in the picture) is not part of the application then it will be loaded by dlopen (similar under Windows) dependent on the CommonAPI C++ configuration. The CommonAPI C++ configuration file contains the information if the D-Bus glue-code or the SOME/IP glue-code shall be loaded; the runtime linker will then load the binding specific runtime libraries and further shared libraries which are needed (in our case libdbus or vsomeip). This procedure is flexible enough for most projects; it even allows it to create two instances of the same interface in one application and one instance can be accessed via D-Bus; the other via SOME/IP.

We now want to delve deeply into practice and show how the simple Hello World example from the introductory chapters works.

Generate code and create an adapted CMake file

Before you proceed, make sure that (see the CommonAPI C++ user guide or the first chapters of this tutorial):

  • you have checked out and built the CommonAPI C++, CommonAPI C++ D-Bus and CommonAPI C++ SOME/IP runtime libraries,
  • you have downloaded the 3 available code generators,
  • you have created the HelloWorld.fidl, HelloWorld.fdepl as it is described in the first chapters,
  • and at the end you have the implementations HelloWorldClient.cpp, HelloWorldService.cpp and HelloWorldStubImpl.hpp/cpp.

The following picture shows input and output of you build process:

CommonAPI Libraries

❗ Please note that in the most recent versions of CommonAPI Generators (>= 3.2) the .cpp file for the skeleton code (HelloWorldStubDefault.cpp) is no longer generated, only the header file (HelloWorldStubDefault.hpp) is generated.

That means that:

  • we call the D-Bus code generator so that it creates different directories for common-, proxy- and stub code
  • we do the same with the SOME/IP code generator (not shown in the picture)
  • we create now different libraries for common-, proxy- and stub code and for SOME/IP and D-Bus
  • we also create a separate library for the skeleton code

The division into proxy, stub and common libraries makes sense because clients need only proxy code, services need only stub code and the common code is needed by everyone. The skeleton code is in a separate library because it might be that the application does not use the generated skeleton code but implements the stub without inheriting from the default implementation. It makes also sense not to mix up D-Bus and SOME/IP glue code in one library.

❗ In principle the CommonAPI core generator generates only C++ header files apart from the skeleton code. But there is one exception: In case you have used polymorphic structs in your Franca file there will be also cpp-files in the common directory of the core generator which must be considered.

As shown in the picture, call the code generator as follows in order to get the different subdirectories:

<.>/project$ rm -r src-gen
<.>/project$ cgen/commonapi_core_generator/commonapi-core-generator-linux-x86 --skel --dest-common ./src-gen/core/common --dest-proxy ./src-gen/core/proxy --dest-stub ./src-gen/core/stub --dest-skel ./src-gen/core/skel ./fidl/HelloWorld.fidl
<.>/project$ cgen/commonapi_dbus_generator/commonapi-dbus-generator-linux-x86 --dest-common ./src-gen/dbus/common --dest-proxy ./src-gen/dbus/proxy --dest-stub ./src-gen/dbus/stub ./fidl/HelloWorld.fidl
<.>/project$ cgen/commonapi_someip_generator/commonapi-someip-generator-linux-x86 --dest-common ./src-gen/someip/common --dest-proxy ./src-gen/someip/proxy --dest-stub ./src-gen/someip/stub ./fidl/HelloWorld.fdepl

As result you should find in the src-gen directory the subfolders core, dbus and someip. Now we create a new CMakeLists.txt that helps us to build all necessary libraries and to build the HelloWorld application (for the moment only for D-Bus).

Note that:

  • the following file is only an example and it might be necessary to adapt it to your needs and your special environment.
  • we still link everything together (we have just divided up in shared libraries) and do not use the standard CommonAPI mechanism to load the glue-code libraries.
  • the option --skel exists only for the core generator, not for the binding generators.
  • the SOME/IP generator always needs a fdepl file; it does not work directly with fidl files.
  • we build the glue-code for D-Bus and SOME/IP for the next steps even if we create only a D-Bus application in this step here.
cmake_minimum_required(VERSION 2.8)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread -std=c++0x -Wl,--no-as-needed")

include(FindPkgConfig)
find_package(CommonAPI 3.2.0 REQUIRED CONFIG NO_SYSTEM_ENVIRONMENT_PATH NO_CMAKE_SYSTEM_PATH)
find_package(CommonAPI-DBus 3.2.0 REQUIRED CONFIG NO_SYSTEM_ENVIRONMENT_PATH NO_CMAKE_SYSTEM_PATH)
find_package(CommonAPI-SomeIP 3.2.0 REQUIRED)
find_package(vsomeip3 3.4.0 REQUIRED)
pkg_check_modules(DBus REQUIRED dbus-1>=1.4)

include_directories(
     src-gen/core/common
     src-gen/core/proxy
     src-gen/core/stub
     src-gen/core/skel
     src-gen/dbus/common
     src-gen/dbus/proxy
     src-gen/dbus/stub
     src-gen/someip/common
     src-gen/someip/proxy
     src-gen/someip/stub
     ${COMMONAPI_INCLUDE_DIRS}
     ${COMMONAPI_DBUS_INCLUDE_DIRS}
     ${COMMONAPI_SOMEIP_INCLUDE_DIRS}
     ${DBus_INCLUDE_DIRS}
     ${VSOMEIP_INCLUDE_DIRS}
)

link_directories(
    <my-work-path>/build
    ${DBus_INCLUDE_DIRS}/dbus/.libs
)

FILE(GLOB_RECURSE HELLOWORLD_DBUS_COMMON_LIB_SRCS src-gen/dbus/common/*.cpp)
add_library(helloworlddbuscommon SHARED ${HELLOWORLD_DBUS_COMMON_LIB_SRCS})
target_link_libraries(helloworlddbuscommon CommonAPI-DBus dbus-1)

FILE(GLOB_RECURSE HELLOWORLD_DBUS_PROXY_LIB_SRCS src-gen/dbus/proxy/*.cpp)
add_library(helloworlddbusproxy SHARED ${HELLOWORLD_DBUS_PROXY_LIB_SRCS})
target_link_libraries(helloworlddbusproxy CommonAPI-DBus dbus-1)

FILE(GLOB_RECURSE HELLOWORLD_DBUS_STUB_LIB_SRCS src-gen/dbus/stub/*.cpp)
add_library(helloworlddbusstub SHARED ${HELLOWORLD_DBUS_STUB_LIB_SRCS})
target_link_libraries(helloworlddbusstub CommonAPI-DBus dbus-1)

FILE(GLOB_RECURSE HELLOWORLD_SOMEIP_COMMON_LIB_SRCS src-gen/someip/common/*.cpp)
add_library(helloworldsomeipcommon SHARED ${HELLOWORLD_SOMEIP_COMMON_LIB_SRCS})
target_link_libraries(helloworldsomeipcommon CommonAPI-SomeIP)

FILE(GLOB_RECURSE HELLOWORLD_SOMEIP_PROXY_LIB_SRCS src-gen/someip/proxy/*.cpp)
add_library(helloworldsomeipproxy SHARED ${HELLOWORLD_SOMEIP_PROXY_LIB_SRCS})
target_link_libraries(helloworldsomeipproxy CommonAPI-SomeIP)

FILE(GLOB_RECURSE HELLOWORLD_SOMEIP_STUB_LIB_SRCS src-gen/someip/stub/*.cpp)
add_library(helloworldsomeipstub SHARED ${HELLOWORLD_SOMEIP_STUB_LIB_SRCS})
target_link_libraries(helloworldsomeipstub CommonAPI-SomeIP)

add_executable(HelloWorldClient src/HelloWorldClient.cpp)
target_link_libraries(HelloWorldClient CommonAPI helloworlddbusproxy helloworlddbuscommon CommonAPI-DBus dbus-1)

add_executable(HelloWorldService src/HelloWorldService.cpp src/HelloWorldStubImpl.cpp)
target_link_libraries(HelloWorldService CommonAPI helloworlddbusstub helloworlddbuscommon CommonAPI-DBus dbus-1)

Now call CMake and make:

<.>/project$ cmake -Bbuild -DCMAKE_PREFIX_PATH=$(realpath ../install_folder) -DPKG_CONFIG_USE_CMAKE_PREFIX_PATH=ON .
<.>/project$ cmake --build build

In order to create a more realistic and professional CMake file we use now find_package to find the libraries and include paths of CommonAPI and vsomeip; for D-Bus this is not possible because we used the autotools to build the D-Bus library. Because we generated all the code in different sub-folders we can now use the FILE(GLOB_RECURSIVE ...) macro for the C++ file and must not list the files explicitly.

Here is another important hint: Note the linker option -Wl,--no-as-needed! For everybody who has no idea what this means here comes a short explanation. The default setting for the linker is --as-needed. Each library or executable application has an entry or tag which other libraries are needed to start the application or to load the library (DT_NEEDED tag). If a library is tagged as "needed" then it will be loaded from the runtime linker, otherwise not. The default setting "--as-needed" means that a library is only tagged as needed if there are symbols which can be resolved by this library. In the case of the CommonAPI glue-code this is not the case because one of the features of CommonAPI is the possibility to load glue-code libraries dependent on the configuration (see below). Therefore the glue-code would not be loaded even if we added the libraries by target_link_libraries. In the case we do not use any configuration file we need the option --no-as-needed in order to mark the CommonAPI libraries as needed even if the linker detects no symbols which can be resolved by this library. You can find out if a library is tagged with the DT_NEEDED flag by the command (example SOME/IP stub library): readelf --dynamic libhelloworldsomeipstub.so. The output is:

Dynamic section at offset 0x38e28 contains 33 entries:
  Tag        Type                         Name/Value
 0x00000001 (NEEDED)                     Shared library: [libCommonAPI-SomeIP.so.3]
 0x00000001 (NEEDED)                     Shared library: [libvsomeip.so.2]
 0x00000001 (NEEDED)                     Shared library: [libCommonAPI.so.3]
 0x00000001 (NEEDED)                     Shared library: [libstdc++.so.6]
 0x00000001 (NEEDED)                     Shared library: [libm.so.6]
 0x00000001 (NEEDED)                     Shared library: [libgcc_s.so.1]
 0x00000001 (NEEDED)                     Shared library: [libpthread.so.0]
 0x00000001 (NEEDED)                     Shared library: [libc.so.6]
 0x0000000e (SONAME)                     Library soname: [libhelloworldsomeipstub.so]
... 

After this brief digression back to our example. If we start the HelloWorld example as before in two different terminals then it should work exactly as before and they should communicate with each other using DBus.

<.>/project$ LD_LIBRARY_PATH=../install_folder/lib:$PWD/build/ build/HelloWorldService
<.>/project$ LD_LIBRARY_PATH=../install_folder/lib:$PWD/build/  build/HelloWorldClient

However, this approach has some drawbacks. One of them is that the binding (D-Bus or SOME/IP) is determined for each instance at compile / link time. This determination is not part of the startup configuration but is part of the build environment. Another drawback is the ugly necessity to add all needed libraries to your target_link_libraries call.

I will now illustrate how the glue-code can be flexibly chosen by the CommonAPI configuration file. First of all we change the target_link_libraries lines for our applications HelloWorldClient and HelloWorldService in the CMakeLists.txt file. Now we need only:

add_executable(HelloWorldClient src/HelloWorldClient.cpp)
target_link_libraries(HelloWorldClient CommonAPI)

add_executable(HelloWorldService src/HelloWorldService.cpp src/HelloWorldStubImpl.cpp)
target_link_libraries(HelloWorldService CommonAPI) 

It is much more easy, isn't it? After that we create an ASCII file with the name commonapi.ini and copy it in the same directory where the executables are. The content is:

[proxy]
local:commonapi.examples.HelloWorld:v0_1:test=libhelloworlddbusproxy.so

[stub]
local:commonapi.examples.HelloWorld:v0_1:test=libhelloworlddbusstub.so

Now again the application should work as before, the only difference is that the glue-code libraries are loaded only if they are needed and at the time when they are needed. This is the standard CommonAPI way to connect to D-Bus or SOME/IP. In case we want to change from D-Bus to SOME/IP we just change the entry in the commonapi.ini file and are ready.

<.>/project$ cmake -Bbuild -DCMAKE_PREFIX_PATH=$(realpath ../install_folder) -DPKG_CONFIG_USE_CMAKE_PREFIX_PATH=ON .
<.>/project$ cmake --build build
<.>/project$ COMMONAPI_CONFIG=commonapi.ini LD_LIBRARY_PATH=../install_folder/lib:$PWD/build/ build/HelloWorldService
<.>/project$ COMMONAPI_CONFIG=commonapi.ini LD_LIBRARY_PATH=../install_folder/lib:$PWD/build/ build/HelloWorldClient

Some more remarks.

  • The commonapi.ini file can be global (in /etc) or local (in the same directory as the application, highest priority) or the location of the file can be determined by an environment variable (COMMONAPI_CONFIG).
  • The configuration of the glue-code can not only be used to change the binding; there are other possibilities, e.g. to change interface versions.
  • Please note that our HelloWorld code is too simple; the shared pointer to the proxy object will be NULL if no glue-code is found. In this case the line while ( !(myProxyDBus->isAvailable()) {...} leads to a segmentation fault. Therefore check always if the proxy could be created.
  • It is no problem to create two instances of HelloWorld in the same application and to communicate with one instance via D-Bus and with the other via SOME/IP.

I outline briefly what to do for the last point.

SOME/IP and D-Bus in the same application

Step 1: Change your service application and register 2 instances (the implementation of the service HelloWorldStubImpl can stay the same).

#include <iostream>
#include <thread>

#include <CommonAPI/CommonAPI.hpp>
#include "HelloWorldStubImpl.hpp"

using namespace std;

int main() {
    CommonAPI::Runtime::setProperty("LogContext", "E01S");
    CommonAPI::Runtime::setProperty("LogApplication", "E01S");
    CommonAPI::Runtime::setProperty("LibraryBase", "HelloWorld");

    std::shared_ptr<CommonAPI::Runtime> runtime = CommonAPI::Runtime::get();

    std::string domain = "local";
    std::string instanceDBus = "testDBus";
    std::string instanceSomeIP = "testSomeIP";
    std::string connection = "service-sample";

    std::shared_ptr<HelloWorldStubImpl> myServiceDBus = std::make_shared<HelloWorldStubImpl>();
    std::shared_ptr<HelloWorldStubImpl> myServiceSomeIP = std::make_shared<HelloWorldStubImpl>();
    runtime->registerService(domain, instanceDBus, myServiceDBus, connection);
    runtime->registerService(domain, instanceSomeIP, myServiceSomeIP, connection);

    std::cout << "Successfully Registered Service!" << std::endl;

    while (true) {
        std::cout << "Waiting for calls... (Abort with CTRL+C)" << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(60));
    }

    return 0;
}

Step 2: Change your client application and register two proxies.

#include <iostream>
#include <string>
#include <thread>

#ifndef _WIN32
#include <unistd.h>
#endif

#include <CommonAPI/CommonAPI.hpp>
#include <v0/commonapi/examples/HelloWorldProxy.hpp>

using namespace v0::commonapi::examples;

int main() {
    CommonAPI::Runtime::setProperty("LogContext", "E01C");
    CommonAPI::Runtime::setProperty("LogApplication", "E01C");
    CommonAPI::Runtime::setProperty("LibraryBase", "HelloWorld");

    std::shared_ptr < CommonAPI::Runtime > runtime = CommonAPI::Runtime::get();

    std::string domain = "local";
    std::string instanceDBus = "testDBus";
    std::string instanceSomeIP = "testSomeIP";

    std::shared_ptr<HelloWorldProxy<>> myProxyDBus = runtime->buildProxy<HelloWorldProxy>(domain,
            instanceDBus);
    std::shared_ptr<HelloWorldProxy<>> myProxySomeIP = runtime->buildProxy<HelloWorldProxy>(domain,
            instanceSomeIP);

    if ( (myProxyDBus == NULL) || (myProxySomeIP == NULL) ) {
        if (myProxyDBus == NULL) {
            std::cout << "No DBus proxy created." << std::endl;
        }
        if (myProxySomeIP == NULL) {
            std::cout << "No SomeIP proxy created." << std::endl;
        }
        return 0;
    }

    while ( !(myProxyDBus->isAvailable()) || !(myProxySomeIP->isAvailable()) ) usleep(10);

    const std::string name = "World";
    CommonAPI::CallStatus callStatusDBus, callStatusSomeIP;
    std::string returnMessageDBus, returnMessageSomeIP;

    CommonAPI::CallInfo info(1000);
    info.sender_ = 1234;

    while (true) {
        myProxyDBus->sayHello(name, callStatusDBus, returnMessageDBus, &info);
        if (callStatusDBus != CommonAPI::CallStatus::SUCCESS) {
            std::cerr << "Remote call failed!\n";
            return -1;
        }
        myProxySomeIP->sayHello(name, callStatusSomeIP, returnMessageSomeIP, &info);
        if (callStatusSomeIP != CommonAPI::CallStatus::SUCCESS) {
            std::cerr << "Remote call failed!\n";
            return -1;
        }
        info.timeout_ = info.timeout_ + 1000;

        std::cout << "Got returnMessageDBus: '" << returnMessageDBus << "'\n";
        std::cout << "Got returnMessageSomeIP: '" << returnMessageSomeIP << "'\n";
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }

    return 0;
}

Please note that there is now a NULL pointer check for the proxy in the application code.

Step 3: Adapt the commonapi.ini file.

[proxy]
local:commonapi.examples.HelloWorld:v0_1:testDBus=libhelloworlddbusproxy.so
local:commonapi.examples.HelloWorld:v0_1:testSomeIP=libhelloworldsomeipproxy.so

[stub]
local:commonapi.examples.HelloWorld:v0_1:testDBus=libhelloworlddbusstub.so
local:commonapi.examples.HelloWorld:v0_1:testSomeIP=libhelloworldsomeipstub.so

Now you should get something like:

<.>/project$ cmake -Bbuild -DCMAKE_PREFIX_PATH=$(realpath ../install_folder) -DPKG_CONFIG_USE_CMAKE_PREFIX_PATH=ON .
<.>/project$ cmake --build build
<.>/project$ COMMONAPI_CONFIG=commonapi.ini LD_LIBRARY_PATH=../install_folder/lib:$PWD/build/ build/HelloWorldService
Successfully Registered Service!
Waiting for calls... (Abort with CTRL+C)
sayHello('World'): 'Hello World!'
sayHello('World'): 'Hello World!'
<.>/project$ COMMONAPI_CONFIG=commonapi.ini LD_LIBRARY_PATH=../install_folder/lib:$PWD/build/ build/HelloWorldClient
Got returnMessageDBus: 'Hello World!'
Got returnMessageSomeIP: 'Hello World!'