搜索
bottom↓
回复: 50

[分享][交流]发布一个消息地图的模块

  [复制链接]

出0入0汤圆

发表于 2014-12-20 16:18:06 | 显示全部楼层 |阅读模式
本帖最后由 Gorgon_Meducer 于 2014-12-20 17:07 编辑


傻孩子图书工作室作品

所谓消息地图就是根据不同的状态来执行对应的处理程序,这一技术成为消息地图。例如我们平时使用的if、else语句switch、case
语句都是消息地图的一种实现方式,而这个模块采用的是函数指针的方式来实现消息地图。采用全状态机开发消息可以进行动态、静态的
配置。消息地图的技术来源于傻孩子老师的<深入浅出AVR单片机>,具体的细节请参考此书。

废话少说,下面直接进入正题,讲解一下怎么移植这个模块。

1、准备工作
        首先我们要有一个硬件平台,具有一个串口的最新系统即可,为了体现代码的平台无关性请 参考文档《平台搭建》来搭建平台。
这样我们有了一个共同讨论的基础。(我的示例工程采用的是STM32神州III的开发板)。

2、解压缩
        将下载下来的“MsgMapService”解压缩,模块的目录结构如下所示,文件夹msgmap是服务实现的具体的代码,而utilities文件夹
的内容是改模块依赖的一些宏以及队列的模板,msgmap.h是调用该模块的接口头文件,app_cfg.h是该模块的配置头文件,使用该模
块的时候对模块的依赖进行配置。
        解压后将“MsgMapService”文件夹整个拷贝到你的工程中,将msgmap.c 和 checkstring.c添加到工程中参与编译。
目录树结构
[MsgMapService]
        | ---- msgmap.h
        | ---- app_cfg.h
        | ---- [utilities]       
        |                | ---- ooc.h
        |                | --- app_type.h
        |                | ---- [template]
        |                                | ---- t_queue.h
        |                                | ---- template.h                                                                               
        | ---- [msgmap]
                        | ----        msgmap.c       
                        | ---- msgmap.h
                        | ---- app_cfg.g
                        | ---- [checkstring]                       
                                        | ---- checkstring.c
                                        | ---- checkstring.h
                                        | ---- app_cfg.h       

3、配置模块
        该模块是通过读取队列的字节流,而消息地图是有用户进行的配置,这里可以采用动态的配置和静态的配置两种方式。
首先该模块依赖队列,我在配置文件中插入一条宏:EXTERN_QUEUE(MsgMapQueue,uint8_t,uint8_t);MsgMapQueue是定义
的队列的名称,队列的使用方法见t_queue.h.我们将数据接收队列用tFIFOin命名,用宏进行插入。
#define CHECK_BYTE_QUEUE     g_tFIFOin
        然后我们需要配置消息系统,这里我们采用静态配置--所谓静态配置是在编译的阶段对模块的配置,
  1. #define INSERT_MSG_MAP_FUNC_EXRERN                                          \
  2.     extern bool msg_apple_handler(const msg_t *ptMSG);                      \
  3.     extern bool msg_orange_handler(const msg_t *ptMSG);                     \
  4.     extern bool msg_hello_handler(const msg_t *ptMSG);

  5. #define INSERT_MSG_MAP_CMD  {"apple", &msg_apple_handler},                  \
  6.                             {"orange", &msg_orange_handler},                \
  7.                             {"hello", &msg_hello_handler},
复制代码

这两条宏就实现了消息地图的静态配置,msg_apple_handler、msg_orange_handler、msg_hello_handler是消息处理函数,
而字符串就是消息了。
        消息地图还有一个依赖,就是我们的字符输出函数。即为平台里的serial_out函数,这里我们用宏来进行插入
#define SERIAL_OUT_HANDLE  serial_out。
现在我们的模块的基本的使用配置就完成了,接下来我们看看如何调用。

4、模块的使用
        现在我们消息地图来完成一个任务,通过这个任务来介绍这个模块的具体的调用的方法。我们要完成的这个任务的功能
