Score
0
Watch 4 Star 24 Fork 7

蛋蛋 / EmbedXrpcC++MIT

Join us
Explore and code with more than 5 million developers,Free private repositories !:)
Sign up
这个东西类似于Google的GRPC,但是应用场景是单片机。RPC远程调用极大的方便了开发,使得不必关注于协议解析,数据的序列化和反序列化等繁琐的工作。可是目前还没有在单片机上实现好用的RPC框架,于是我就谋生了做这个RPC框架的想法,所用的技术是:C#做 IDL语言+csscript+自己实现序列化和反序列化+代码生成(Razor Engine) QQ群134161401 spread retract

https://gitee.com/snikeguo/EmbedXrpc

Clone or download
Loading...
readme.md

EmbedXrpc 用户手册

QQ群134161401

这个东西类似于Google的GRPC,但是应用场景是单片机。RPC远程调用极大的方便了开发,使得不必关注于协议解析,数据的序列化和反序列化等繁琐的工作。可是目前还没有在单片机上实现好用的RPC框架,于是我就谋生了做这个RPC框架的想法,所用的技术是:C#做 IDL语言+csscript+自己实现序列化和反序列化+代码生成(Razor Engine) QQ群134161401

应用场景:单片机近距离Client/Server交互场景(USB、串口、CAN(如J1939 、ISO15765协议等),)只要是流协议都支持

Sample1工程是例子!除了main.cpp的代码是手工写的之外,其他的代码都是工具生成的!这是个最简单的例子!

编译此工程你需要安装Qt5.14.2和Qt For Vs Addin插件。

此工程演示了:

1.客户端每一秒向服务端发送1、2 服务端计算出来3后,把3发送给客户端

2.服务端每1秒广播当前的时间,客户端打印到控制台上

编译EmbedXrpc代码生成器EXE

1.用vs2019 打开EmbedXrpcIdlParser.sln编译好(编译EXE只做一次)。 2.在EmbedXrpcIdlParser\bin\Debug会出现一个EmbedXrpcIdlParser.exe,

用户使用步骤(以idltest1.cs为例子)

1.编写你的IDL文件,目录下会有一个idltest1.cs 文件,是例子,你可以参考一下。 2.执行命令

.\EmbedXrpcIdlParser.exe -g all -l cpp -i idltest1.cs -o yourPath
-g 代表生成client还是server 还是all
-l 代表生成cpp还是 cs
-o 代表生成的代码打算放到哪个目录

