CommonAPI C++是用于开发分布式应用程序的标准C++ API规范,该分布式应用程序通过中间件进行进程间通信。
CommonAPI C++依靠FrancaIDL来描述静态接口,根据通信协议部署参数,一起组建完整的实例依赖关系模型。目的是封装通信协议和相邻的中间件,使应用程序的C++接口独立于底层IPC堆栈。
也就是IPC Common API允许针对开发的应用程序(即使用C++的客户端和服务器)可以与不同的IPC后端链接(someip,或D-Bus),而无需更改应用程序代码。因此,为使用特定IPC X(例如someip)的系统开发的组件可以轻松地部署到另一个使用IPC Y(例如D-Bus)的系统,只需要交换IPC Common API后端(someip或D-Bus),而无需重新编译应用程序代码。
实际的接口定义将使用Franca IDL创建(*.fild文件)。
而各项部署根据部署文件定义(*.fdepl文件)。
CommonAPI的用户API分为两部分:
这张图片更详细地显示了CommonAPI C++的元素如何组合在一起。注意:
CommonAPI需要基本的工作流程才能创建可执行的应用程序。
创建具有方法和属性的接口规范的FrancaIDL文件。
通过启动CommonAPI代码生成器为客户端和服务端生成代码。
通过实现所生成框架中的方法来实现服务;或设置为默认实现。
在应用程序中通过创建proxy并使用proxy调用这些方法来实现客户端。
具体的工作流程参考例子:https://at.projects.genivi.org/wiki/pages/viewpage.action?pageId=5472316#CommonAPIC++D-Busin10minutes(fromscratch)-Step1:Preparation/Prerequisites
CommonAPI可执行文件通常由6部分组成:
可以将这6部分划分为共享库或静态库并将它们集中到目标平台上。
现在将在创建proxy的确切时间加载粘合代码库。通过CommonAPI配置文件可以找到正确的库,该配置文件包含CommonAPI地址和粘合代码库之间的关联。如果配置文件中没有条目,则使用默认设置。
胶水代码库是binding特定的;这意味着所需的运行库由运行时链接程序自动加载。
第一部分是由Common
API代码生成器生成的基于Franca的部分,也就是根据*.fidl文件生成的部分。那是接口的一部分,它是根据Franca
IDL文件中的规范生成的,指数据类型,数组,枚举和接口等基础知识,包含属性,方法,回调,错误处理,广播等方面。
第二个固定部分,是基于CommonAPI
Runtime的功能,且独立于接口的规范。它们主要与基于中间件(someip/d-bus)提供的运行时环境有关。此外,此部分包含常见的类型定义和基类。也就是CommonAPI源代码库与所选中间件(someip/d-bus)的源代码库,供代码生成器生成的代码调用。
通用API运行时是从其开始所有类加载的基类。通用API运行时访问配置文件,以确定应加载哪个特定的中间件运行时库。中间件库是静态链接的,或者是作为共享库(文件扩展名.so)提供的,因此可用动态加载它们。
类别1 | 类别2 | 说明 | 举例 |
---|---|---|---|
package | 限定包名,与命名空间(namespace)有关,必须包含 | Package commonapi.examples | |
interface | 接口,提供一个接口名称,用于包含接口函数等,当定义接口时需要 | Interface Methods | |
version | 版本号,同命名空间(namespace)有关,必须包含 | version{major 0 minor 1} | |
method | 方法,定义供应用程序所调用的输入输出接口函数,具有in,out,error三种参数,这三种参数都是可选的,可以只有in和out,或只有in等多种组合。 | method foo { in { Int32 x1 String x2 } out { Int32 y1 String y2} error{ stdErrorTypeEnum } } | |
broadcast | 广播,定义供应用程序调用的广播类接口函数;只有out参数。 | broadcast myStatus { out { Int32 myCurrentValue } } | |
attribute | 属性,对各种属性进行定义,可用于获取或设置属性值 | attribute Int32 x | |
typedef | 定义类型别名 | typedef MyType is Int32 | |
array | 数组 | array myArray of UInt16 | |
enumeration | 枚举类型 | enumeration stdErrorTypeEnum { NO_FAULT MY_FAULT } | |
union | 联合类型 | union MyUnion {UInt32 MyUIntString MyString} | |
struct | 结构体 | struct a2Struct { Int32 a Boolean b Double d} | |
map | 一种STL关联容器,以一种键值-对的机制存储数据 | map MyMap {UInt32 to String} | |
typeCollection | 类型集合,用户根据自己的需要添加的数据类型的集合,各种数据结构在同一个文件中进行扩展等 | typeCollection CommonTypes | |
version | 同interface::version | 同interface::version | |
typedef | 同interface::typedef | 同interface::typedef | |
array | 同interface::array | 同interface::array | |
enumeration | 同interface::enumeration | 同interface::enumeration | |
union | 同interface::union | 同interface::union | |
struct | 同interface::struct | 同interface::struct | |
map | 同interface::map | 同interface::map |
Common API基本函数的名称空间是Common API;Common API应用程序的名称空间取决于Franca IDL中定义接口规范和接口版本的限定包名。
在名称空间的开头添加版本的主要原因是,我们将接口的名称以及包路径(完全限定名称)作为一个单元,这是在Franca文件中指定的。此名称空间不应被中间的附加内容破坏。
Franca IDL
CommonAPI C++
namespace 在每个文件的开头都有,代表这个文件所在的src-gen下面的路径,每个namespace代表一层目录;
如果接口没有版本,则省略名称空间中与版本有关的部分(例如图中的namespace v0)。
Version {major 1 minor 0} -- namespace v1 { }
Version {major 0 minor 1} -- namespace v0 { }
Version {major 0 minor 0} -- 无与版本相关的namespce
对应Franca接口名称(称为interfacename),将interfacename生成为一个提供getInterfaceName()方法和getInterfaceVersion()方法的类。版本映射到CommoAPI::Version。
Franca IDL
CommonAPI C++
版本结构的规范是CommonAPI的一部分:
在Franca中,可以将一组用户定义的类型定义为type collection。类型集合的名称称为typecollectionname,可以为空。CommonAPI为空类型集合使用默认名称Anonymous。CommonAPI代码生成器生成头文件Anonymous.hpp,并为类型集合创建C++ struct。如果类型集合的名称不为空(例如:CommonTypes),则CommonAPI代码生成器生成头文件CommonTypes.hpp。
生成的函数类似于接口interface。
Franca IDL
CommonAPI C++
注意:在内部Franca模型中,类型集合是接口的基类。类型集合也可以有一个版本。在这种情况下,名称空间就像生成的版本名称一样被扩展。
方法具有in和out参数,并且可以返回可选的应用程序错误error。如果指定了附加标志fireAndForget,则不允许使用out参数,它指示既不返回值也不表示调用状态。没有fireAndForget标志的方可能返回error,可以在Franca IDL中将其指定为枚举。
对于没有fireAndForget标志的方法,提供了一个附加的返回值CallStatus,它被定义为枚举:
在CallStatus定义了呼叫的传输层的结果,即它返回:
不鼓励在没有定义超时的情况下发送任何方法调用。可以通过将可选参数CallInfo传递给方法调用或在CommonAPI部署文件中配置超时。
该结构包含一个附加成员sender_,该成员可用于标识此函数的调用方。
标志 | 参数 | 说明 |
---|---|---|
None | In + out + error(可选) | 提供一个附加的返回值CallStatus |
fireAndForget | in | 不允许使用out参数,指示既不返回也不表示调用状态 |
Franca IDL
Method CommonAPI C++
Proxy
返回类型 | 函数名称 | 参数 |
---|---|---|
virtual void | foo | const int32_t &_x1, const std::string &_x2, CommonAPI::CallStatus &_internalCallStatus, Methods::fooError &_error, int32_t &_y1, std::string &_y2, const CommonAPI::CallInfo *_info = nullptr |
virtual std::futureCommonAPI::CallStatus | fooAsync | const int32_t &_x1, const std::string &_x2, fooAsyncCallback _callback = nullptr, const CommonAPI::CallInfo *_info = nullptr |
virtual void | newFoo | const std::string &_MessageName, CommonAPI::CallStatus &_internalCallStatus |
在Franca IDL中,方法的异步或同步调用之间没有区别。CommonAPI将同时提供两者。API的用户可以决定他调用哪个变体。含有fireAndForget标志的method,只有同步调用,没有异步调用。
在proxy端生成的函数调用:
Method CommonAPI C++
Stub
返回类型 | 函数名称 | 参数 |
---|---|---|
virtual void | foo | const std::shared_ptrCommonAPI::ClientId _client, int32_t _x1, std::string _x2, fooReply_t _reply |
virtual void | newFoo | const std::shared_ptrCommonAPI::ClientId _client, std::string _MessageName |
在stub端,生成的函数是生成的stub的一部分,这些功能是纯虚函数的。这意味着必须提供一个实现。此实现的框架可以由代码生成器生成。函数调用的返回值包装在一个函数对象中:
typedef std::functionfooReply_t;
这允许它将此对象传递给其他函数,以便在stub端实现异步行为。
在stub端,传递了ClientId类型的附加参数。ClientId标识向stub发送调用的客户端。它用于在stub中标识调用者,并且应该由中间件添加,可以使用==操作符进行比较。
广播只能有out参数。对于广播,可以定义一个附加标志selective。该标志指示该消息不应该发送给所有注册的参与者,而是该服务进行选择,表示只有选定的客户端才能在广播中注册。
标志 | 参数 | 说明 |
---|---|---|
None | Out | 表示发送给所有注册的参与者 |
Selective | Out | 该标志表示只有服务选定的客户端才能在广播中注册 |
Franca IDL
Broadcast CommonAPI C++
Proxy
返回类型 | 函数名称 | 参数 |
---|---|---|
virtual MyStatusEvent & | getMyStatusEvent | |
virtual StatusSpecialSelectiveEvent & | getStatusSpecialSelectiveEvent |
这些方法返回一个事件的包装类,该事件提供对广播MyStatus的访问。包装类提供订阅和取消订阅的方法。Private属性delegate_用于将函数调用转发到特定的绑定(也就是用于与someip协议关联)。
Broadcast CommonAPI C++
Stub
返回类型 | 函数名称 | 参数 | 说明 |
---|---|---|---|
virtual void | fireMyStatusEvent | const int32_t &_myCurrentValue | |
virtual void | fireStatusSpecialSelective | const int32_t &_NewCurrentValue, const std::shared_ptrCommonAPI::ClientIdList _receivers = nullptr | |
virtual std::shared_ptrCommonAPI::ClientIdList const | getSubscribersForStatusSpecialSelective | ||
virtual void | onStatusSpecialSelectiveSubScriptionChanged | conststd::shared_ptrCommonAPI::ClientId _client, const CommonAPI::SelectiveBroadcastStubscriptionEvent _event | 钩子方法,分别对选择性广播的新订阅或已删除订阅作出反应 |
virtual bool | onStatusSpecialSelectiveSubscriptionRequest | const std::shared_ptrCommonAPI::ClientId _client | 钩子方法,用于响应接受或拒绝新订阅生成的stub提供触发广播(fire*())的方法和一些挂钩请注意,通过使用ClientId和提供的钩子,Franca关键字selective仅在stub端实现 |
接口的属性由名称和类型定义。另外,属性的规范可以具有两个标志:
·noSubscriptions
·readonly
标志的组合有四种可能:
标志 | 说明 |
---|---|
none | 没有附加任何标志的标准属性,默认允许所有内容 |
readonly | 只读属性 |
noSubscriptions | 不可观察的属性 |
readonly onSubscriptions | 不可观察和不可写的属性 |
可观察的属性提供了一个ChangedEvent,可用于订阅对该属性的更新。此事件与所有其他事件完全一样。
头文件Attribute.h中定义了这四种类型的每种属性的模板类。
Franca IDL
CommonAPI C++
Proxy
返回类型 | 函数名称 | 参数 |
---|---|---|
virtual XAttribute& | getXAttribute |
仅考虑属性时,stub端属性的CommonAPI如上,该get函数必须由应用程序实现。
Stub
返回类型 | 函数名称 | 参数 |
---|---|---|
virtual const int32_t & | getXAttribute | const std::shared_ptrCommonAPI::ClientId _client |
此外,CommonAPI定义了必要的回调来处理与IDL描述中为interface定义的属性相关的远程设置事件。对于每个属性,在类AttributesStubRemoteEvent中定义了两个回调:
StubRemoteEvent
返回类型 | 函数名称 | 参数 | 说明 |
---|---|---|---|
virtual bool | onRemoteSetXAttribute | const std::shared_ptrCommonAPI::ClientId_client, int32_t _value | 验证回调 |
virtual void | onRemoteXAttributeChanged | 操作回调 |
类AttributesStubAdapter提供了一个用于发送广播和可观察属性的更改通知的API,这个API可以在设置属性时调用:
StubAdapter
返回类型 | 函数名称 | 参数 | 说明 |
---|---|---|---|
virtual void | fireXAttributeChanged | const int32_t& x | 用于发送广播和可观察属性的更改通知 |
CommonAPI代码生成器生成的stub的默认实现将属性定义为stub类的私有属性。可以通过getter和setter函数从stub实现中访问此属性。此外,用于stub实现的API提供了一些回调:
StubDefault
返回类型 | 函数名称 | 参数 | 说明 |
---|---|---|---|
virtual const int32_t& | getXAttribute | 获取属性值 | |
virtual const int32_t& | getXAttribute | const std::shared_ptrCommonAPI::ClientId _client | 获取属性值 |
virtual void | setXAttribute | int32_t _value | 在stub实现时更改属性值 |
virtual void | setXAttribute | const std::shared_ptrCommonAPI::ClientId _client, int32_t _value | 在stub实现时更改属性值 |
virtual bool | trySetXAttribute | int32_t _value | 从客户端更改给定值 |
virtual bool | validateXAttributeRequestedValue | const int32_t & _value | 阻止设置属性的回调函数 |
virtual void | onRemoteXAttributeChanged | 通知该属性已更改的回调函数 |
CommonAPI提供了属性接口的基本实现和扩展机制。所谓扩展机制,就是因为根据应用程序要求属性个数的不同,而存在的一种通用方案,其中包括单个扩展,以便为属性提供任何其他功能(属性扩展)。这将防止开发人员突然添加属性,而缓存不足的情况。
扩展的基类定义在AttributeExtension.hpp中。
事件为远程触发的动作提供了一个异步接口。这涵盖了FrancaIDL中的广播,方法和属性的更改,事件每个proxy还提供了一个可用性事件,可用于通知proxy状态。事件提供了订阅和取消订阅的方法,该方法允许注册和注销回调。
事件类的公共接口的相关部分如下:
订阅后,调用listener进行侦听,然后在出现新事件(例如,属性已更改)的任何时候被调用。
CommonAPI使用的整数数据类型在stdint.h中定义。
Franca Type Name | CommonAPI C++ Type | Notes |
---|---|---|
UInt8 | uint8_t | unsigned 8-bit integer(range 0…255) |
Int8 | int8_t | signed 8-bit integer(range -128…127) |
UInt16 | uint16_t | unsigned 16-bit integer(range 0…65535) |
Int16 | int16_t | signed 16-bit integer(range -32768…32767) |
UInt32 | uint32_t | unsigned 32-bit integer(range 0…4294967295) |
Int32 | int32_t | signed 32-bit integer(range -2147483648…2147473647) |
UInt64 | uint64_t | unsigned 64-bit integer |
Int64 | int64_t | signed 64-bit integer |
Boolean | bool | boolean value, which can take one of two values: false or true |
Float | float | Floating point number(4 bytes, range +/3.4e+/-38, ~7 digits). |
Double | double | double precision floating point number(8 bytes, range+/-1.7e+/-308, ~15 digits). |
String | std::string | character string. |
ByteBuffer | std::vector |
buffer of bytes(aka BLOB). |
Franca只有一种字符串数据类型String,并且如有必要,可以通过部署模型(也就是*.fdepl文件)指定线路格式/编码。
Franca数组类型(可以以显式和隐式两种方法表示)映射到std::vector。虽然可以使用typedef 将Franca IDL中给出的名称显式定义为数组类型,但隐式版本将仅在需要是生成std::vector。
Franca IDL
CommonAPI C++
Franca struct类型映射到C++ struct类型。
Structures映射到从CommonAPI::Struct继承的struct。CommonAPI::Struct将结构化数据保存在tuple中。生成的类为结构成员提供getter和setter方法。
Franca IDL
CommonAPI C++
Franca枚举将映射到从基类继承的C++结构CommonAPI::Enumeration。默认情况下,Enum支持的数据类型和连接格式为uint32_t。如果需要,可以通过CommonAPI部署文件*.fdepl文件(枚举支持类型)指定连接格式。
Franca IDL
CommonAPI C++
出于效率原因,Franca映射的Common API数据类型为std::unordered_map
Franca IDL
CommonAPI C++
Franca 中定义的联合类型被实现为CommonAPI通用模板化C++ variant类的类型定义。
Franca IDL
CommonAPI C++
Franca typedef映射到C++ typedef。
Franca IDL
CommonAPI C++
CommonAPI代码生成器几乎支持FrancaIDL的全部功能,并且无需任何部署文件即可工作。但是,可以根据以下部署规范,不仅为绑定而且为CommonAPI本身编写接口规范的部署文件。
可以在CommonAPI C++级别上设置枚举支持类型,不仅针对单个枚举,而且通常针对整个接口。请注意,绑定也可能具有与枚举的支持类型有关的部署设置。
也可以定义函数调用的超时时间。设置此超时的另一种可能性是在方法调用的可选参数中对CallInfo进行定义。
代码生成器不评估实例和提供者的设置。
有关部署参数的用法,请参见下方,CommonAPI本身的所有部署参数都是可选的。
类别1 | 类别2 | 说明 | 举例 |
---|---|---|---|
for interface | 对接口进行一些部署,设置ServiceID值,也可以设置枚举支持的类型 | *Reliable = false表示使用UDP协议,Reliable = true表示使用TCP协议 | |
attribute | 为属性的getter, setter等方法提供ID值 | attribute x { SomeIpGetterID = 3000 SomeIpSetterID = 3001 SomeIpNotifierID = 33010 SomeIpEventGroups = { 33010 } SomeIpGetterReliable = false SomeIpSetterReliable = false SomeIpNotifierReliable = false} | |
method | 设置method的ID值,并设置输入输出字符串类型的编码/解码格式;也可以订阅方法调用的超时 | method foo { SomeIpMethodID = 30000 SomeIpReliable = false in { x2 { SomeIpStringEncoding = utf16le } } out { y2 {SomeIpStringEncoding =utf16le} } } | |
broadcast | 设置广播事件以及广播事件组的ID值,并设置输出字符串类型的编码/解码格式 | broadcast myStatus { SomeIpEventID = 33020 SomeIpEventGroups = { 33020 } } | |
array | 定义数组的长度 | SomeIpArrayLengthWidth = 2 | |
enumeration | 设置枚举的数据类型 | EnumBackingType = UInt64 | |
for provider | 提供实例 | ||
instance | 设置实例名称以及实例ID值,并设置实例的地址和端口号 | instance commonapi.mthd.Method { InstanceId = “commonapi.mthd.Method” SomeIpInstanceID = 22136 SomeIpUnicastAddress = “192.168.0.2” SomeIpReliableUnicastPort = 30500 SomeIpUnreliableUnicastPort = 30501 } | |
for typeCollection | 用户自己定义的各种数据类型的集合 | ||
array | 定义数组的长度 | SomeIpArrayLengthWidth = 2 | |
enumeration | 设置枚举的数据类型 | EnumBackingType = UInt64 |
for interface,对应于*.fidl文件中的interface,在这里面主要是配置中间件的ServiceID,以及其他method,broadcast,attribute等使用的methodID,eventID值等。
设置ServiceID:
SomeIpServiceID = 4660
也可以在此定义整个接口的CommonAPI C++级别上定义枚举支持的类型,默认是UInt32:
DefaultEnumBackingType: { UInt8, UInt16, UInt32, UInt64, Int8, Int16, Int32, Int64 } (default: UInt32);
例如:
DefaultEnumBackingType = UInt8
为每个属性所使用的getter, setter等方法设置ID值,并设置这些方法的可靠性。
为方法method设置methodID值,并设置可靠性,也可以为in, out的输入输出参数的字符串类型设置编码/解码格式,也可以不设置,从而使用默认设置。
也可以在此处设置超时:
Timeout : Integer (default: 0);
例如:
Timeout = 1
为broadcast事件设置EventID值,Event Groups值(这个是将自己想要划分为一组的事件ID写在一起),设置可靠性,也可以定义out参数里面字符串类型的编码/解码格式。
根据*.fidl文件中使用的数组的个数,来设置数组的长度。
例如,当*.fidl中使用了两个数组:
attribute myArray myArraySpecific
attribute myArray myArrayDefault
在*.fdepl部署文件中将数组的长度定义为2:
array myArray {
SomeIpArrayLengthWidth = 2
}
可以在此设置枚举使用的数据类型:
EnumBackingType : {UInt8, UInt16, UInt32, UInt64, Int8, Int16, Int32, Int64} (optional);
例如:
EnumBackingType = UInt64
在此提供程序所依赖的所有服务实例(如果有),并为实例设置名称,ID值以及IP地址和端口号。
开发CommonAPI应用程序的第一步可能是客户端将用于与服务器通信的接口的定义。在CommonAPI的上下文中,无论最终打算使用哪种通信机制,此接口的定义始终通过Franca IDL进行。
根据*.fidl文件与*.fdepl文件生成的代码文件,具体分为以下几部分,以及各部分的功能。
**.fidl文件生成的接口代码 | ||
---|---|---|
HelloWorld.hpp | 用于客户端开发:代理是一个提供方法调用的类,该方法调用将导致对服务的远程方法调用,以及服务可以广播的事件的注册方法。 | |
HelloWorldProxy.hpp | ||
HelloWorldProxyBase.hpp | ||
HelloWorldStub.hpp | 服务器开发:存根是服务的一部分,当来自客户端的远程方法调用到达时,存根将被调用,它还包含将事件(广播)激发到几个或所有客户端的方法。 | |
HelloWorldStubDefault.hpp | ||
HelloWorldStubDefault.cpp | ||
*.fdepl文件生成的粘合代码 | 名称中具有绑定名称(例如someip)的所有文件都是绑定所需的粘合代码,并且在开发应用程序时不相关,它们仅需与应用程序一起编译 | |
HelloWorldSomeIPDeployment.hpp | ||
HelloWorldSomeIPDeployment.cpp | ||
HelloWorldSomeIPProxy.cpp | ||
HelloWorldSomeIPProxy.hpp | ||
HelloWorldSomeIPStubAdapter.hpp | ||
HelloWorldSomeIPStubAdapter.cpp |
这属于代理/存根结构,通过这张流程图,很容易发现CommonAPI实现IPC其实是在原来的C/S框架上加入了代理/存根结构。
具体的编写参考例子:https://blog.csdn.net/C_Silence_K/article/details/104674945
1.CommonAPI+someip的配置与使用,操作流程,用HelloWorld演示
https://at.projects.genivi.org/wiki/pages/viewpage.action?pageId=5472311
2.CommonAPI C++规范
https://docs.projects.genivi.org/ipc.common-api-tools/3.1.3/html/CommonAPICppSpecification.html
3.CommonAPI C++用户指南
https://docs.projects.genivi.org/ipc.common-api-tools/3.1.3/html/CommonAPICppUserGuide.html
4.CommonAPI C++的使用例子
https://github.com/GENIVI/capicxx-core-tools/tree/master/CommonAPI-Examples