电子量产工具项目框架--基本思想


前言

在我学习完韦东山老师的电子量产项目后,发现框架都是挺简单实用的,简单来说就是通过构建一个中间层去管理低一层的程序。

在这里将和大家一起分析韦东山老师的一个书写的核心框架。最后使用C++来简单重构部分代码,使得可阅读性和可拓展性更强一些。毕竟C++更适合应用层面的开发。

韦老师电子量产项目链接点这里
韦老师的Linux快速入门链接点这里


1、电子量产项目框架

1.1 基本理解

电子项目的框架是不难的,思想是很容易理解,一般来说,有C的基础不错的情况下,看几个视频,阅读一些韦老师的源码就很好理解了。

这个框架,使用的是一种面向对象的思想。管理器对象能够访问其具体模块的实现,也就是继承的思想。而且也能够通过一个统一的接口访问不同的函数,这个是多态的思想,管理器和具体模块的实现都是封装给使用者的,这个是封装的思想。既然都是利用了这个基本思想,那就能够使用C++来进行代码的重构(往后会实现)

我们可以清楚的看到下图的关系,是通过一个管理器实现不同模块的访问。这让我们想到了,Linux的open和write这两个比较核心的函数,也是直接通过一个统一的接口就可以提供外界访问不同的模块的信息。这里的思路也open和write类似。
在这里插入图片描述

1.2 简单输入管理器代码实现

既然已经分析出了,那是不是就可以实现上面的思路。说干就干,马上写用C一个简单的。就写一个简单和输入管理器吧,需求就是,通过统一接口输入按键值(按键值编码为:1~4)和字符串,然后通过统一接口读取相关的按键数据;在输入数据前还需要注册设备,避免数据胡乱的输入,只输入已经注册设备的数据。

#include "stdio.h"
#include<malloc.h>
#include<string.h>

#include<windows.h>
/*
 *
 *思路:使用链表的方式来管理 管理器成员
 *
 */
typedef void (*IDEVCRE)(void);/*设备初始化函数*/
typedef void (*IDEVDEL)(void);/*设备删除函数*/

struct iManagerCtr;

/*输入管理器对象成员*/
struct inputmember
{
    
    
    int inputType;/*输入类型*/
    int keyVal;   /*输入按键值*/
    char strVal[10];  /*输入字串值*/
    int id;       /*对象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);/*将缓冲区数据+1*/
            
            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 1\r\n");
}

void RegDev2()
{
    
    
    printf("RegDev 2\r\n");
}

void DelDev1()
{
    
    
    printf("DelDev 1\r\n");
}

void DelDev2()
{
    
    
    printf("DelDev 2\r\n");
}

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=%s\r\n",GetData.id,GetData.inputType,GetData.keyVal,GetData.strVal);
    imger.cvtbl->read(&imger,&GetData);
    printf("id = %d ,itype = %d ,keyVal =%d strVal=%s\r\n",GetData.id,GetData.inputType,GetData.keyVal,GetData.strVal);
    /*删除管理的对象*/
    inputManagerdelete(&imger);
    system("pause");
}
int main()
{
    
    
     Test();
}

1.3 简单输入管理器代码运行结果

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;       /*对象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 输入管理器缓冲区

电子量产工具实现的缓冲区,使用一个环形队列的方式来存放数据。但是将缓冲区的内容定义在了一个文件中,也就是不同的管理器共用一个缓冲区。个人更倾向于将缓冲区私有化,也就是一个管理器对象对应一个对应一个缓冲区。这样我们可以构造多个输入管理器来管理我们的内容。

/* start of 实现环形buffer */
#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),就是灵活度不太够,如果需要多个缓冲区,就需要对代码结构进行比较大的调整。

1.5.3 创建管理器成员

下面就是具体的注册的过程,由于文件定义有点乱,就把输入管理器实现的部分和子模块实现部分的.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)
{
    
    
	/* regiseter touchscreen */
	extern void TouchscreenRegister(void);
	TouchscreenRegister();

	/* regiseter netinput */
	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;
	
	/* for each inputdevice, init, pthread_create */
	PInputDevice ptTmp = g_InputDevs;
	while (ptTmp)
	{
    
    
		/* init device */
		ret = ptTmp->DeviceInit();

		/* pthread create */
		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.5 使用例子

由于命名有点怪,看注释可以看出,实际上电子量产输入管理器的使用和一开始创建的简单的输入管理器的使用思路是一致的。不同的就是,输入数据是使用线程来输入的,在测试函数里面不能够显示看出来。看上面的1.5.4输入管理器的读写就可以简单分析出来。

#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <linux/fb.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>

#include <input_manager.h>

int main(int argc, char **argv)
{
    
    
	int ret;
	InputEvent event;
	/*注册对象+设备初始化*/
	InputInit();
	IntpuDeviceInit();

	while (1)
	{
    
    	
		/*从缓冲区读取和显示*/
		printf("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
		ret = GetInputEvent(&event);

		printf("%s %s %d, ret = %d\n", __FILE__, __FUNCTION__, __LINE__, ret);
		if (ret) {
    
    
			printf("GetInputEvent err!\n");
			return -1;
		}
		else
		{
    
    
			printf("%s %s %d, event.iType = %d\n", __FILE__, __FUNCTION__, __LINE__, event.iType );
			if (event.iType == INPUT_TYPE_TOUCH)
			{
    
    
				printf("Type      : %d\n", event.iType);
				printf("iX        : %d\n", event.iX);
				printf("iY        : %d\n", event.iY);
				printf("iPressure : %d\n", event.iPressure);
			}
			else if (event.iType == INPUT_TYPE_NET)
			{
    
    
				printf("Type      : %d\n", event.iType);
				printf("str       : %s\n", event.str);
			}
		}
	}
	return 0;	
}

1.5.5 简单总结

说了这么多,让我们用一个图来对这个思想进行总结吧。就用文章提开始写的输入管理器的例子叭~
在这里插入图片描述

难点
从上图可以看出,创建这样一个管理器最难的就是需要使用到的数据的抽象和提供合适的接口函数(使用合适的数据结构)进行管理。

应用
应用的话,都可以用。也就是将众多子模块进行统一管理,即便下层驱动更改了,只要输入的信息不变,那么代码就可以复用。可以说,这样的框架能够提高代码的复用性。

2、碎碎念

电子量产项目这个项目从头到尾都是使用管理器的思路来进行管理的,明白其中的思想,阅读源码就很简单了。阅读这个项目的源码比较难的就是函数命名不够人性化,具体设备的读取和处理需要专业的知识(之前可以看之前的Linux应用开发的视频重新复习一下)比较难理解。如果不是很理解具体的实现细节也不要紧,这个项目最重要是学到这个框架或者思路就行。

最后不得不吐糟一句,电子量产项目这个代码命名和代码调用真的是难顶,能把人给绕晕。

C++重构由于文章边幅有限和作者功底不够,得先继续深入复习一下,过段时间再进行C++重构文章的编写。

猜你喜欢

转载自blog.csdn.net/weixin_46185705/article/details/125939047