5.生成好代码后(我假定你生成在了A文件夹下,

其中:
A文件夹所有的文件(包括子目录下的) + EmbedXrpcRuntime/Cpp + EmbedXrpcRuntime/Cpp/Win32.Port = 客户端(上位机端)的源文件(如果上位机用C++开发)
A文件夹所有的文件(包括子目录下的) + EmbedXrpcRuntime/CS = 客户端(上位机端)的源文件(如果上位机用C#开发)
A文件夹所有的文件(包括子目录下的) + EmbedXrpcRuntime/Cpp + EmbedXrpcRuntime/Cpp/RT-Thread.Port = 服务端(单片机端)的源文件 RT-Thread的移植
A文件夹所有的文件(包括子目录下的) + EmbedXrpcRuntime/Cpp + EmbedXrpcRuntime/Cpp/FreeRTOS.Port = 服务端(单片机端)的源文件 FreeRTOS的移植

又一个简单的例子

1.编写idl文件:demo.cs

using System;
using EmbedXrpcIdlParser;

[FileName("demo.cs")]
interface Inter
{
    UInt16 GetValue();
}
[FileName("demo.cs")]
public class OptionProcess:IOptionProcess
{
    public GenerationOption Process()
    {
        GenerationOption option = new GenerationOption();
        option.OutPutFileName = "Demo";
        option.CSharpNameSpace = "Demo";
        return option;
    }
}

2.执行命令(见上述),生成客户端(电脑端)的代码和服务端代码(这里假定执行命令中,-g参数你写的是all,而不是单独的client和server),接口所示:

//客户端
class InterClientImpl
{
public:
    EmbedXrpcClientObject *RpcClientObject = nullptr;
    InterClientImpl(EmbedXrpcClientObject *rpcobj)
    {
        this->RpcClientObject = rpcobj;
    }
    Inter_GetValue_Response &GetValue();
    void Free_GetValue(Inter_GetValue_Response *response);
};


//----------服务端-----------
class Inter_GetValueService : public IService
{
public:
    uint16_t GetSid() { return Inter_GetValue_ServiceId; }
    Inter_GetValue_Response Response;
    void GetValue();
    void Invoke(SerializationManager &recManager, SerializationManager &sendManager);
};

3.服务端(单片机)这边,你需要编写代码:

EmbedXrpcServerObject ServerRpcObject(.....);//参数我这里省略了

void Inter_GetValueService::GetValue()
{
    Response.ReturnValue=188;
}

4.客户端(电脑)这边,你需要编写代码:

EmbedXrpcClientObject ClientRpcObject(....Args...);//参数我这里省略了
InterClientImpl Client(&ClientRpcObject);
auto val=Client.GetValue();//这个函数将会把数据发送到服务器(比如单片机)上
if(val.State==ResponseState_Ok)
{
    UInt16 value=val.ReturnValue;//如果不出意外的话,value的值是188
    //你的代码
}
Client.Free_GetValue(&val);//最后用完val,记得调用接口对应的释放函数,形如: Free_你的接口()

至此一个完整的RPC调用就完成了 可以看下我自己项目上实现的

auto ThreadsInfo=Dev.Request.GetThreadsInfo();
CheckResponseState(ThreadsInfo);//我自己写的一个宏,就是判断State是否是OK,如果不是OK,就返回错误的,简单辅助宏罢了
printf("...");//打印参数

auto EraseResult=Dev.Request.FlashErase(addr,size);
CheckResponseState(EraseResult);
while(...)
{
  auto WriteResult=Dev.Request.FlashWrite(addr,size,bin);
  CheckResponseState(WriteResult);
}

是不是非常方便~~

原理部分(可以不看)

序列化部分:

例如有如下结构体:

Struct sub
{
Uint8 v1;
Uint8 v2;
}
Struct ss
{
 Uint8  a;
 Uint16 b_len;
 sub[] b;
 sub c;
}

这个IDL文件通过exe后会生成序列化描述源文件,功能和高级语言的反射差不多。 假定生成代码后,有这样的语句:

Struct ss  instance;
Instance.a=1;
Instance. b_len=2;
Instance.b=malloc(....);
Instance.b[0].v1=3;
Instance.b[0].v2=4;
Instance.b[1].v1=5;
Instance.b[1].v2=6;
Instance.c.v1=7;
Instance.c.v2=8;

序列化库会根据描述源文件 会把instance序列化成字节流; 0x1,0x2,0x0(小端序),3,4,5,6,7,8;(并没有使用TLV或者TTLV编码。因为要减少内存消耗和提高效率,如果你想改成TLV,自己可以很简单改好。) 支持一维固定数组和一维动态数组(指针),不支持2维; 一般地,数组需要整数型字段(例子中的c_len)作为他的长度表示,如果没有,序列化的时候只序列化一个单位(例子中的MyStructType)长度; 总之他最终序列化的基本单位是整数u8.i8...i64 因为任何数据都是这个的集合体(不支持位域)

RPC部分:

序列化库和RPC是解耦的,你可以单独使用序列化,也可以两个一起用 假定有如下Service接口:

MyType GetValue(var a1,var a2,)

会生成一个结构体 GetValue_Request ,客户端生成GetValue同名函数,并生成如下代码:

GetValue(var a1,var a2,)
{
//填充结构体
Memcpy(GetValue_Request.a1,a1,sizeof(a1));
Memcpy....
GetValue_Request_Type.serialize(buf,request)
ClientRpcObject->Send(buf)//通过串口啥的发送出去
GetValue_Response response;
 wait(..&response)//等待串口的回复 如果接受成功,反序列化到response
Return 
}

关于使用注意:

例如例子中 结构体ss中的b这个动态数组,如果b的值是无效的。也就是说当b指向一块无效的内存的时候,这时候执行序列化代码会发生异常,因为序列化库不管这些,他只是忠实的序列化b指向的内容。所以这些部分由用户去控制。 具体的看代码吧,暂时看不懂序列化和反序列化没关系。

Comments ( 6 )

Sign in for post a comment

C++
1
https://gitee.com/snikeguo/EmbedXrpc.git
git@gitee.com:snikeguo/EmbedXrpc.git
snikeguo
EmbedXrpc
EmbedXrpc
master

Help Search