当前位置: 首页 > news >正文

使用CommonAPI开发Some/IP的流程

一. fidl和fdepl

        fidl对应的是服务的接口,这个是抽象层面的接口,和下面的绑定层(中间件层Some/IP或者DBus)无关。

        fidl中除了可以申明接口中的方法以外,还可以申明事件(broadcast)或者属性(event)以及自定义数据类型(struct):

        

        fdepl文件是对fidl文件中申明的接口和数据类型的部署信息(基于特定绑定层中间件some/ip或者dbus)。

        因此,在fdepl中,需要申明是使用什么绑定层中间件来实现fidl中的接口,例如 org.genivi.commonapi.someip.deployment

        可以看到,针对fidl接口中的interface, method,在fdepl中都有对应的Some/IP的Service ID和Method ID对应,即针对commonapi.Helloworld接口,在Some/IP绑定层,使用了Service ID=0x1234的Some/IP Service来实现该接口中定义的方法/事件/属性,其中,Servvice ID =0x1234的Some/IP Service中,定义了一个方法,Method ID=0x7b,用来实现commonapi.Helloworld接口中的sayHello方法。

        最后,对于核心层的接口实例test,在Some/IP绑定层,定义了Service ID =0x1234 Instance ID =0x01的Some/IP服务实例来对应。

二. 生成核心层和绑定层的Proxy和Stub代码

2.1 核心层生成代码

        根据fidl文件生成核心层代码依赖 commonapi-core-generator-linux-x86_64 工具来生成核心层的代码,这些代码依赖libCommonAPI.so来调用核心层的功能。生成的代码主要是使用C++语言来描述fidl中定义的接口,产生了fidl接口的Proxy和Stub的C++类定义。

        例如根据上面的commonapi.HelloWorld接口,生成了三个核心层的代码文件:

HelloWorld.hpp,HelloWorldProxy.hpp,HelloWorldProxyBase.hpp,HelloWorldStub.hpp和HelloWorldStubDefault.hpp

        其中HelloWorld.hpp是commonapi.HelloWorld接口的C++描述,其中最重要的接口就是getInterface,该接口函数会返回CommonAPI接口名:

const char* HelloWorld::getInterface() {return ("commonapi.HelloWorld:v1_0");
}

        然后,HelloWorldProxy.hpp,HelloWorldProxyBase.hpp,HelloWorldStubDefault.hpp和HelloWorldStub.hpp分别定义了C++层commonapi.HelloWorld接口的客户端和服务端实现的基础代码。

        其中HelloWorldStub和HelloWorldProxyBase类是接口类,而HelloWorldProxy和HelloWorldStubDefault类是实现类。

template <typename ... _AttributeExtensions>
class HelloWorldProxy: virtual public HelloWorld,virtual public HelloWorldProxyBase,virtual public _AttributeExtensions... {...}
​
class COMMONAPI_EXPORT_CLASS_EXPLICIT HelloWorldStubDefault: public virtual HelloWorldStub {...}

        核心层生成的HelloWorldProxy内部依赖HelloWorldProxyBase接口类的实现对象来完成功能,例如:

template <typename ... _AttributeExtensions>
void HelloWorldProxy<_AttributeExtensions...>::sayHello(std::string _name, CommonAPI::CallStatus &_internalCallStatus, std::string &_message, const CommonAPI::CallInfo *_info) {delegate_->sayHello(_name, _internalCallStatus, _message, _info);
}

delegate_的类型是 std::shared_ptr< HelloWorldProxyBase> delegate_; 这个delegate_虽然和HelloWorldProxy一样都是HelloWorldProxyBase接口类的实现类对象,但是,delegate_ 实际上是绑定层生成的Proxy类HelloWorldSomeIPProxy,同样实现了HelloWorldProxyBase接口。

        核心层生成的HelloWorldStubDefault类实现了HelloWorldStub接口类,但是属于是一个基本上的空实现,可以看到大部分函数都是无用的代码实现:

