本文主要是介绍嵌入式C语言OO编程方法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
原文转自:http://blog.csdn.net/ice1224/article/details/4414183
嵌入式C语言OO编程方法
目前嵌入式系统的代码量也在逐渐增加,即使对于不是非常复杂的嵌入式系统,单板执行代码量在1M以下,源代码往往也会在两三万行以上。因此代码重用的要求也渐渐迫切。
本文参考OO与组件技术,提出嵌入式C语言组件编程方法,针对没有内建专业OS的嵌入式中小型应用,目标是实现源代码级的复用,提高开发效率。
- 组件编程思想
由于C语言没有Class,对象采用struct进行描述。
组件由一系列嵌入式COM对象组成,以下将嵌入式COM对象称为ECOM对象。组件程序以C文件作为基本单位。每个ECOM对象在组件程序中加以具体实现。组件程序可以用Lib的方式发布给客户。
客户程序使用组件进行编程,客户访问ECOM对象的方法是通过接口,每个ECOM对象包含相应的接口。接口是对具体对象的抽象,只包括ECOM对象的方法指针,不包括方法的具体实现与ECOM对象的数据。接口在用户程序不存在实例。对于用户仅仅开放了接口,通过接口与实现的分离,降低客户与ECOM对象的耦合度,隐藏了具体对象的实现细节,有助于减少耦合带来的错误。
接口的C语言描述是只包含多个函数指针的struct,通过接口可以实现多态性,所有拥有相同接口的对象是相关的对象。接口必须是完整而最小的,接口一经定义,应该是尽量不变的。因此接口需要在程序设计时加以仔细考虑,接口的不变性能减少对客户程序的影响,最小性则保证客户使用组件的简单性。
一个ECOM对象可以拥有多个接口,但因为C语言的函数总是全局的,不存在成员函数,而且在客户端不能暴露对象,所以ECOM对象的C语言struct描述包含自身的数据与内嵌接口对象。
由于不同的对象可以拥有相同的接口,所以每个对象都必须对应一个绑定全局函数,以完成对接口的绑定。绑定函数由组件程序实现,由客户进行调用。
为了提高效率,接口方法应该尽量传址,因此不同对象间数据通过接口方法的参数列的结构指针实现传递。
由于是源代码级的重用,组件变化客户将重新编译连接,所以不存在接口查询问题,ECOM无需引入接口ID与对象ID。
由于C语言没有继承机制,仍采用struct,将有关系的对象转变为一系列独立的struct,采用组合技术复用。根据以上思想,本文提出了抽象接口和句柄接口两种编程模式。
三、抽象接口编程模式
1、ECOM对象接口声明
//IComm 通讯接口 IComm.H
#ifndef ICOMM_H
#define ICOMM_H
struct TSendData;
struct TReceiveData;
struct IComm
{
int (*pfSendData)(TSendData *pSendData);
int (*pfReceiveData)(TReceiveData *pReceiveData);
int (*pfGeti)(void);
};
int BindICommRS232 (IComm **p); //对接口的绑定
int BindICommRS485 (IComm **p); //对接口的绑定
#endif
以上IComm接口仅包括发送、接收方法,传入参数是发送、接收数据结构指针,函数调用成功返回非0值。
接口命名以I开头。
由于对象可能实现多个接口(本例只有一个接口),所以绑定函数将接口指针放到参数表中,这样多个接口采用多个参数即可。参数采用指针的指针进行传递(详见具体实现)。函数调用成功返回非0值。一个ECOM对象只有一个绑定函数。
2、ECOM对象声明
//COMM.H
#ifndef COMM_H
#define COMM_H
#include "../interface/IComm.H"
struct TRS232 // RS232对象
{
//对接口的复制,必须放到最前面
IComm ICommRS232;
//数据.....
int i;
};
int SendDataRS232(TSendData *pSendData);
int ReceiveDataRS232(TReceiveData *pReceiveData);
int GetiRS232(void);
int BindICommRS232(IComm **p); //对接口的绑定
///
struct TRS485 // RS485对象
{
//对接口的复制,必须放到最前面
IComm ICommRS485;
//数据.....
int i;
};
// RS485对象方法
int SendDataRS485(TSendData *pSendData);
int ReceiveDataRS485(TReceiveData *pReceiveData);
int GetiRS485(void);
int BindICommRS485(IComm **p); //对接口的绑定
#endif
此处有一个难题,TRS232对象的数据如何在内存中创建,否则还需要开放给用户,这样基体实现就暴露给了用户。困难的本质原因是C缺乏封装(关键是数据与方法的封装)和继承(从接口到具体类的继承)。解决此问题的方法是在RS232对象TRS232中复制接口,这和C++虚函数机制类似,看来或许还不如用C++呢。
3、ECOM对象实现(组件程序)
//Comm.C
//RS232对象
#include "Comm.H"
#include <iostream.H>
#include <malloc.h>
TRS232 *pRS232; //全局堆指针
TRS485 *pRS485; //全局堆指针
int SendDataRS232(TSendData *pSendData)
{
cout<<"SendDataRS232"<<endl;
return 1;
}
int ReceiveDataRS232(TReceiveData *pReceiveData)
{
cout<<"ReceiveDataRS232"<<endl;
return 1;
}
int GetiRS232(void)
{
return pRS232->i;
}
int BindICommRS232(IComm **p) //对接口的绑定
{
static char cCount = 0;
if(cCount)
{
return 1; //防止多次绑定接口
}
else
{
pRS232 = (TRS232*)malloc(sizeof(TRS232)); //只能采用堆指针传递
if (pRS232 == NULL)
return 0;
//接口绑定
pRS232->ICommRS232.pfSendData = SendDataRS232;
pRS232->ICommRS232.pfReceiveData = ReceiveDataRS232;
pRS232->ICommRS232.pfGeti = GetiRS232;
//完成对象数据的构造
pRS232->i =232;
}
cCount = 1;
*p = (IComm*)pRS232;
return 1;
}
///RS485对象///
int SendDataRS485(TSendData *pSendData)
{
cout<<"SendDataRS485"<<endl;
return 1;
}
int ReceiveDataRS485(TReceiveData *pReceiveData)
{
cout<<"ReceiveDataRS485"<<endl;
return 1;
}
int GetiRS485(void)
{
return pRS485->i;
}
int BindICommRS485(IComm **p) //对接口的绑定
{
static char cCount = 0;
if(cCount)
{
return 1; //防止多次绑定接口
}
else
{
pRS485 = (TRS485*)malloc(sizeof(TRS485)); //只能采用堆指针传递
if (pRS485 == NULL)
return 0;
//接口绑定
pRS485->ICommRS485.pfSendData = SendDataRS485;
pRS485->ICommRS485.pfReceiveData = ReceiveDataRS485;
pRS485->ICommRS485.pfGeti = GetiRS485;
//完成对象数据的构造
pRS485->i =485;
}
cCount = 1;
*p = (IComm*)pRS485;
return 1;
}
Comm.c编译连接为Lib文件,源代码不需提供给客户。
4、客户程序
//Client.c
#include "../interface/IComm.H" //客户程序仅仅需要接口头文件,隐藏ECOM对象细节
#include "Client.h"
#include <malloc.h>
#include <iostream.h>
IComm *IpRS232 = 0;
IComm *IpRS485 = 0;
TSendData SendData = {1};
TReceiveData ReceiveData = {2};
void main(void)
{
if(!BindICommRS232(&IpRS232)) return; //绑定接口
if(!BindICommRS485(&IpRS485)) return;
IpRS232->pfSendData(&SendData); //用户通过接口完成功能
IpRS232->pfReceiveData(&ReceiveData);
int i = IpRS232->pfGeti();
cout<<i<<endl;
(*IpRS485->pfSendData) (&SendData); //用户通过接口完成功能
(*IpRS485->pfReceiveData) (&ReceiveData);
i = IpRS485->pfGeti();
cout<<i<<endl;
free(IpRS232);
free(IpRS485);
return;
}
以上设计方法有几点好处:
1、 对用户仅开放接口,信息隐藏,耦合度降低,完全杜绝了extern变量,而且接口一经绑定,对象初始化工作已经完成,不必用户参与。总之用户程序出错的机会减小。
2、 代码重用程度高,由于用户程序使用标准的接口,即使接口的具体实现(Comm.C)变化,用户程序不需要修改,重新连接即可,开发效率提高,出错机会也相应减小。
以上设计方法缺点在于访问对象必须采用接口方法,函数调用的开销较大,不过这一点开销是值得的。
这种模式适用于程序员间代码重用。
四、句柄接口编程模式
句柄接口编程模式强调的没有抽象接口严格,将具体实现交给句柄对象去完成,用户访问接口方法。但由于接口与句柄对象都需要暴露给用户,所以信息隐藏不够彻底。但用户对句柄对象实现可以完全不关心,用户与实现的耦合程度已经大大降低,在一定程度上将接口与实现分离。
这种模式较简单,适用于单一程序员的代码重用。
以下是一个实现的例子。
1、句柄对象声明
//Handle.H
#ifndef HANDLE_H
#define HANDLE_H
struct TAImp //具体实现的句柄对象
{
//接口
int (*pfFunc)(TAImp *); //具体实现接口,句柄对象通过参数表传递
//数据
int i;
};
int FuncImp(TAImp *pAImp); //具体实现
int BindAImp(TAImp **pAImp); //用户绑定句柄对象的方法
#endif
2、接口声明
//Interface.h
#ifndef INTERFACE_H
#define INTERFACE_H
#include "Handle.h"
int Func(TAImp *pAImp); //接口仅是普通的函数原型
#endif
3、句柄对象实现
//Handle.C
#include "Handle.h"
#include <malloc.h>
#include <iostream.h>
int FuncImp(TAImp *pAImp)
{
cout<<pAImp->i<<endl;
pAImp->i++;
return 1;
}
int BindAImp(TAImp **pAImp)
{
static char cCount = 0;
if(cCount)
{
return 1; //防止多次绑定接口
}
else
{
*pAImp = (TAImp *)malloc(sizeof(TAImp));
if (*pAImp == NULL)
return 0;
//接口绑定
(*pAImp)->pfFunc = FuncImp;
//对象数据构造
(*pAImp)->i=10;
}
cCount = 1;
return 1;
}
4、接口实现
//Interface.C
#include "Interface.h"
int Func(TAImp *pAImp)
{
(*pAImp->pfFunc) (pAImp); //只是简单调用句柄对象方法,将具体实现交给句柄对象
return 1;
}
5、用户程序
//User.c
#include "Interface.h"
#include "Handle.h"
#include <malloc.h>
void main(void)
{
TAImp *pAImp = 0;
int i = BindAImp(&pAImp);
if(!i) return;
Func(pAImp);
Func(pAImp);
free(pAImp);
return;
}
为了测试方便,所有代码在VC6.0下测试通过。
这篇关于嵌入式C语言OO编程方法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!