是“芝麻开门”,就是我通过超级终端进行字符输入,然后该任务对输入的字符进行相应,不同的字符串对应不同的相应,例如
我们输入hello的时候向我们输出world,就好比我们操作系统的命令行一样,你输入一个命令,操作系统给出一个响应,下面
看看这个任务怎么实现。
        在模块配置的环节我们介绍了消息地图的静态配置,现在我们继续介绍消息地图的另一种配置------动态配置,所谓动态
配置就是消息地图在运行的工程中可以通过cmd_register进行注册,通过cmd_unregister进行删除。
        首先定义消息地图以及消息处理函数:
  1. bool msg_use2_handler(const msg_t *ptMSG);
  2. bool msg_use1_handler(const msg_t *ptMSG);

  3. static msg_t s_tUserMSGMap[] = {
  4.     {"use1", &msg_use1_handler},
  5.     {"use2", &msg_use2_handler},
  6. };
复制代码

这样消息地图就定义好了,而消息地图的处理函数由你自己编写,就我们的任务而言消息地图的相应函数只需要做一件事,那
就是置位事件,例如 msg_apple_handler的功能是置位事件s_tEventApple:
  1. bool msg_apple_handler(const msg_t *ptMSG)
  2. {
  3.     if(NULL == ptMSG) {
  4.         return false ;
  5.     }
  6.                     
  7.     SET_EVENT(&s_tEventApple);
  8.     return  true;
  9. }
复制代码

现在动态消息地图已经配置好了,再使用前通过cmd_register(s_tUserMSGMap,UBOUND(s_tUserMSGMap));进行注册。
        在对msg_map_search的使用时将他进行了二次封装,当msg_map_search执行到fsm_rt_cpl状态时调用他的消息
处理函数。
  1. static  fsm_rt_t CheckSringUseMsgMap(void)
  2. {
  3.     const msg_t *ptMsg = NULL;

  4.     if(fsm_rt_cpl == msg_map_search(&ptMsg)) {
  5.         ptMsg->fnHandler(ptMsg);
  6.     }
  7.         
  8.     return fsm_rt_on_going;
  9. }
复制代码

        现在消息地图部分已经OK,使用的时候调用CheckSringUseMsgMap就可以了。现在我们来实现task_a、task_b、task_c
这三个进程是输出进程,他们的功能是等待事件触发,事件触发后执行事件的相应-----输出字符串。现在我们定义事件
  1. static event_t s_tEventApple;
  2. static event_t s_tEventOrange;
  3. static event_t s_tEventWorld;
复制代码

然后进行初始化,初始化完成后就可以使用了:
  1.     INIT_EVENT(&s_tEventApple,false,MANUAL);
  2.     INIT_EVENT(&s_tEventOrange,false,MANUAL);
  3.     INIT_EVENT(&s_tEventWorld,false,MANUAL);
复制代码

然后编写task_a的进程函数
  1. #define TASK_A_FSM_RESET() do {s_tState = TASK_A_START;} while(0)
  2. static fsm_rt_t task_a(void)
  3. {
  4.     static enum {
  5.         TASK_A_START                    = 0,
  6.         TASK_A_WAIT_EVENT,
  7.         TASK_A_PRINT
  8.     }s_tState = TASK_A_START;
  9.    
  10.     switch(s_tState) {
  11.         case TASK_A_START:
  12.             s_tState = TASK_A_WAIT_EVENT;
  13.             //break;
  14.         
  15.         case TASK_A_WAIT_EVENT:
  16.             if(WAIT_EVENT(&s_tEventApple)){
  17.                 s_tState = TASK_A_PRINT;
  18.             }
  19.             break;
  20.          
  21.         case TASK_A_PRINT:
  22.             if(fsm_rt_cpl == print_apple()){
  23.                 RESET_EVENT(&s_tEventApple);
  24.                 TASK_A_FSM_RESET();
  25.                 return fsm_rt_cpl;
  26.             }
  27.             break;               
  28.     }
  29.    
  30.     return fsm_rt_on_going;
  31. }