class COMMONAPI_EXPORT_CLASS_EXPLICIT HelloWorldStubDefault: public virtual HelloWorldStub {
public:...COMMONAPI_EXPORT virtual void sayHello(const std::shared_ptr<CommonAPI::ClientId> _client, std::string _name, sayHelloReply_t _reply) {(void)_client;(void)_name;std::string message = "";  // 基本上什么实现代码也没有_reply(message);}...
}

        这个类是提供给用户一个模版,用户参照这个类,自己继承后实现commonapi.HelloWorld接口中的method,broadcast和attribute对应的方法。

        此外,核心层生成的HelloWorldStub.hpp文件中,还有一个接口类HelloWorldStubAdapter,这个类是用来衔接Stub和实际中间件的,后面生成绑定层代码的时候会生成绑定层的HelloWorldSomeIPStubAdapter和HelloWorldSomeIPStubAdapterInternal类(实现HelloWorldStubAdapter接口类)

2.2 绑定层生成代码

        根据fdepl文件生成绑定层代码依赖 commonapi-someip-generator-linux-x86_64 工具来生成绑定层的代码,这些代码原来libCommonAPI-SomeIP.so 来调用绑定层的功能,最后调用到中间件层(libvsomeip.so) 完成通信功能。绑定层生成的代码实现了核心层fidl接口生成的Proxy和Stub的C++类。

        对于前面的commonapi.HelloWorld接口,在绑定层生成了HelloWorldSomeIPStubAdapter,HelloWorldSomeIPProxy和HelloWorldSomeIPDeployment这三个类型,HelloWorldSomeIPStubAdapter和HelloWorldSomeIPProxy分别实现了前面核心层生成的HelloWorldStubAdapter,HelloWorldProxyBase两个接口类:

class HelloWorldSomeIPProxy: virtual public HelloWorldProxyBase,virtual public CommonAPI::SomeIP::Proxy {...
}
​
template <typename _Stub = ::v1::commonapi::HelloWorldStub, typename... _Stubs>
class HelloWorldSomeIPStubAdapterInternal: public virtual HelloWorldStubAdapter,public CommonAPI::SomeIP::StubAdapterHelper< _Stub, _Stubs...>,public std::enable_shared_from_this< HelloWorldSomeIPStubAdapterInternal<_Stub, _Stubs...>>
{...
}
​
template <typename _Stub = ::v1::commonapi::HelloWorldStub, typename... _Stubs>
class HelloWorldSomeIPStubAdapter: public HelloWorldSomeIPStubAdapterInternal<_Stub, _Stubs...> {...
}

        这些类的代码内部调用了libCommonAPI-SomeIP.so中的接口和下面的someip中间件库libvsomeip打交道,从而实现使用some/ip通信的方式完成commonapi.HelloWorld接口的通信功能。

        此外,绑定层还生成了一个类,HelloWorldSomeIPDeployment,但是因为HelloWorld.fidl中没有定义额外的属性(attribute),用户自定义类型或者广播(broadcast),因此这个类是个空实现,如果fidl中有定义这些的话,可以看下CommonAPI中带有属性的demo,如下:

        可以看到针对fidl中定义的用户自定义结构体CommonTypes.a2Struct,生成了对应的C++结构体,其实就是用一个struct内部包了一个tuple对象,这个tuple对象保存的就是结构体中的成员,另外,由于结构体对象通过绑定层Some/IP中间件传输的时候需要做序列化和反序列化,因此额外生成了CommonTypesSomeIPDeployment.hpp和CommonTypesSomeIPDeployment.cpp,这两个文件中生成的类使用基本类型(Int, Boolean, String等)的SomeIPDeployment类组合出用户自定义的a2Struct结构体,这些SomeIPDeployment类会用于对CommonTypes.a2Struct结构体中的成员做序列化和反序列化。

三. 绑定层,核心层和中间件层的配置文件

        绑定层的配置文件如下:

[logging]
console=true
file=/tmp/capi_helloworld.log
dlt=false
level=verbose
​
[default]
binding=someip

