- 前言
- 1、电子量产项目框架
- 1.1 基本理解
- 1.2 简单输入管理器代码实现
- 1.3 简单输入管理器代码运行结果
- 1.4 简单输入管理器思路简要分析
- 1.5 电子量产项目输入管理器源码分析
- 1.5.1 输入管理器数据结构
- 1.5.2 输入管理器缓冲区
- 1.5.3 创建管理器成员
- 1.5.4 输入管理器的读和写
- 1.5.5 使用例子
- 1.5.5 简单总结
- 2、碎碎念
前言
在我学习完韦东山老师的电子量产项目后,发现框架都是挺简单实用的,简单来说就是通过构建一个中间层去管理低一层的程序。
在这里将和大家一起分析韦东山老师的一个书写的核心框架。最后使用C++来简单重构部分代码,使得可阅读性和可拓展性更强一些。毕竟C++更适合应用层面的开发。
韦老师电子量产项目链接点这里
韦老师的Linux快速入门链接点这里
1、电子量产项目框架 1.1 基本理解
电子项目的框架是不难的,思想是很容易理解,一般来说,有C的基础不错的情况下,看几个视频,阅读一些韦老师的源码就很好理解了。
这个框架,使用的是一种面向对象的思想。管理器对象能够访问其具体模块的实现,也就是继承的思想。而且也能够通过一个统一的接口访问不同的函数,这个是多态的思想,管理器和具体模块的实现都是封装给使用者的,这个是封装的思想。既然都是利用了这个基本思想,那就能够使用C++来进行代码的重构(往后会实现)
我们可以清楚的看到下图的关系,是通过一个管理器实现不同模块的访问。这让我们想到了,Linux的open和write这两个比较核心的函数,也是直接通过一个统一的接口就可以提供外界访问不同的模块的信息。这里的思路也open和write类似。
既然已经分析出了,那是不是就可以实现上面的思路。说干就干,马上写用C一个简单的。就写一个简单和输入管理器吧,需求就是,通过统一接口输入按键值(按键值编码为:1~4)和字符串,然后通过统一接口读取相关的按键数据;在输入数据前还需要注册设备,避免数据胡乱的输入,只输入已经注册设备的数据。
#include "stdio.h" #include1.3 简单输入管理器代码运行结果#include #include typedef void (*IDEVCRE)(void); typedef void (*IDEVDEL)(void); struct iManagerCtr; struct inputmember { int inputType; int keyVal; char strVal[10]; int id; struct inputmember *next; }; typedef struct inputManager { struct inputmember *buffer; struct inputmember *memberHead; unsigned int count; unsigned int maxbuff; struct iManagerCtr *cvtbl; }inputManager; struct iManagerCtr { int (*write)(inputManager * const Me,struct inputmember data); int (*regdev)(inputManager * const Me,int id,IDEVCRE dev); int (*deldev)(inputManager * const Me,int id,IDEVDEL dev); int (*read)(inputManager * const Me,struct inputmember* data); }; void inputManagerCreate(inputManager * const Me,unsigned int maxbuffercount); void inputManagerdelete(inputManager * const Me); static int WriteInputManger(inputManager * const Me,struct inputmember data) { struct inputmember * cur; struct inputmember t; if(Me->count>=Me->maxbuff-1) return 0; cur=Me->memberHead; while(cur!=NULL) { if(cur->id==data.id) { memcpy((Me->buffer+Me->count++),&data,sizeof(struct inputmember)); return 1; } cur=cur->next; } return 0; } static int ReadDevicedata(inputManager * const Me,struct inputmember* data) { if(Me->count>0) { memcpy(data,(Me->buffer+(--Me->count)),sizeof(struct inputmember)); return 1; } return 0; } static int RegristedDevice(inputManager * const Me,int id,IDEVCRE dev) { struct inputmember *cur,*create; IDEVCRE doDevCreate=dev; cur=Me->memberHead; create=(struct inputmember *)malloc(sizeof(struct inputmember)); create->id=id; create->next=cur->next; cur->next=create; doDevCreate(); return 1; } static int DeleteDevice(inputManager * const Me,int id,IDEVDEL dev) { struct inputmember *cur,*tmp; IDEVCRE doDevDelete=dev; cur=Me->memberHead; while(cur->next!=NULL) { if(cur->id==id) { tmp=cur; free(tmp); cur->next=cur->next->next; doDevDelete(); return 1; } cur=cur->next; } return 0; } void inputManagerCreate(inputManager * const Me,unsigned int maxbuffercount) { struct iManagerCtr *iMagevtal; if(maxbuffercount<=0) return ; iMagevtal=(struct iManagerCtr *)malloc(sizeof(struct iManagerCtr)); Me->buffer=(struct inputmember *)malloc(sizeof(struct inputmember)*maxbuffercount); Me->memberHead=(struct inputmember *)malloc(sizeof(struct inputmember)); memset(Me->buffer,0,sizeof(struct inputmember)*maxbuffercount); Me->count=0; Me->maxbuff=maxbuffercount; Me->memberHead->next=NULL; iMagevtal->read=&ReadDevicedata; iMagevtal->write=&WriteInputManger; iMagevtal->regdev=&RegristedDevice; iMagevtal->deldev=&DeleteDevice; Me->cvtbl=iMagevtal; } void inputManagerdelete(inputManager * const Me) { struct inputmember *cur,*del; cur=Me->memberHead->next; while (cur!=NULL) { del=cur; cur=cur->next; free(del); } free(Me->memberHead); free(Me->cvtbl); free(Me->buffer); } void RegDev1() { printf("RegDev 1rn"); } void RegDev2() { printf("RegDev 2rn"); } void DelDev1() { printf("DelDev 1rn"); } void DelDev2() { printf("DelDev 2rn"); } void PrintfNode(inputManager * const Me) { struct inputmember *cur,*del; cur=Me->memberHead->next; while (cur!=NULL) { printf("%d ",cur->id); cur=cur->next; } } void Test() { inputManager imger; inputManagerCreate(&imger,100); imger.cvtbl->regdev(&imger,1,RegDev1); imger.cvtbl->regdev(&imger,2,RegDev2); struct inputmember RegDev1; struct inputmember RegDev2; struct inputmember GetData; RegDev1.id=1; RegDev1.inputType=1; RegDev1.keyVal=1; strcpy(RegDev1.strVal,""); RegDev2.id=2; RegDev2.inputType=2; strcpy(RegDev2.strVal,"RegDev2"); imger.cvtbl->write(&imger,RegDev2); imger.cvtbl->write(&imger,RegDev1); imger.cvtbl->read(&imger,&GetData); printf("id = %d ,itype = %d ,keyVal =%d strVal=%srn",GetData.id,GetData.inputType,GetData.keyVal,GetData.strVal); imger.cvtbl->read(&imger,&GetData); printf("id = %d ,itype = %d ,keyVal =%d strVal=%srn",GetData.id,GetData.inputType,GetData.keyVal,GetData.strVal); inputManagerdelete(&imger); system("pause"); } int main() { Test(); }
RegDev 1 RegDev 2 id = 1 ,itype = 1 ,keyVal =1 strVal= id = 2 ,itype = 2 ,keyVal =0 strVal=RegDev2 请按任意键继续. . .1.4 简单输入管理器思路简要分析
对上述的实现的一个非常简单的输入管理器进行简单分析。首先我们根据需求构造出合适的数据结构,还需要将内容进行封装起来,因此,我们需要用到结构体。结构体构造数据合适不合适,直接影响代码的后续开发,这步还是蛮重要的。然后根据需求,我们需要预留接口给上一层应用调用,因此最好是使用函数指针来预留接口。对于输入管理器,有一点小特殊,可能会有多次输入,我们需要将输入的数据都进行保存,因此需要用到一个缓冲区,这里为了方便,就使用最简单的缓冲区。思路分析到这里就已经可以了。
开头说了,这个框架利用了面向对象的编程思想,因此,我选择了一般C++类的实现思路来写C语言,如果看的不是很懂C语言实现面向对象的编程,点这里,我有写文章介绍C语言面向对象的设计方式
1.5 电子量产项目输入管理器源码分析有了上面的简单基础,就可以分析韦老师的源码,看是如何实现的。为了和上述输入管理器一致,这里也是对韦老师的输入管理器进行分析,看看他是如何实现的,以及这样写代码的优劣。
文件结构定义如下,输入模块都是放在input文件夹,定义都是在include文件夹。文件结构还算清晰。
1.5.1 输入管理器数据结构看这个数据接口之前,让我们先对需求进行简单的分析,需求一共就两个。两个输入模块,一个是触摸屏是输入,一个是网络的输入。触摸屏输入就是需要XY坐标以及压力值等等,二网络输入,可以用一个字符串进行保存。根据这两个简单的需求就可以构造出一个合适的数据结构。
typedef struct InputEvent {
struct timeval tTime;
int iType;
int iX;
int iY;
int iPressure;
char str[1024];
}InputEvent, *PInputEvent;
typedef struct InputDevice {
char *name;
int (*GetInputEvent)(PInputEvent ptInputEvent);
int (*DeviceInit)(void);
int (*DeviceExit)(void);
struct InputDevice *ptNext;
}InputDevice, *PInputDevice;
void RegisterInputDevice(PInputDevice ptInputDev);
void InputInit(void);
void IntpuDeviceInit(void);
int GetInputEvent(PInputEvent ptInputEvent);
对比
会看上面的一个简单的输入管理器的代码进行简单比对,可以看出来,这个简单的管理器的构造方式和韦老师的基本一致,不同的之一:上面的一个简单的例子使用了一个函数表来存放输入管理器的行为函数;不同之二:上面的构造的数据缓冲区直接定义在了输入管理器之中。这样更符合直观的认知。
struct iManagerCtr;
struct inputmember
{
int inputType;
int keyVal;
char strVal[10];
int id;
struct inputmember *next;
};
typedef struct inputManager
{
struct inputmember *buffer;
struct inputmember *memberHead;
unsigned int count;
unsigned int maxbuff;
struct iManagerCtr *cvtbl;
}inputManager;
struct iManagerCtr
{
int (*write)(inputManager * const Me,struct inputmember data);
int (*regdev)(inputManager * const Me,int id,IDEVCRE dev);
int (*deldev)(inputManager * const Me,int id,IDEVDEL dev);
int (*read)(inputManager * const Me,struct inputmember* data);
};
1.5.2 输入管理器缓冲区
电子量产工具实现的缓冲区,使用一个环形队列的方式来存放数据。但是将缓冲区的内容定义在了一个文件中,也就是不同的管理器共用一个缓冲区。个人更倾向于将缓冲区私有化,也就是一个管理器对象对应一个对应一个缓冲区。这样我们可以构造多个输入管理器来管理我们的内容。
#define BUFFER_LEN 20
static int g_iRead = 0;
static int g_iWrite = 0;
static InputEvent g_atInputEvents[BUFFER_LEN];
static int isInputBufferFull(void)
{
return (g_iRead == ((g_iWrite + 1) % BUFFER_LEN));
}
static int isInputBufferEmpty(void)
{
return (g_iRead == g_iWrite);
}
static void PutInputEventToBuffer(PInputEvent ptInputEvent)
{
if (!isInputBufferFull())
{
g_atInputEvents[g_iWrite] = *ptInputEvent;
g_iWrite = (g_iWrite + 1) % BUFFER_LEN;
}
}
static int GetInputEventFromBuffer(PInputEvent ptInputEvent)
{
if (!isInputBufferEmpty())
{
*ptInputEvent = g_atInputEvents[g_iRead];
g_iRead = (g_iRead + 1) % BUFFER_LEN;
return 1;
}
else
{
return 0;
}
}
对比
对比上面实现的最简单的输入管理器,代码太长就不贴了。最简单的输入管理器直接将缓冲区内嵌到了管理器当中,使用指针来访问缓冲区内容。这样可以动态创建缓冲区,更加的灵活,但是处理不好容易内存泄漏。使用静态缓冲区(不使用malloc),就是灵活度不太够,如果需要多个缓冲区,就需要对代码结构进行比较大的调整。
下面就是具体的注册的过程,由于文件定义有点乱,就把输入管理器实现的部分和子模块实现部分的.c文件里面包含组测内容都放在一起了。实际看起来还是比较乱的,结构不够分明,但也能够理解。个人不推荐这种写法,可阅读性是真的不搞,东一块西一块的。
static InputDevice g_tTouchscreenDev ={
.name = "touchscreen",
.GetInputEvent = TouchscreenGetInputEvent,
.DeviceInit = TouchscreenDeviceInit,
.DeviceExit = TouchscreenDeviceExit,
};
void TouchscreenRegister(void)
{
RegisterInputDevice(&g_tTouchscreenDev);
}
void RegisterInputDevice(PInputDevice ptInputDev)
{
ptInputDev->ptNext = g_InputDevs;
g_InputDevs = ptInputDev;
}
void InputInit(void)
{
extern void TouchscreenRegister(void);
TouchscreenRegister();
extern void NetInputRegister(void);
NetInputRegister();
}
1.5.4 输入管理器的读和写
在电子量产项目中,这块差不多是最难的了,原因就是使用了多个线程进行调度。
这里的逻辑是在管理器成员注册的时候将已经写好的管理器成员的内容放入链表中。通过遍历链表,得到不同的内容,之后根据不同的内容,调用不同的初始化函数(IntpuDeviceInit)。
然后创建两个线程,分别传入管理器成员的信息。调用之前初始化好的函数,就可以在线程里面得到相应的数据。如果得到数据则写入缓冲区,并唤醒,没有则休眠线程。
static void *input_recv_thread_func (void *data)
{
PInputDevice ptInputDev = (PInputDevice)data;
InputEvent tEvent;
int ret;
while (1)
{
ret = ptInputDev->GetInputEvent(&tEvent);
if (!ret)
{
pthread_mutex_lock(&g_tMutex);
PutInputEventToBuffer(&tEvent);
pthread_cond_signal(&g_tConVar);
pthread_mutex_unlock(&g_tMutex);
}
}
return NULL;
}
void IntpuDeviceInit(void)
{
int ret;
pthread_t tid;
PInputDevice ptTmp = g_InputDevs;
while (ptTmp)
{
ret = ptTmp->DeviceInit();
if (!ret)
{
ret = pthread_create(&tid, NULL, input_recv_thread_func, ptTmp);
}
ptTmp= ptTmp->ptNext;
}
}
int GetInputEvent(PInputEvent ptInputEvent)
{
InputEvent tEvent;
int ret;
pthread_mutex_lock(&g_tMutex);
if (GetInputEventFromBuffer(&tEvent))
{
*ptInputEvent = tEvent;
pthread_mutex_unlock(&g_tMutex);
return 0;
}
else
{
pthread_cond_wait(&g_tConVar, &g_tMutex);
if (GetInputEventFromBuffer(&tEvent))
{
*ptInputEvent = tEvent;
ret = 0;
}
else
{
ret = -1;
}
pthread_mutex_unlock(&g_tMutex);
}
return ret;
}
模仿
模仿这个思路,我们一开始的那个输入管理器也是能够实现多线程的读写,只不过在读写函数的调用子模块上面,由于没有将子模块的读写都注册,需要在线程中手动的调用相应的读写模块,也就是即便实现方式不同,只要思路一致,就基本可以快速复现类似的功能。
由于命名有点怪,看注释可以看出,实际上电子量产输入管理器的使用和一开始创建的简单的输入管理器的使用思路是一致的。不同的就是,输入数据是使用线程来输入的,在测试函数里面不能够显示看出来。看上面的1.5.4输入管理器的读写就可以简单分析出来。
#include1.5.5 简单总结#include #include #include #include #include #include #include #include #include int main(int argc, char **argv) { int ret; InputEvent event; InputInit(); IntpuDeviceInit(); while (1) { printf("%s %s %dn", __FILE__, __FUNCTION__, __LINE__); ret = GetInputEvent(&event); printf("%s %s %d, ret = %dn", __FILE__, __FUNCTION__, __LINE__, ret); if (ret) { printf("GetInputEvent err!n"); return -1; } else { printf("%s %s %d, event.iType = %dn", __FILE__, __FUNCTION__, __LINE__, event.iType ); if (event.iType == INPUT_TYPE_TOUCH) { printf("Type : %dn", event.iType); printf("iX : %dn", event.iX); printf("iY : %dn", event.iY); printf("iPressure : %dn", event.iPressure); } else if (event.iType == INPUT_TYPE_NET) { printf("Type : %dn", event.iType); printf("str : %sn", event.str); } } } return 0; }
说了这么多,让我们用一个图来对这个思想进行总结吧。就用文章提开始写的输入管理器的例子叭~
难点
从上图可以看出,创建这样一个管理器最难的就是需要使用到的数据的抽象和提供合适的接口函数(使用合适的数据结构)进行管理。
应用
应用的话,都可以用。也就是将众多子模块进行统一管理,即便下层驱动更改了,只要输入的信息不变,那么代码就可以复用。可以说,这样的框架能够提高代码的复用性。
电子量产项目这个项目从头到尾都是使用管理器的思路来进行管理的,明白其中的思想,阅读源码就很简单了。阅读这个项目的源码比较难的就是函数命名不够人性化,具体设备的读取和处理需要专业的知识(之前可以看之前的Linux应用开发的视频重新复习一下)比较难理解。如果不是很理解具体的实现细节也不要紧,这个项目最重要是学到这个框架或者思路就行。
最后不得不吐糟一句,电子量产项目这个代码命名和代码调用真的是难顶,能把人给绕晕。
C++重构由于文章边幅有限和作者功底不够,得先继续深入复习一下,过段时间再进行C++重构文章的编写。



