本章主要介绍在SOME/IP通信过程中的另外一个IPC通信利剑,CommonAPI库,文章将从如下几个角度让读者了解什么是CommonAPI, 以及库在实际工作中的作用
文中资源:vsomeip+commonapi+指导文档与demo源码
SOME/IP通信之CommonAPI
- CommonAPI库是什么
- CommonAPI库的编译
- 写个Demo实战一下
CommonAPI库是什么
CommonAPI是GENIVI组织开发的一个基于C++的应用API库,没错,跟vsomeip协议栈是一个爹。其主要提供给使用通讯中间件传输数据的分布式应用来操作通讯中间件的接口。主要的作用是使使用CommonAPI进行IPC通信的的应用能够隔离底层协议栈的差异。比如使用CommonAPI时,我们的底层协议栈可以是vsomeip,也可以是DBUS等. 他的架构如下:
从图中可以看到,CommonAPI C++ 框架分为两个主要部分:
CommonAPI Core: 这部分与中间件无关,包含了跨不同中间件技术共享的基本 API、类和组件。它提供了构建应用程序所需的基本通信机制和抽象,而不与特定的中间件协议绑定。
CommonAPI Binding: 这部分与特定中间件相关,并包含了每种支持的中间件协议的实现细节。
通过将Core与Binding分开,CommonAPI C++ 允许开发人员使用与中间件无关的核心编写他们的应用程序逻辑,使其代码更具可移植性,不会紧密绑定到特定的中间件。然后,他们可以选择特定的绑定,将他们的应用程序适配到所需的中间件技术,而无需重写整个应用程序逻辑。
这种关注点分离使得开发更容易,可以在具有不同中间件要求的各种汽车系统中部署应用程序。它通过在中间件无关逻辑和特定中间件实现细节之间提供明确的分离,促进了可重用性和模块化。
图右边的四个框架,分别代表了Core层的代码生成工具,以及接口描述语言(fidl), Binding层的代码生成工具,以及接口描述语言(fdepl),即:
Code Generator Tools <--> *.fidl
Code Generator Binding Tools <--> *.fdepl
关于fidl跟fdepl文件是什么,如果你做过android开发,那么对aidl文件以及hidl文件会比较熟悉,这个fidl跟fdepl文件也是跟aidl&hidl的作用类似,我们将通信行为的接口以及参数定义在文件中,通过CommonAPI提供的Code Generators,即可将这些文件转成对应的C++接口。上层在在进行IPC接口时,只需要调用这些C++中的接口即可
CommonAPI库的编译
因为我们是基于android系统来使用CommonAPI通信,故使用ndk+gradle来编译库
编译环境
操作系统:win11
Gradle: 7.3.3
cmake版本:3.18.1
下载源码
这里包含两部分,一个是CommonAPI Core,一个是CommonAPI Binding分别对应如下两个库:
capicxx-core-runtime
capicxx-someip-runtime
下载完成后,将文件夹拷贝到上一篇文章提供的附件工程中的external目录下面,放好后如下所示:
修改CMakeList
然后我们需要修改工程根目录下的CMakeLists.txt
文件, 注意是工程根目录,不是模块根目录,修改后内容如下所示:
cmake_minimum_required(VERSION 3.10)
project(SOMEIP)
list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/output/${ANDROID_ABI})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/output/${ANDROID_ABI})
set(FETCHCONTENT_SOURCE_DIR_BOOST ${CMAKE_CURRENT_SOURCE_DIR}/external/boost_1_71_0)
add_subdirectory(external/boost-cmake)
add_subdirectory(external/vsomeip)
add_subdirectory(external/capicxx-core-runtime-master)
add_subdirectory(external/capicxx-someip-runtime-master)
add_subdirectory(app/src/main/cpp)
external/capicxx-core-runtime-master/CMakeLists.txt
#添加编译标记,不然会编译报错(-Wno-ignored-attributes -Wno-deprecated-declarations)
IF(MSVC)
message("using MSVC Compiler")
add_definitions(-DCOMMONAPI_INTERNAL_COMPILATION -DCOMMONAPI_DLL_COMPILATION)
add_compile_options(/EHsc /wd4996)
ELSE ()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -Wformat -Wformat-security -Wconversion -fexceptions -fstrict-aliasing -fstack-protector -fasynchronous-unwind-tables -fno-omit-frame-pointer -Werror -DCOMMONAPI_INTERNAL_COMPILATION -fvisibility=hidden -Wno-ignored-attributes -Wno-deprecated-declarations")
ENDIF(MSVC)
external/capicxx-core-runtime-master/CMakeLists.txt
if ("${USE_INSTALLED_COMMONAPI}" STREQUAL "ON")
FIND_PACKAGE(CommonAPI 3.2.0 REQUIRED CONFIG NO_CMAKE_PACKAGE_REGISTRY)
else()
# 注释掉REQUIRED
# FIND_PACKAGE(CommonAPI 3.2.0 REQUIRED CONFIG NO_SYSTEM_ENVIRONMENT_PATH NO_CMAKE_SYSTEM_PATH)
FIND_PACKAGE(CommonAPI 3.2.0)
endif()
# maintainer-clean target去掉
if(NOT ANDROID)
add_custom_target(maintainer-clean COMMAND rm -rf *)
endif()
在根目录的cmake文件夹下再按照cmake的find_package规则添加两个文件,FindCommonAPI.cmake
, 跟FindCommonAPI-SomeIP.cmake
两个文件
然后启动编译,就可以在工程根目录的output目录下看到库基本已经全部输出了
写个Demo实战一下
上面库编译完成了,书接上回的vsomeip demo,我们需要将someip_server跟someip_client这两个库改造以下,不直接使用vsomeip协议栈的API, 改为CommonAPI的方式来实现。
在写代码之前,咱们来回顾一下之前使用vsomeip协议栈API写的例子,我们定义了一个天气服务,然后服务中提供了一个获取温度的方法,参数如下:
//服务ID
static vsomeip::service_t weather_service_id = 0x1001;
//服务实例ID
static vsomeip::instance_t weather_service_instance_id = 0x0001;
//方法ID
static vsomeip::method_t weather_get_temp_method_id = 0x0001;
这里我们把它改造成CommonAPI的方式,首先在cpp目录下创建如下两个文件,并填入内容:
IWeatherService.fidl
package com.commapi.test
interface IWeatherService {
version { major 0 minor 1 }
method getTemp {
out {
Int32 temp
}
}
}
IWeatherService.fdepl
import "platform:/plugin/org.genivi.commonapi.someip/deployment/CommonAPI-4-SOMEIP_deployment_spec.fdepl"
import "IWeatherService.fidl"
define org.genivi.commonapi.someip.deployment for interface com.commapi.test.IWeatherService {
SomeIpServiceID = 4097 //0x1001
method getTemp {
SomeIpMethodID = 1 //0x0001
SomeIpReliable = true
}
}
define org.genivi.commonapi.someip.deployment for provider as Service {
instance com.commapi.test.IWeatherService {
InstanceId = "com.commapi.test.IWeatherService"
SomeIpInstanceID = 1 //0x0001
}
}
我们这里只展示使用方式,fidl&fdepl的语法,我们后面会专门起文章来介绍。
写好后,需要下载如下两个工具:
commonapi_core_generator-3.2.0
commonapi_someip_generator-3.2.0.1
下载完成后,解压到执行的目录, 然后启动cmd窗口,跳转到我们的fidl & fdepl文件所在目录,然后输入如下命令,即可:
commonapi-core-generator-windows-x86_64.exe -sk *.fidl
commonapi-someip-generator-windows-x86_64.exe *.fdepl
这里我们会发现我们的fidl文件夹下多了自动生成了一个src-gen的文件夹,内容如下:
接下来改造server端跟client端,第一步先把cpp跟.h文件加入到编译源文件中, 修改cpp目录下的CMakeLists.txt如下:
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.18.1)
# Declares and names the project.
project("someip")
find_package (vsomeip3 3.3.8 REQUIRED)
find_library(log-lib log)
include_directories(${VSOMEIP3_INCLUDE_DIRS})
# CommonAPI
find_package(CommonAPI 3.2.0 REQUIRED)
include_directories(${COMMONAPI_INCLUDE_DIRS})
# CommonAPI-SomeIP
find_package(CommonAPI-SomeIP 3.2.0 REQUIRED)
include_directories(${CommonAPI-SomeIP_INCLUDE_DIRS})
# SRC-GEN
include_directories(fidl/src-gen)
file(GLOB SRC-GEN fidl/src-gen/v0/com/commapi/test/*.cpp)
add_executable(someip_server
# Provides a relative path to your source file(s).
someip_server.cpp
${SRC-GEN})
target_link_libraries(someip_server ${log-lib} vsomeip3 vsomeip3-e2e vsomeip3-sd CommonAPI CommonAPI-SomeIP)
add_executable(someip_client
# Provides a relative path to your source file(s).
someip_client.cpp
${SRC-GEN})
target_link_libraries(someip_client ${log-lib} vsomeip3 vsomeip3-e2e vsomeip3-sd CommonAPI CommonAPI-SomeIP)
编译文件修改完成后,再将我们之前的vsomeip的两个端代码修改为CommonAPI方式,对应两个文件如下:
服务端实现:someip_server.cpp
#include <string>
#include <csignal>
#include <unistd.h>
#include "CommonAPI/CommonAPI.hpp"
#include "v0/com/commapi/test/IWeatherServiceStubDefault.hpp"
using namespace v0::com::commapi::test;
class WeatherServiceStub : public IWeatherServiceStubDefault {
private:
int temp = -32;
public:
WeatherServiceStub() = default;
~WeatherServiceStub() override = default;
void getTemp(const std::shared_ptr<CommonAPI::ClientId> _client, getTempReply_t _reply) override{
printf("%s : gid[%d], uid[%d]\n", __func__ , _client->getGid(), _client->getUid());
_reply(temp++);
}
};
int main(int args, char** argc){
//设置配置文件路径
setenv("COMMONAPI_CONFIG", "vendor/etc/commonapi.ini",1);
setenv("VSOMEIP_CONFIGURATION", "/vendor/etc/local_server.json", 1);
auto runtime = CommonAPI::Runtime::get();
auto weather_service = std::make_shared<WeatherServiceStub>();
auto server_register = runtime->registerService("local",
"com.commapi.test.IWeatherService",
weather_service,
"someip_server");
printf("register weather service : %d\n", server_register);
while(true){
usleep(1000 * 1000);
}
return 1;
}
客户端实现:someip_client.cpp
#include <string>
#include <csignal>
#include <unistd.h>
#include "thread"
#include "CommonAPI/CommonAPI.hpp"
#include "v0/com/commapi/test/IWeatherService.hpp"
#include "v0/com/commapi/test/IWeatherServiceProxy.hpp"
using namespace v0::com::commapi::test;
int main(int args, char** argc){
setenv("COMMONAPI_CONFIG", "vendor/etc/commonapi.ini",1);
setenv("VSOMEIP_CONFIGURATION", "/vendor/etc/local_client.json", 1);
auto runtime = CommonAPI::Runtime::get();
auto app = runtime->buildProxy<IWeatherServiceProxy>("local",
"com.commapi.test.IWeatherService",
"someip_client");
while(true){
if(!app->isAvailable()){
printf("connecting failed, retry\n");
sleep(1);
continue;
}
break;
}
app->getProxyStatusEvent().subscribe([&app](CommonAPI::AvailabilityStatus status){
if(status == CommonAPI::AvailabilityStatus::AVAILABLE){
printf("service available\n");
std::thread t1([&app]{
for(int i=0; i<10; i++){
CommonAPI::CallStatus callStatus;
int32_t temp;
CommonAPI::CallInfo callInfo;
app->getTemp(callStatus, temp, &callInfo);
printf("CallStatus = %d, getTemp: %d\n",callStatus, temp);
}
});
t1.detach();
}
});
while(true){
usleep(1000 * 1000);
}
return 1;
}
我这里为了方便仅测试单机模式(双机模式一样的,只是配置不一样)
修改服务端配置local_server.json
{
"unicast":"127.0.0.1",
"logging":
{
"level":"debug",
"console":"true"
},
"applications":
[
{
"name":"someip_server",
"id":"0x1000"
}
],
"services" :
[
{
"service" : "0x1001",
"instance" : "0x0001",
"reliable" : { "port" : "30509", "enable-magic-cookies" : "false" }
}
],
"routing":"someip_server",
"service-discovery" :
{
"enable" : "true",
"multicast" : "239.224.224.245",
"port" : "30490",
"protocol" : "udp",
"initial_delay_min" : "10",
"initial_delay_max" : "100",
"repetitions_base_delay" : "200",
"repetitions_max" : "3",
"ttl" : "3",
"cyclic_offer_delay" : "2000",
"request_response_delay" : "1500"
}
}
修改客户端配置:local_client.json
{
"unicast":"127.0.0.1",
"netmask" : "255.255.255.0",
"logging":
{
"level":"debug",
"console":"true"
},
"applications":
[
{
"name":"someip_client",
"id":"0x1001"
}
],
"clients" :
[
{
"service" : "0x1001",
"instance" : "0x0001",
"reliable" : [ "41234" ]
}
],
"service-discovery" :
{
"enable" : "false"
}
}
然后按照上一篇vsomeip的方式,把库跟文件分别push到系统对应的目录,执行后,即可看到通信正常执行了,如下图。