CommonAPI如何完成参数的序列化

一. 代码范例

​ 从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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值