        其中最重要的配置是[default]这个section中的binding配置,用来配置该CommonAPI应用的绑定层使用的是dbus还是some/ip,这里的绑定层使用的是some/ip。

        这个配置文件是CommonAPI::Runtime需要使用到的,通过环境变量 COMMONAPI_CONFIG 来指定该配置文件的路径:

export COMMONAPI_CONFIG=./HelloWorld-commonapi.ini

        CommonAPI核心层动态库libCommonAPI.so中通过该环境变量读取到binding配置后,就可以决定下一步加载的绑定层动态库是哪一个,例如some/ip的话就是加载libCommonAPI-SomeIP.so。

        一旦绑定层的动态库被加载进来后,绑定层的动态库也会去读取其配置文件,对于libCommonAPI-SomeIP.so,其通过 COMMONAPI_SOMEIP_CONFIG 读取配置文件:

export COMMONAPI_SOMEIP_CONFIG=./HelloWorld-commonapi-someip.ini

        核心层的配置文件如下:

# domain : service : version(v[major]_[minor] : instance)
[local:commonapi.HelloWorld:v1_0:test]
# someip service id
service=4660
# someip service instance id
instance=1
# someip service major version
major=1
# someip service minor version
minor=0

        该配置文件中,保存了CommonAPI接口的实例和Some/IP服务实例的对应关系

        CommonAPI接口的域为domain,接口为commonapi.HelloWorld:v1_0,其中1_0为接口的major和minor版本号,test为实例名

        对应的Some/IP的Service ID为4660,instance ID为1。

        libCommonAPI-SomeIP.so读取到该配置后,会将这个对应关系保存起来(保存到CommonAPI::SomeIP::AddressTranslator中)

        这个对应关系为什么需要保存起来?那是因为对于最终用户来说,他面对的是核心层的Proxy和Stub类,而不是绑定层的,因此,他不需要关系和知道绑定层的参数。但是绑定层需要知道这些参数,因为绑定层需要调用中间件的动态库来进行通信,例如他需要知道用户创建的CommonAPI接口的Proxy是要和哪个Some/IP服务进行通信,需要提供libvsomeip.so的创建application, requestService的参数。

        有了这个对应关系,核心层只需要告知绑定层用于想要创建那个接口的Proxy或者Stub,绑定层就可以找到对应中间件的参数了。

四. 流程图

相关文章:

  • Spring-MyBatis基本操作
  • rent8_wechat-最常用出租屋管理系统-微信小程序
  • 华为云Flexus+DeepSeek征文 | 基于Flexus X实例的金融AI Agent开发:智能风控与交易决策系统
  • C++题解:【入门】快乐的马里奥(BFS)
  • 从代码学习深度学习 - 预训练BERT PyTorch版
  • 【LeetCode 热题 100】15. 三数之和——排序 + 双指针解法
  • FastAPI框架的10个重要知识点总结
  • Chromium 136 编译指南 macOS篇:编译流程(五)
  • Linux进程间通信——信号
  • kibana和elasticsearch安装
  • (详细介绍)线性代数中的零空间(Null Space)
  • Git 使用手册:从入门到精通
  • MCPServer编程与CLINE配置调用MCP
  • ubuntu20.04速腾聚创airy驱动调试
  • Redis哨兵模式的学习(三)
  • 百度萝卜快跑携4颗禾赛激光雷达进军迪拜,千辆L4无人车开启全球化战略
  • Tensor的常用计算方法(torch示例说明)
  • RPC - 客户端注册和发现模块
  • Unity Addressable使用之AddressableAssetSettings
  • Java UDP Socket 实时在线刷卡扫码POS消费机服务端示例源码
  • 河北外贸网站建设/湖南seo快速排名
  • 北京网站建设公司网络营销外包网络建站报价/2021全国大学生营销大赛
  • 如何申请域名做网站知乎/万网域名注册查询
  • 潜力的网站设计制作/营销软文小短文
  • 电子工程师资格证/抖音seo怎么做的
  • 息壤备案网站客服/2024年2月疫情又开始了吗