复制代码
  1. #define PRINT_APPLE_RESET_FSM() do { s_tState = PRINT_APPLE_START; } while(0)
  2. static fsm_rt_t print_apple(void)
  3. {
  4.     static enum {
  5.         PRINT_APPLE_START = 0,
  6.         PRINT_APPLE_INIT,
  7.         PRINT_APPLE_SEND
  8.     }s_tState = PRINT_APPLE_START;
  9.    
  10.     static uint8_t *s_pchString = (uint8_t *)"apple\r\n";
  11.     static print_str_t s_tPrintStruct;
  12.    
  13.     switch(s_tState) {
  14.         case PRINT_APPLE_START:
  15.             s_tState = PRINT_APPLE_INIT;
  16.             //break;
  17.             
  18.         case PRINT_APPLE_INIT:
  19.             if(INIT_SRT_OUTPUT(&s_tPrintStruct,s_pchString)){
  20.                 s_tState = PRINT_APPLE_SEND;
  21.             }else {
  22.                 return fsm_rt_err;
  23.             }
  24.             break;
  25.         
  26.         case PRINT_APPLE_SEND:
  27.             if(fsm_rt_cpl == print_string(&s_tPrintStruct)){
  28.                 PRINT_APPLE_RESET_FSM();
  29.                 return fsm_rt_cpl;
  30.             }
  31.             break;
  32.     }
  33.         
  34.     return fsm_rt_on_going;
  35. }
复制代码

这里很清楚的可以看到该进程的处理过程,等待事件s_tEventApple触发,然后调用输出 print_apple,而 子状态机 print_apple
就是调用 print_string将输出的内容放到输出队列。其他的两个进程以此编写,这里不在赘述。

        下面我们队输入输出字节流的进程进行说明(stream_in_out),在这个进程中我们用到了队列,而队列的功能代码通过
宏进行插入:DEF_QUEUE(MsgMapQueue,uint8_t,uint8_t,ATOM_ACESS);这样我们就可以使用队列了,首先定义两个输入、输
出的队列:
  1. QUEUE(MsgMapQueue) g_tFIFOin;
  2. QUEUE(MsgMapQueue) g_tFIFOout;
复制代码

字节流的接口和发送很简单参考如下代码。
  1. #define SERIAL_IN_TASK_FSM_RESET() do {s_tState = SERIAL_IN_TASK_START;} while(0)
  2. static fsm_rt_t serial_in_task(void)
  3. {
  4.     static uint8_t s_chByte = 0;
  5.     static enum {
  6.         SERIAL_IN_TASK_START = 0,
  7.         SERIAL_IN_TASK_READ
  8.     }s_tState = SERIAL_IN_TASK_START;
  9.    
  10.     switch(s_tState) {
  11.         case SERIAL_IN_TASK_START:
  12.             s_tState = SERIAL_IN_TASK_READ;
  13.             //breka;
  14.         case SERIAL_IN_TASK_READ:
  15.             if(serial_in(&s_chByte)){
  16.                 ENQUEUE(MsgMapQueue,&g_tFIFOin,s_chByte);
  17.                 SERIAL_IN_TASK_FSM_RESET();
  18.                 return fsm_rt_cpl;
  19.             }
  20.             break;
  21.     }
  22.    
  23.     return fsm_rt_on_going;
  24. }

  25. #define SERIAL_OUT_TASK_FSM_RESET() do {s_tState = SERIAL_OUT_TASK_START;} while(0)
  26. static fsm_rt_t serial_out_task(void)
  27. {
  28.     static uint8_t s_chByte = 0;
  29.     static enum {
  30.         SERIAL_OUT_TASK_START = 0,
  31.         SERIAL_OUT_TASK_READ_QUE,
  32.         SERIAL_OUT_TASK_OUTPUT
  33.     }s_tState = SERIAL_OUT_TASK_START;
  34.    
  35.     switch(s_tState) {
  36.         case SERIAL_OUT_TASK_START:
  37.             s_tState = SERIAL_OUT_TASK_READ_QUE;
  38.             //breka;
  39.         case SERIAL_OUT_TASK_READ_QUE:
  40.             if(DEQUEUE(MsgMapQueue,&g_tFIFOout,&s_chByte)){
  41.                 s_tState = SERIAL_OUT_TASK_OUTPUT;
  42.             }
  43.             break;        
  44.             
  45.         case SERIAL_OUT_TASK_OUTPUT:
  46.             if(serial_out(s_chByte)) {
  47.                 SERIAL_OUT_TASK_FSM_RESET();
  48.                 return fsm_rt_cpl;
  49.             }
  50.             break;
  51.     }
  52.    
  53.     return fsm_rt_on_going;
  54. }
