一. 代码范例
从CommonAPI自带的HelloworldClient来示范:
int main() {
...
std::shared_ptr<HelloWorldProxy<>> myProxy =
runtime->buildProxy<HelloWorldProxy>("local", "test"); // 此处返回的是绑定层的HelloWorldSomeIPProxy,具体看buildProxy实现
CommonAPI::CallStatus callStatus;
std::string returnMessage;
myProxy->sayHello("Bob", callStatus, returnMessage);
...
}
可以看到,在上面的代码中, “Bob”是SomeIP的sayHello这个Method方法的参数,因此在发送sayHello的REQUEST报文的时候,“Bob”需要被序列化到报文的payload中,这部分工作应该是在HelloWorldSomeIPProxy的sayHello函数中完成的:
void HelloWorldSomeIPProxy::sayHello(std::string _name, CommonAPI::CallStatus &_internalCallStatus, std::string &_message, const CommonAPI::CallInfo *_info) {
CommonAPI::Deployable< std::string, CommonAPI::SomeIP::StringDeployment> deploy_name(_name, static_cast< CommonAPI::SomeIP::StringDeployment* >(nullptr)); // 输入参数name
CommonAPI::Deployable< std::string, CommonAPI::SomeIP::StringDeployment> deploy_message(static_cast< CommonAPI::SomeIP::StringDeployment* >(nullptr)); // 返回参数message
CommonAPI::SomeIP::ProxyHelper<...>::callMethodWithReply( // 调用CommonAPI Someip绑定层的接口发送REQUEST报文
*this,
CommonAPI::SomeIP::method_id_t(0x7b),
false,
false,
(_info ? _info : &CommonAPI::SomeIP::defaultCallInfo),
deploy_name, // 输入参数(Deployable类型),需要序列化到payload中
_internalCallStatus,
deploy_message);
_message = deploy_message.getValue();
}
二. 具体实现
从实例代码中看到,发送REQUEST报文依赖了SOMEIP绑定层ProxyHelper类的callMethodWithReply函数,ProxyHelper是个模板类,模板参数为SOMEIP Method的每个输入参数的类型和输出参数的类型:
template <
template <class...> class In_, class... InArgs_, // 输入参数类型集合
template <class...> class Out_, class... OutArgs_> // 输出参数类型集合
struct ProxyHelper<In_<InArgs_...>, Out_<OutArgs_...>> {
...
}
然后看callMethodWithReply的实现:
static void callMethodWithReply(
Proxy_ &_proxy,
Message &_methodCall,
const bool _isLittleEndian,
const CommonAPI::CallInfo *_info,
const InArgs_ &... _inArgs,
CommonAPI::CallStatus &_callStatus,
OutArgs_ &... _outArgs) {
...
OutputStream outputStream(_methodCall, _isLittleEndian); // 创建输出流用于序列化入参
// 注意_methodCall参数类型是Message,
// 也就是OutputStream是基于SOMEIP Message创建的
const bool success = SerializableArguments<InArgs_...>::serialize(outputStream, _inArgs...);
if (!success) {
...
return;
}
outputStream.flush(); // 将序列化后的字节流放入Message
Message reply = _proxy.getConnection()->sendMessageWithReplyAndBlock(_methodCall, _info); // 发送Message(Connection包装了vsomeip)
...
}
也就是说,序列化的工作主要是依赖了输入参数(XXXXDeployment类对象),Deployable,OutputStream以及SerializableArguments这四个类
SomeIP协议定义了可以序列化的基本类型,包括bool, unsigned int8~64, signed int8~64以及float32, float64,另外,也定义了字符串类型的序列化规则(如何写入BOM,长度和payload)。而CommonAPI则提供了对应的Deployment类来帮助实现这些类型的序列化,例如IntegerDeployment(可以同时用来处理bool和uint, int), StringDeployment,此外,还额外提供了EnumerationDeployment以及ByteBufferDeployment类用于处理枚举类型和字节流类型的序列化。
总结来说,就是Deployment类提供了不同类型的值进行序列化时依赖的一些信息(值长度,字节序,字符集编码等),本身不保存要序列化的值
Deployable类保存输入参数(要序列化的值),同时也保存参数类型的Deployment对象指针:
struct Deployable {
Deployable(const Type_ &_value, const TypeDepl_ *_depl)
: value_(_value),
depl_(const_cast<TypeDepl_ *>(_depl)) {
};
...
protected:
Type_ value_; // 参数值的拷贝
TypeDepl_ *depl_; // 该参数类型对应的Deployment(IntegerDeployment,StringDeployment,...)指针
};
对于这些Deployable类和OutputStream类来说,SerializableArguments类是他们两者间的桥梁,提供了对于序列化和反序列化最重要的两个函数: serialize和deserialize。
template < typename ArgumentType_ > // 只有一个待序列化参数的模板类,对应只有一个参数的payload的序列化
struct SerializableArguments< ArgumentType_ > {
static inline bool serialize(OutputStream &_output, const ArgumentType_ &_argument) {
_output << _argument; // 这个操作符定义在核心层OutputStream.hpp中(另有一个OutputStream.hpp在SomeIP子目录中)
return !_output.hasError();
}
...
};
template < typename ArgumentType_, typename ... Rest_ > // 每个模板参数对应一个payload中的参数的类型,会递归调用自己以及上面单参数版本
struct SerializableArguments< ArgumentType_, Rest_... > {
static inline bool serialize(OutputStream &_output, const ArgumentType_ &_argument, const Rest_ &... _rest) {
_output << _argument;
const bool success = !_output.hasError();
return success ? SerializableArguments<Rest_...>::serialize(_output, _rest...) : false;
}
...
};
SerializableArguments类将输入的需要序列化到Payload中的Deployment参数序列化到OutputStream对象中,这是依赖一系列全局 << 操作符重载来实现的,<< 操作符函数的定义位于绑定层的OutputStream.hpp中,其中最重要的一个重载的版本是以OutputStream和Deployable为参数的实现:
template<class Derived_, typename Type_, typename TypeDepl_>
/**
* _output: OutputStream类型参数,用于保存最终序列化的字节流,并且提供write操作用于写入成员
* _value: Deployable类型参数,
inline OutputStream<Derived_> &operator<<(OutputStream<Derived_> &_output, const Deployable<Type_, TypeDepl_> &_value) {
// getValue获取要序列化的值
// getDepl获取该值类型序列化的所依赖的信息(Deployment)
return _output.template writeValue<TypeDepl_>(_value.getValue(), _value.getDepl());
}
其中writerValue函数是OutputStream类内部的重载成员函数,下面是其中一个重载的版本:
template<int minimum, int maximum>
COMMONAPI_EXPORT OutputStream &writeValue(const RangedInteger<minimum, maximum> &_value, const IntegerDeployment<int> *_depl) {
if (_value.validate()) {
_writeBitValue(_value.value_,(_depl ? _depl->bits_ : 32), true);
}
else
errorOccurred_ = true;
return (*this);
}
三. 总结
简单来说,CommonAPI绑定SomeIP做通信时,参数的序列化流程如下:
/CommonAPI/SomeIP/ProxyHelper.hpp -> callMethod
/CommonAPI/SerializableArguments.hpp -> serialize
/CommonAPI/OutputStream.hpp -> &operator<< -> _output.template writeValue
/CommonAPI/OutputStream.hpp -> writeValue
/CommonAPI/SomeIP/OutputStream.hpp -> writeValue