使用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,绑定层就可以找到对应中间件的参数了。