复制代码


现在各个进程都已经准备完毕,就剩下我们进行调用了,main函数如下
  1. int main(void)
  2. {
  3.     system_init();
  4.    
  5.     INIT_EVENT(&s_tEventApple,false,MANUAL);
  6.     INIT_EVENT(&s_tEventOrange,false,MANUAL);
  7.     INIT_EVENT(&s_tEventWorld,false,MANUAL);
  8.    
  9.     QUEUE_INIT(MsgMapQueue,&g_tFIFOin,s_tBuf, UBOUND(s_tBuf));
  10.     QUEUE_INIT(MsgMapQueue,&g_tFIFOout,s_tPiPeBuf, UBOUND(s_tPiPeBuf));
  11.    
  12.     cmd_register(s_tUserMSGMap,UBOUND(s_tUserMSGMap));
  13.    
  14.     while(1) {
  15.         task_a();
  16.         task_b();
  17.         task_c();
  18.         CheckSringUseMsgMap();
  19.         stream_in_out();
  20.     }
  21. }
复制代码


个人水平有限,欢迎大家拍砖,盖房娶媳妇。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

阿莫论坛20周年了!感谢大家的支持与爱护!!

一只鸟敢站在脆弱的枝条上歇脚,它依仗的不是枝条不会断,而是自己有翅膀,会飞。

出0入0汤圆

 楼主| 发表于 2014-12-20 16:18:57 | 显示全部楼层
坐等大家来拍砖啊。。。

出0入0汤圆

发表于 2014-12-20 16:48:15 | 显示全部楼层
学习了   自己C语言水平还很烂  得多看书学习

出0入0汤圆

发表于 2014-12-20 17:01:00 | 显示全部楼层
傻孩子工作室出品的?

出0入42汤圆

发表于 2014-12-20 17:48:00 来自手机 | 显示全部楼层
先标记一下,大概看了下,应该是个酷贴,晚上仔细研究研究

出0入0汤圆

发表于 2014-12-20 17:49:04 | 显示全部楼层
好好学学,一直用的都只是消息循环队列搞的,

出0入0汤圆

发表于 2014-12-20 20:13:32 来自手机 | 显示全部楼层
支持,回头得好好看看

出0入0汤圆

 楼主| 发表于 2014-12-21 00:25:29 | 显示全部楼层
chenguanghua 发表于 2014-12-20 17:01
傻孩子工作室出品的?

对,傻孩子老师教的

出0入0汤圆

 楼主| 发表于 2014-12-21 00:26:01 | 显示全部楼层
Vampireyifeng 发表于 2014-12-20 17:49
好好学学,一直用的都只是消息循环队列搞的,

欢迎讨论

出0入0汤圆

发表于 2014-12-21 03:39:06 来自手机 | 显示全部楼层
好好学习一下…

出0入0汤圆

发表于 2014-12-21 10:22:57 | 显示全部楼层
以前使用过消息字的方式。通过枚举定义消息字,一个消息字对应一个函数指针。不过没做到楼主这么规范

使用消息的方式,去实现比如类似于长按键(长按N秒后执行),感觉好像有点别扭

出0入0汤圆

发表于 2014-12-21 22:10:03 | 显示全部楼层
学习下,谢谢!

出0入0汤圆

发表于 2014-12-21 22:14:44 | 显示全部楼层
支持一个,厉害了

出0入0汤圆

发表于 2014-12-21 22:35:10 | 显示全部楼层
为什么“CheckSringUseMsgMap”函数是首字母大写,“msg_apple_handler”函数是下划线+全小写呢?

出0入0汤圆

