嵌入式C语言OO编程方法

嵌入式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下测试通过。

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值