CommonAPI学习笔记-2

一. 概述

​ 这篇文章主要是想整理并且分析CommonAPI代码生成工具根据fidl和fdepl配置文件生成出来的代码的结构和作用。

二. fidl

​ 用户根据业务需求在fidl文件中定义业务服务接口的结构以及自定义数据类型,然后使用core生成工具传入fidl文件生成该fidl的核心层Proxy和Stub以及配套的相关代码。

​ 例如官方范例上的HelloWorld(https://github.com/COVESA/capicxx-core-tools/blob/master/CommonAPI-Examples/E01HelloWorld/fidl/E01HelloWorld.fidl),使用生成工具commonapi-core-generator-linux-x86_64输入该fidl文件可以生成HelloWorld.hpp,HelloWorldProxy.hpp,HelloWorldProxyBase.hpp,HelloWorldStub.hpp,HelloWorldStubDefault.hpp这5个代码文件。

2.1 interface

​ 一个fidl文件对应一个CommonAPI的interface,对于C++来说,要定义一个interface,当然是使用一个抽象类来定义了,这个抽象类就是HelloWorld.hpp文件中的HelloWorld类了。

namespace v1 {
namespace commonapi {class HelloWorld {
public:virtual ~HelloWorld() { }static inline const char* getInterface();                  // HelloWorld业务的CommonAPI接口名称static inline CommonAPI::Version getInterfaceVersion();    // HelloWorld业务的CommonAPI接口版本
};const char* HelloWorld::getInterface() {return ("commonapi.HelloWorld:v1_0");
}CommonAPI::Version HelloWorld::getInterfaceVersion() {return CommonAPI::Version(1, 0);
}} // namespace commonapi
} // namespace v1

​ 当看到core工具生成的HelloWorld接口类,感觉有点奇怪,这个HelloWorld接口类只定义并且实现了两个关于接口信息的函数(接口名称和接口版本信息),而fidl中关于HelloWorld接口中的业务函数sayHello在生成的HelloWorld接口类中确没有

// HelloWorld.fidl
package commonapiinterface HelloWorld {version {major 1 minor 0}method sayHello {    // 该函数没有生成在HelloWorld接口类中in {String name}out {String message}}
}

​ 对于为什么生成的HelloWorld接口类中没有定义sayHello函数的原因,个人阅读完生成的代码后理解是,由于HelloWorld.fidl生成的核心层Proxy以及Stub代码在sayHello函数上的定义是不兼容的,而HelloWorld接口类又是核心层HelloWorldProxy和HelloWorldStub的父类,因此无法将sayHello函数的申明放入HelloWorld接口类中。

​ HelloWorld接口类中的getInterface和getInterfaceVersion函数是为了给CommonAPI核心层代码库创建核心层Proxy/Stub的时候使用的,代码如下:

// 代码文件/usr/local/include/CommonAPI-3.2/CommonAPI/Runtime.hpp
template<template<typename ...> class ProxyClass_, typename ... AttributeExtensions_>COMMONAPI_EXPORT std::shared_ptr<ProxyClass_<AttributeExtensions_...>>buildProxy(const std::string &_domain,const std::string &_instance,const ConnectionId_t &_connectionId = DEFAULT_CONNECTION_ID) {std::shared_ptr<Proxy> proxy= createProxy(_domain,//这里的ProxyClass就是HelloWorldProxy类,调用HelloWorldProxy的父类HelloWorld中的getInterfaceProxyClass_<AttributeExtensions_...>::getInterface(),   _instance,_connectionId);if (proxy) {return std::make_shared<ProxyClass_<AttributeExtensions_...>>(proxy);}return nullptr;}

2.2 Proxy

​ 在核心层生成了HelloWorldProxy.hpp和HelloWorldProxyBase.hpp两个代码文件,里面分别定义了HelloWorldProxy和HelloWorldProxyBase两个类。

​ 其中,HelloWorldProxyBase是HelloWorldProxy的一个父类,其中包含了fidl中sayHello函数的相关定义(包括了同步调用和异步调用两种)

class HelloWorldProxyBase: virtual public CommonAPI::Proxy {
public:typedef std::function<void(const CommonAPI::CallStatus&, const std::string&)> SayHelloAsyncCallback; // 异步调用的回调通知接口virtual void sayHello(std::string _name, CommonAPI::CallStatus &_internalCallStatus, std::string &_message, const CommonAPI::CallInfo *_info = nullptr) = 0;  // 同步调用接口virtual std::future<CommonAPI::CallStatus> sayHelloAsync(const std::string &_name, SayHelloAsyncCallback _callback = nullptr, const CommonAPI::CallInfo *_info = nullptr) = 0;  // 异步调用接口virtual std::future<void> getCompletionFuture() = 0;
};

HelloWorldProxy类中实现了HelloWorldProxyBase父类中定义的sayHello相关接口:

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);
}

​ 可以看到,HelloWorldProxy内部实现fidl中定义的接口时,还依赖了一个delegate_对象,这个delegatge_对象是HelloWorldProxyBase类型的共享指针

template <typename ... _AttributeExtensions>
class HelloWorldProxy: virtual public HelloWorld,virtual public HelloWorldProxyBase,virtual public _AttributeExtensions... {public:...private:std::shared_ptr< HelloWorldProxyBase> delegate_;
};

​ 这里就有点不对了,前面说过HelloWorldProxy的一个父类就是HelloWorldProxyBase,难道HelloWorldProxy对象内部的这个delegate_共享指针是指向自己的?当然不是,在src-gen目录下查找还有没有其他类是HelloWorldProxy,结果看到HelloWorldProxyBase类也是HelloWorldSomeIPProxy类的父类,HelloWorldSomeIPProxy是通过commonapi-someip-generator-linux-x86_64工具生成的绑定层的Proxy类。

​ 也就是说核心层的Proxy是依赖绑定层的Proxy的,绑定层的内部会产生依赖所绑定中间件的代码来实现fidl中定义的接口函数。

2.3 Stub

​ 在核心层生成了HelloWorldStub.hpp,HelloWorldStubDefault.hpp这两个代码文件,其中包含HelloWorldStubAdapter,HelloWorldStubRemoteEvent, HelloWorldStub以及HelloWorldStubDefault这四个类,这四个类的关系如下:

​ 首先是HelloWorldStubAdapter类,其继承于CommonAPI::StubAdapter(实现了CommonAPI地址提供接口getAddress)和HelloWorld(实现了接口信息提供方法getInterfacegetInterfaceVersion)。

​ 所谓CommonAPI地址,包含三个成员(domain, interface, instance),这三个成员都是字符串:

namespace CommonAPI {
class Address {
public:COMMONAPI_EXPORT Address();...private:std::string domain_;     // 所在域std::string interface_;   // 接口名称std::string instance_;    // 实例名称friend COMMONAPI_EXPORT std::ostream &operator<<(std::ostream &_out, const Address &_address);
};
}

CommonAPI::StubAdapter中CommonAPI地址的来源是绑定层的StubAdapter(CommonAPI::SomeIP::StubAdapter)在初始化的时候赋值的

// Stub.hpp
namespace CommonAPI {class StubAdapter {public:virtual ~StubAdapter() {}inline const Address &getAddress() const { return address_; }protected:Address address_;  // 在绑定层的StubAdapter中赋值};
}// StubAdapter.cpp
namespace CommonAPI {
namespace SomeIP {
class StubAdapter: virtual public CommonAPI::StubAdapter, public InterfaceHandler {   // 继承自核心层的StubAdapter
...
void
StubAdapter::init(std::shared_ptr<StubAdapter> instance) {(void) instance;// AddressTranslator保存了绑定层地址和核心层地址的映射关系// 这个映射关系在绑定层StubAdapter的initializer函数中插入的AddressTranslator::get()->translate(someipAddress_, address_);  // address_是CommonAPI::StubAdapter中的成员变量
}
...
}
}

​ 其次是HelloWorldStubRemoteEvent类,这个类目前基本是空的,因为这是个和属性相关的类,如果你的fidl中没有定义属性成员,那么生成的StubRemoteEvent类可能就是空的,对照官网上https://github.com/COVESA/capicxx-core-tools/tree/master/CommonAPI-Examples/E02Attributes这个带属性的范例生成的代码可以看到,有属性的fidl生成的StubRemoteEvent类是定义了一些属性相关的接口的,例如:

class E02AttributesStubRemoteEvent
{
public:virtual ~E02AttributesStubRemoteEvent() { }/// Verification callback for remote set requests on the attribute xvirtual bool onRemoteSetXAttribute(const std::shared_ptr<CommonAPI::ClientId> _client, int32_t _value) = 0;/// Action callback for remote set requests on the attribute xvirtual void onRemoteXAttributeChanged() = 0;/// Verification callback for remote set requests on the attribute a1virtual bool onRemoteSetA1Attribute(const std::shared_ptr<CommonAPI::ClientId> _client, ::v1::commonapi::examples::CommonTypes::a1Struct _value) = 0;/// Action callback for remote set requests on the attribute a1virtual void onRemoteA1AttributeChanged() = 0;
};

​ 也就是说,生成的这个StubRemoteEent类是个关于事件通知的接口类,是需要用户自己来实现内部接口来接收属性的变化通知。

​ 接者是HelloWorldStub类,这也是个接口类,继承自CommonAPI::Stub,CommonAPI::Stub最重要的工作是和CommonAPI::StubHelper打交道

namespace CommonAPI {
template<typename StubAdapter_, typename StubRemoteEventHandler_>
class Stub: public virtual StubBase {
public:// 初始化StubAdaptervirtual StubRemoteEventHandler_* initStubAdapter(const std::shared_ptr<StubAdapter_> &_stubAdapter) = 0;// 返回StubAdapterinline const std::shared_ptr<StubAdapter_> getStubAdapter() const { return stubAdapter_.lock(); }protected:std::weak_ptr<StubAdapter_> stubAdapter_;

​ 因此,HelloWorldStub类包含了初始化和返回StubAdapter的接口,此外还增加了fidl中sayHello接口的申明,也就是说HelloWorldStub接口类的实现类至少要实现initStubAdapter,getStubAdapter和sayHello三个接口。

​ 最后就是HelloWorldStubDefault类,它就是实现上面HelloWorldStub接口类中三个接口的实现类,代码如下:

class COMMONAPI_EXPORT_CLASS_EXPLICIT HelloWorldStubDefault: public virtual HelloWorldStub {
public:COMMONAPI_EXPORT HelloWorldStubDefault(): remoteEventHandler_(this),interfaceVersion_(HelloWorld::getInterfaceVersion()) {}COMMONAPI_EXPORT const CommonAPI::Version& getInterfaceVersion(std::shared_ptr<CommonAPI::ClientId> _client) {(void)_client;return interfaceVersion_;  // 返回接口版本}COMMONAPI_EXPORT HelloWorldStubRemoteEvent* initStubAdapter(const std::shared_ptr< HelloWorldStubAdapter> &_adapter) {CommonAPI::Stub<HelloWorldStubAdapter, HelloWorldStubRemoteEvent>::stubAdapter_ = _adapter;return &remoteEventHandler_;}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);}...

​ HelloWorldStubDefault类是一个默认的生成的Stub接口实现类,里面的函数实现大多数是无用的,用户需要在HelloWorldStubDefault这个生成的默认Stub实现类上再做一次实现(继承并且实现),例如代码实例中的HelloWorldStubImpl类那样。

三. fdepl

​ fdepl文件生成绑定层的代码,会生成HelloWorldSomeIPProxyHelloWorldSomeIPStubAdapterInternalHelloWorldSomeIPStubAdapter这几个绑定层的类。

3.1 Proxy

HelloWorldSomeIPProxy类是绑定层生成的Proxy类,其构造时需要传入绑定层地址(CommonAPI::SomeIP::Address),CommonAPI::SomeIP::Address包含SomeIP服务实例的信息:

namespace CommonAPI {
namespace SomeIP {class COMMONAPI_EXPORT Address {...
private:service_id_t service_;     // 服务IDinstance_id_t instance_;   // 实例IDmajor_version_t major_version_;   // Max版本号minor_version_t minor_version_;   // Min版本号
}
}
}

​ 先来看下创建HelloWorldSomeIPProxy的过程,首先,用户需要在自己的应用程序中创建核心层的Proxy(HelloWorldProxy)

// HelloWorldClient.cpp
int main() {...// CommonAPI::Address// {// domainid = "local"// interface = HelloWorld::getInterface = "commonapi.HelloWorld:v1_0"// instance = "test"// }std::shared_ptr<HelloWorldProxy<>> myProxy = runtime->buildProxy<HelloWorldProxy>("local", "test");...
}

​ 可以看到,创建核心层HelloWorldProxy的时候,对应的CommonAPI::Address地址已经提供出来了,然后runtime在创建的时候,会在调用核心层的工厂类(CommonAPI::SomeIP::Factory)的createProxy创建绑定层的HelloWorldSomeIPProxy对象,这个时候就需要从AddressTranslator中获取绑定层地址(SomeipIP的服务和实例信息)

// Factory.cppstd::shared_ptr<CommonAPI::Proxy>
Factory::createProxy(const std::string &_domain,const std::string &_interface, const std::string &_instance,const ConnectionId_t &_connectionId) {auto proxyCreateFunctionsIterator= proxyCreateFunctions_.lower_bound(_interface);// 查找是否为该interface注册过SomeIP绑定层的Proxy的创建函数if (proxyCreateFunctionsIterator!= proxyCreateFunctions_.end()) {   // 找到该CommonAPI interface注册的SomeIP绑定层Proxy创建函数...CommonAPI::Address address(_domain, itsInterface, _instance);Address someipAddress;// 在AddressTranslator中查找CommonAPI地址对应的SomeIP地址if (AddressTranslator::get()->translate(address, someipAddress)) {std::shared_ptr<Connection> itsConnection= getConnection(_connectionId);if (itsConnection) {// 使用注册的Proxy创建函数createHelloWorldSomeIPProxy创建HelloWorldSomeIPProxy// 传入的CommonAPI::SomeIP::Address为{0x1234, 0x1, 1, 0}std::shared_ptr<Proxy> proxy= proxyCreateFunctionsIterator->second(someipAddress, itsConnection);if (proxy && proxy->init())return proxy;}}}COMMONAPI_ERROR("Creating proxy for \"", _domain, ":", _interface, ":",_instance, "\" failed!");return nullptr;
}

​ AddressTranslator中CommonAPI地址和SomeIP绑定层地址的对应关系在生成的绑定层HelloWorldSomeIPProxy.cpp中插入的:

// HelloWorldSomeIPProxy.cpp
void initializeHelloWorldSomeIPProxy() {// 插入地址对应关系CommonAPI::SomeIP::AddressTranslator::get()->insert("local:commonapi.HelloWorld:v1_0:test",   // CommonAPI Address0x1234, 0x1, 1, 0);     // CommonAPI::SomeIP Address// 注册绑定层SomeIP创建函数CommonAPI::SomeIP::Factory::get()->registerProxyCreateMethod("commonapi.HelloWorld:v1_0",   // CommonAPI interface&createHelloWorldSomeIPProxy);   // SomeIP Proxy create function
}std::shared_ptr<CommonAPI::SomeIP::Proxy> createHelloWorldSomeIPProxy(const CommonAPI::SomeIP::Address &_address,const std::shared_ptr<CommonAPI::SomeIP::ProxyConnection> &_connection) {return std::make_shared< HelloWorldSomeIPProxy>(_address, _connection);
}

​ 然后,生成的绑定层HelloWorldSomeIPProxy也是需要实现fidl中定义的sayHello接口的,其实现主要是依赖Deployment类来包装输入的参数,然后通过ProxyHelper类来完成参数的序列化和中间件通信接口的调用(ProxyHelper::callMethod):

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));   // 输入参数CommonAPI::Deployable< std::string, CommonAPI::SomeIP::StringDeployment> deploy_message(static_cast< CommonAPI::SomeIP::StringDeployment* >(nullptr));   // 返回参数// 依赖ProxyHelper类发起someip的Method请求(REQUEST-RESPONSE)CommonAPI::SomeIP::ProxyHelper<...>::callMethodWithReply(    // ProxyHelper类是个静态类,内部都是静态方法,不保存成员*this,    // 主要是提供HelloWorldSomeIPProxy内部的Connection指针CommonAPI::SomeIP::method_id_t(0x7b),false,false,(_info ? _info : &CommonAPI::SomeIP::defaultCallInfo),deploy_name,_internalCallStatus,deploy_message);_message = deploy_message.getValue();
}

​ 而ProxyHelper的callMethod函数则依赖Connetion类来完成中间件接口的调用。

template <typename Proxy_ = Proxy>
static void callMethod(...) {if (_proxy.isAvailable()) {  // 对端service为可用状态// 首先序列化参数OutputStream outputStream(_methodCall, _isLittleEndian);const bool success = SerializableArguments<InArgs_...>::serialize(outputStream, _inArgs...);...// 通过Connection指针调用到vsomeip中间件接口bool success = _proxy.getConnection()->sendMessage(_methodCall);  ...} else {...}
}

3.2 Stub

HelloWorldSomeIPStubAdapterInternalHelloWorldSomeIPStubAdapter的父类,此外,HelloWorldSomeIPStubAdapterInternal类也是绑定层对于核心层HelloWorldStubAdapter接口类的实现。

​ 总体来说,StubAdapter类是在Stub和中间件之间做适配功能的类,并且实现了核心层的StubAdapter接口类中定义的接口。

​ 先看下HelloWorldSomeIPStubAdapterInternal类,HelloWorldSomeIPStubAdapterInternal继承自核心层生成的HelloWorldStubAdapter接口和CommonAPI::SomeIP::StubAdapterHelper类。

​ HelloWorldSomeIPStubAdapterInternal类有获取内部属性的成员,其中包括Version信息的属性的getHelloWorldInterfaceVersionStubDispatcher成员,如果fidl中定义了业务的属性,则还会包含该业务属性的StubDispatcher成员,例如https://github.com/COVESA/capicxx-core-tools/tree/master/CommonAPI-Examples/E02Attributes 中fdepl生成的E02AttributesSomeIPStubAdapter.hpp文件中,E02AttributesSomeIPStubAdapterInternal类就有getXAttributeStubDispatcher,getA1AttributeStubDispatcher和getE02AttributesInterfaceVersionStubDispatcher,其中getXAttributeStubDispatcher对应fidl中定义的属性x,getA1AttributeStubDispatcher对应fidl中定义的属性a1。

​ SomeIPStubAdapterInternal类内部的StubDispatcher成员都是GetAttributeStubDispatcher类型的,实现了StubDispatcher接口,查看GetAttributeStubDispatcher类的实现可以看到内部成员函数dispatchMessage,这个函数就是通过Connection类调用SomeIP中间件将属性值通过消息的方式发送给客户端:

template <typename StubClass_, typename AttributeType_, typename AttributeDepl_ = EmptyDeployment>
class GetAttributeStubDispatcher: public StubDispatcher<StubClass_> {
public:...bool dispatchMessage(const Message &message, const std::shared_ptr<StubClass_> &stub,RemoteEventHandlerType* _remoteEventHandler,std::shared_ptr<ProxyConnection> _connection) {...return sendAttributeValueReply(message, stub, _connection);}protected:   inline bool sendAttributeValueReply(const Message &message, const std::shared_ptr<StubClass_>& stub,std::shared_ptr<ProxyConnection> _connection) {...return _connection->sendMessage(reply);}...}

​ 当服务端进程收到客户端发送的对属性访问的SOMEIP method消息触发该接口的调用,调用栈如下:

Connection::handleStubReceive   // 从中间件收到client发送的消息// 处理消息(根据消息中的serviceID, instanceID找到对应的CommonAPI::SomeIP::StubAdapter// 例如E02AttributesSomeIPStubAdapterInternal,HelloWorldSomeIPStubAdapterInternal StubManager::handleMessage StubAdapterHelper::onInterfaceMessage  // StubAdapterHelper也是E02AttributesSomeIPStubAdapterInternal的父类StubAdapterHelper::findDispatcherAndHandleStubDispatcher::dispatchMessage  // 内部调用Stub对应的get属性值的方法获取当前属性值

​ 当服务端进程将属性的值通过SOMEIP消息回复给客户端的时候,首先是需要知道当前服务instance中该属性的值对不对,fidl文件生成的核心层Stub中提供这个属性的值的获取方法:

// src-gen/v1/commonapi/examples/E02AttributesStubDefault.hpp
COMMONAPI_EXPORT virtual const int32_t &getXAttribute() {return xAttributeValue_;
}

​ 也就是说如果有Stub对象,就可以调用其getXAttribute方法获取其x属性的当前值,然后在E02AttributesSomeIPStubAdapterInternal的构造函数中可以看到,HelloWorldSomeIPStubAdapterInternal构造函数中创建x属性对应的getXAttributeStubDispatcher对象时提供getXAttribute方法:

E02AttributesSomeIPStubAdapterInternal(...getXAttributeStubDispatcher(&::v1::commonapi::examples::E02AttributesStub::lockXAttribute,&::v1::commonapi::examples::E02AttributesStub::getXAttribute,  // 注册到内部getStubFunctor_成员中false,_stub->hasElement(0)),22...
}

​ getXAttributeStubDispatcher是GetAttributeStubDispatcher类型的,其dispatchMessage方法中可以看到对get属性值方法的调用:

class GetAttributeStubDispatcher: public StubDispatcher<StubClass_> {
public:...bool dispatchMessage(const Message &message, const std::shared_ptr<StubClass_> &stub,...return sendAttributeValueReply(message, stub, _connection);}
protected:inline bool sendAttributeValueReply(const Message &message, const std::shared_ptr<StubClass_>& stub,std::shared_ptr<ProxyConnection> _connection) {Message reply = message.createResponseMessage();OutputStream outputStream(reply, isLittleEndian_);...// 获取属性值auto deployable = CommonAPI::Deployable<AttributeType_, AttributeDepl_>((stub.get()->*getStubFunctor_)(clientId), depl_);outputStream << deployable;  // 写入消息outputStream.flush();return _connection->sendMessage(reply);  // 发送属性值get-method消息的回复消息}
}

HelloWorldSomeIPStubAdapter继承了HelloWorldSomeIPStubAdapterInternal,也就具备了通过绑定层调用SomeIP中间件进行通信的功能,在此基础上,主要增加了一个功能,就是在构造函数中增加了SomeIP地址和Connection对象的传入,有了这两个对象,HelloWorldSomeIPStubAdapterInternal的SomeIP通信功能才能真正工作起来。

public:HelloWorldSomeIPStubAdapter(const CommonAPI::SomeIP::Address &_address,const std::shared_ptr<CommonAPI::SomeIP::ProxyConnection> &_connection,const std::shared_ptr<CommonAPI::StubBase> &_stub): CommonAPI::SomeIP::StubAdapter(_address, _connection),HelloWorldSomeIPStubAdapterInternal<_Stub, _Stubs...>(_address, _connection, _stub) {}

​ 那么这个构造函数什么时候调用的呢?是CommonAPI::SomeIP::Factory调用的,之前在核心层说过,生成的绑定层Stub代码会将HelloWorldSomeIPStubAdapter的创建函数和对应的CommonAPI::Address注册给Factory:

void initializeHelloWorldSomeIPStubAdapter() {CommonAPI::SomeIP::AddressTranslator::get()->insert("local:commonapi.HelloWorld:v1_0:test",0x1234, 0x1, 1, 0);CommonAPI::SomeIP::Factory::get()->registerStubAdapterCreateMethod(   // 注册HelloWorldSomeIPStubAdapter的创建函数"commonapi.HelloWorld:v1_0",&createHelloWorldSomeIPStubAdapter);
}

​ 当我们在服务端代码中注册我们的HelloWorldStubImpl时,通过核心层调用到绑定层的SomeIP::Factory进行HelloWorldSomeIPStubAdapter对象的创建:

int main() {std::shared_ptr<CommonAPI::Runtime> runtime = CommonAPI::Runtime::get();std::shared_ptr<HelloWorldStubImpl> myService =std::make_shared<HelloWorldStubImpl>();runtime->registerService("local", "test", myService);   // 这一步会调用到SomeIP::Factory创建HelloWorldSomeIPStubAdapter...
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/13184.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

什么叫DeepSeek-V3,以及与GPT-4o的区别

1. DeepSeek 的故事 1.1 DeepSeek 是什么&#xff1f; DeepSeek 是一家专注于人工智能技术研发的公司&#xff0c;致力于打造高性能、低成本的 AI 模型。它的目标是让 AI 技术更加普惠&#xff0c;让更多人能够用上强大的 AI 工具。 1.2 DeepSeek-V3 的问世 DeepSeek-V3 是…

数据结构:队列篇

图均为手绘,代码基于vs2022实现 系列文章目录 数据结构初探: 顺序表 数据结构初探:链表之单链表篇 数据结构初探:链表之双向链表篇 链表特别篇:链表经典算法问题 数据结构:栈篇 文章目录 系列文章目录前言一.队列的概念和结构1.1概念一、动态内存管理优势二、操作效率与安全性…

MySQL

二进制方式&#xff1a; 下载并上传安装包到设备 创建组与用户 [rootlocalhost ~]# groupadd mysql [rootlocalhost ~]# useradd -r -g mysql -s /bin/false mysql解压安装包&#xff1a; [rootlocalhost ~]# tar xf mysql-8.0.36-linux-glibc2.28-x86_64.tar.xz -C /usr/l…

Windows电脑本地部署运行DeepSeek R1大模型(基于Ollama和Chatbox)

文章目录 一、环境准备二、安装Ollama2.1 访问Ollama官方网站2.2 下载适用于Windows的安装包2.3 安装Ollama安装包2.4 指定Ollama安装目录2.5 指定Ollama的大模型的存储目录 三、选择DeepSeek R1模型四、下载并运行DeepSeek R1模型五、常见问题解答六、使用Chatbox进行交互6.1 …

洛谷网站: P3029 [USACO11NOV] Cow Lineup S 题解

题目传送门&#xff1a; P3029 [USACO11NOV] Cow Lineup S - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 前言&#xff1a; 这道题的核心问题是在一条直线上分布着不同品种的牛&#xff0c;要找出一个连续区间&#xff0c;使得这个区间内包含所有不同品种的牛&#xff0c;…

如何利用maven更优雅的打包

最近在客户现场部署项目&#xff0c;有两套环境&#xff0c;无法连接互联网&#xff0c;两套环境之间也是完全隔离&#xff0c;于是问题就来了&#xff0c;每次都要远程到公司电脑改完代码&#xff0c;打包&#xff0c;通过网盘&#xff08;如果没有会员&#xff0c;上传下载慢…

360手机刷机 360手机解Bootloader 360手机ROOT

360手机刷机 360手机解Bootloader 360手机ROOT 问&#xff1a;360手机已停产&#xff0c;现在和以后&#xff0c;能刷机吗&#xff1f; 答&#xff1a;360手机&#xff0c;是肯定能刷机的 360手机资源下载网站 360手机-360手机刷机RootTwrp 360os.top 360rom.github.io 一、…

8.攻防世界Web_php_wrong_nginx_config

进入题目页面如下 尝试弱口令密码登录 一直显示网站建设中&#xff0c;尝试无果&#xff0c;查看源码也没有什么特别漏洞存在 用Kali中的dirsearch扫描根目录试试 命令&#xff1a; dirsearch -u http://61.147.171.105:53736/ -e* 登录文件便是刚才登录的界面打开robots.txt…

排序算法--计数排序

唯一种没有比较的排序(指没有前后比较,还是有交换的)。统计每个元素出现的次数&#xff0c;直接计算元素在有序序列中的位置&#xff0c;要求数据是整数且范围有限。适用于数据为小范围整数&#xff08;如年龄、成绩&#xff09;&#xff0c;数据重复率较高时效率更优。可用于小…

PyTorch快速入门

Anaconda Anaconda 是一款面向科学计算的开源 Python 发行版本&#xff0c;它集成了众多科学计算所需的库、工具和环境管理系统&#xff0c;旨在简化包管理和部署&#xff0c;提升开发与研究效率。 核心组件&#xff1a; Conda&#xff1a;这是 Anaconda 自带的包和环境管理…

树莓派卷积神经网络实战车牌检测与识别

文章目录 树莓派介绍1. 树莓派的硬件规格2. 树莓派的操作系统3. 树莓派的应用场景 研究背景一、效果演示1.0 项目获取1.1 图像识别1.2 视频识别 二、技术原理2.1 整体流程2.2 CCPD数据集介绍2.3 车牌定位2.4 车牌矫正2.5 车牌识别2.5.1 CRNN概述2.5.2 CRNN网络架构实现2.5.3 CN…

Redis入门概述

1.1、Redis是什么 Redis&#xff1a;官网 高性能带有数据结构的Key-Value内存数据库 Remote Dictionary Server&#xff08;远程字典服务器&#xff09;是完全开源的&#xff0c;使用ANSIC语言编写遵守BSD协议&#xff0c;例如String、Hash、List、Set、SortedSet等等。数据…

如何在自己电脑上私有化部署deep seek

要在自己的电脑上私有化部署 DeepSeek&#xff0c;通常需要以下步骤&#xff1a; 1. 环境准备 操作系统&#xff1a;确保你的电脑操作系统支持 Docker 或直接安装 Python 环境&#xff08;如 Linux、Windows 或 macOS&#xff09;。 Python 环境&#xff1a;安装 Python 3.7 …

【办公类-99-01】20250201学具PDF打印会缩小一圈——解决办法:换一个PDF阅读器

背景需求&#xff1a; 2024年1月13日&#xff0c;快要放寒假了&#xff0c;组长拿着我们班的打印好的一叠教案来调整。 “前面周计划下面的家园共育有调整&#xff0c;你自己看批注。” “还有你这个教案部分的模版有问题&#xff0c;太小&#xff08;窄&#xff09;了。考虑…

k8s集群

文章目录 项目描述项目环境系统与软件版本概览项目步骤 环境准备IP地址规划关闭selinux和firewall配置静态ip地址修改主机名添加hosts解析 项目步骤一、使用kubeadm安装k8s单master的集群环境&#xff08;1个master2个node节点&#xff09;1、互相之间建立免密通道2.关闭交换分…

HTTP和HTTPS协议详解

HTTP和HTTPS协议详解 HTTP详解什么是http协议http协议的发展史http0.9http1.0http1.1http2.0 http协议的格式URI和URL请求request响应response http协议完整的请求与响应流程 HTTPS详解为什么使用HTTPSSSL协议HTTPS通信过程TLS协议 HTTP详解 什么是http协议 1、全称Hyper Tex…

2025开源DouyinLiveRecorder全平台直播间录制工具整合包,多直播同时录制、教学直播录制、教学视频推送、简单易用不占内存

一、DouyinLiveRecorder软件介绍&#xff08;文末提供下载&#xff09; 官方地址&#xff1a;GitHub - ihmily/DouyinLiveRecorder 本文信息来源于作者GitHub地址 一款简易的可循环值守的直播录制工具&#xff0c;基于FFmpeg实现多平台直播源录制&#xff0c;支持自定义配置录制…

学习threejs,pvr格式图片文件贴图

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️PVR贴图1.2 ☘️THREE.Mesh…

Beans模块之工厂模块注解模块CustomAutowireConfigurer

博主介绍&#xff1a;✌全网粉丝5W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

(一)DeepSeek大模型安装部署-Ollama安装

大模型deepseek安装部署 (一)、安装ollama curl -fsSL https://ollama.com/install.sh | sh sudo systemctl start ollama sudo systemctl enable ollama sudo systemctl status ollama(二)、安装ollama遇到网络问题&#xff0c;请手动下载 ollama-linux-amd64.tgz curl -L …