发表于 2014-12-21 22:35:57 | 显示全部楼层
先标识下,明天上班看

出0入0汤圆

 楼主| 发表于 2014-12-21 22:50:16 | 显示全部楼层
yiming988 发表于 2014-12-21 22:35
为什么“CheckSringUseMsgMap”函数是首字母大写,“msg_apple_handler”函数是下划线+全小写呢? ...

大意了。抛开此贴不说,编码规范本身就是一个习惯

出0入0汤圆

发表于 2014-12-23 09:26:05 | 显示全部楼层


疯子,不是说true不一定等于1吗

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

 楼主| 发表于 2014-12-23 10:37:06 | 显示全部楼层
yikuang 发表于 2014-12-23 09:26
疯子,不是说true不一定等于1吗

typedef struct{
    bool bAutoReset;
    bool bIsSet;
}event_t;

这里定义bAutoReset;类型错了,应该是

typedef struct{
    even_model_t bAutoReset;
    bool bIsSet;
}event_t;

出0入0汤圆

发表于 2014-12-23 16:24:05 | 显示全部楼层
傻孩子图书工作室作品"",最近偶感软件架构及软件思想方面有些欠缺,问下傻孩子的新书 有出版时间表了没?

出0入0汤圆

发表于 2014-12-23 16:41:34 | 显示全部楼层
软件架构及软件思想方面有些欠缺


例如呢,说说你的建议和想法?

出0入0汤圆

发表于 2014-12-23 17:01:14 | 显示全部楼层
啊,终于找到疯子了!
俺芯创
另,yikuang是?

出0入296汤圆

发表于 2014-12-23 17:13:57 | 显示全部楼层
yikuang 发表于 2014-12-23 16:41
例如呢,说说你的建议和想法?

人家是偶感自己有欠缺……

出0入0汤圆

发表于 2014-12-23 17:18:39 | 显示全部楼层
wangkx1990 发表于 2014-12-23 17:01
啊,终于找到疯子了!
俺芯创
另,yikuang是?

不再犹豫呢

出0入0汤圆

发表于 2014-12-23 17:21:16 | 显示全部楼层
Gorgon_Meducer 发表于 2014-12-23 17:13
人家是偶感自己有欠缺……

OMG,看错了!

出0入0汤圆

 楼主| 发表于 2014-12-23 19:07:00 | 显示全部楼层
本帖最后由 tanek 于 2014-12-23 19:11 编辑
neverlic 发表于 2014-12-23 16:24
傻孩子图书工作室作品"",最近偶感软件架构及软件思想方面有些欠缺,问下傻孩子的新书 有出版时间表了没? ...


跑这里来催斑竹的书来了,不过问得好!斑竹你的书什么时候出啊。

出0入0汤圆

 楼主| 发表于 2014-12-23 19:10:25 | 显示全部楼层
wangkx1990 发表于 2014-12-23 17:01
啊,终于找到疯子了!
俺芯创
另,yikuang是?

我是四哥

出0入0汤圆

发表于 2014-12-23 19:17:41 | 显示全部楼层

那为什么他叫你疯子啊,再说四哥你不是y打头,然后你的qq吗,难道你不敢拿出来秀了,犯大错了

出0入296汤圆

发表于 2014-12-23 19:32:19 来自手机 | 显示全部楼层
水军……

出0入0汤圆

 楼主| 发表于 2014-12-23 20:34:30 | 显示全部楼层
wangkx1990 发表于 2014-12-23 19:17
那为什么他叫你疯子啊,再说四哥你不是y打头,然后你的qq吗,难道你不敢拿出来秀了,犯大错了  ...


时而四哥,时而疯子,我们是------疯四哥组合

出0入0汤圆

发表于 2014-12-23 21:16:37 | 显示全部楼层
绝对牛X啊!徘徊在牛A和牛C之间!

出0入0汤圆

发表于 2014-12-24 00:47:12 | 显示全部楼层
有点像contiki.....

出0入0汤圆

 楼主| 发表于 2014-12-24 10:07:18 | 显示全部楼层

哪里像呢。你说的这个没玩过

出0入0汤圆

发表于 2014-12-25 10:06:27 | 显示全部楼层
tanek 发表于 2014-12-21 22:50
大意了。抛开此贴不说,编码规范本身就是一个习惯

扯犊子  这个习惯不好

出0入0汤圆

 楼主| 发表于 2014-12-25 10:27:31 | 显示全部楼层
yiming988 发表于 2014-12-25 10:06
扯犊子  这个习惯不好

恩。是的不好的习惯,所以说大意了

出0入0汤圆

发表于 2014-12-25 11:19:47 | 显示全部楼层
好好研究系

出0入0汤圆

发表于 2014-12-25 23:25:54 | 显示全部楼层
真心看不懂啊

出0入0汤圆

 楼主| 发表于 2014-12-26 09:19:08 | 显示全部楼层

哪里不懂。是代码觉得复杂看不懂还是文档说明表述不清楚呢

出0入0汤圆

发表于 2014-12-29 23:33:00 | 显示全部楼层
tanek 发表于 2014-12-26 09:19
哪里不懂。是代码觉得复杂看不懂还是文档说明表述不清楚呢

俺水平不够,暂时理解不了这个模块的思路。

出0入36汤圆

发表于 2015-1-6 21:07:15 | 显示全部楼层
傻孩子的东东的确够严谨,但是我在想在小rom mcu里面比如2krom的片子里怎么用。随随便便一个kei模块,一个消息地图框架  就干掉2krom了吧。

出0入296汤圆

发表于 2015-1-7 10:15:59 | 显示全部楼层
GZZXB 发表于 2015-1-6 21:07
傻孩子的东东的确够严谨,但是我在想在小rom mcu里面比如2krom的片子里怎么用。随随便便一个kei模块,一个 ...

又是一个想当然的家伙,实际编译下就知道了。并不大。

出0入0汤圆

 楼主| 发表于 2015-1-7 10:50:03 | 显示全部楼层
GZZXB 发表于 2015-1-6 21:07
傻孩子的东东的确够严谨,但是我在想在小rom mcu里面比如2krom的片子里怎么用。随随便便一个kei模块,一个 ...

你说的占用2K ROM应该是你编译帖子提供的demo工程后看到的数字吧。

出0入296汤圆

发表于 2015-1-7 11:00:36 | 显示全部楼层
tanek 发表于 2015-1-7 10:50
你说的占用2K ROM应该是你编译帖子提供的demo工程后看到的数字吧。

我估计他没编译……

出0入76汤圆

发表于 2015-1-31 16:01:39 | 显示全部楼层
有个地方感觉有点怪...

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2015-4-3 16:35:55 | 显示全部楼层
呵呵,楼上应该是逗号表达式吧……
但是我相信他的本意是分号~

出0入0汤圆

发表于 2015-4-16 11:27:08 | 显示全部楼层
命令泵。。。。。。

出0入76汤圆

发表于 2015-4-23 11:03:55 | 显示全部楼层
catwill 发表于 2015-4-3 16:35
呵呵,楼上应该是逗号表达式吧……
但是我相信他的本意是分号~

呵呵, 是的, 可能是LZ没注意....

出0入0汤圆

发表于 2015-8-24 14:57:29 | 显示全部楼层
谢谢 mark再看

出0入0汤圆

发表于 2015-8-24 15:06:25 | 显示全部楼层
跟消息队列有什么区别?

出0入296汤圆

发表于 2015-8-25 11:05:01 | 显示全部楼层
wenqingli 发表于 2015-8-24 15:06
跟消息队列有什么区别?

45楼答案。

出0入0汤圆

发表于 2017-7-22 15:34:12 | 显示全部楼层
wangle315065 发表于 2015-4-16 11:27
命令泵。。。。。。

我没有搜索到你说的这个概念?
麻烦告知下 谢谢!

出0入0汤圆

发表于 2017-8-10 09:43:42 | 显示全部楼层
catwill 发表于 2015-4-3 16:35
呵呵,楼上应该是逗号表达式吧……
但是我相信他的本意是分号~

逗号表达式挺好的哇
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-6-18 09:34

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表