嵌入式C语言OO编程方法

2024-08-24 01:32

本文主要是介绍嵌入式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编程方法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1101060

相关文章

PHP轻松处理千万行数据的方法详解

《PHP轻松处理千万行数据的方法详解》说到处理大数据集,PHP通常不是第一个想到的语言,但如果你曾经需要处理数百万行数据而不让服务器崩溃或内存耗尽,你就会知道PHP用对了工具有多强大,下面小编就... 目录问题的本质php 中的数据流处理:为什么必不可少生成器:内存高效的迭代方式流量控制:避免系统过载一次性

MySQL的JDBC编程详解

《MySQL的JDBC编程详解》:本文主要介绍MySQL的JDBC编程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录前言一、前置知识1. 引入依赖2. 认识 url二、JDBC 操作流程1. JDBC 的写操作2. JDBC 的读操作总结前言本文介绍了mysq

python获取指定名字的程序的文件路径的两种方法

《python获取指定名字的程序的文件路径的两种方法》本文主要介绍了python获取指定名字的程序的文件路径的两种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要... 最近在做项目,需要用到给定一个程序名字就可以自动获取到这个程序在Windows系统下的绝对路径,以下

JavaScript中的高级调试方法全攻略指南

《JavaScript中的高级调试方法全攻略指南》什么是高级JavaScript调试技巧,它比console.log有何优势,如何使用断点调试定位问题,通过本文,我们将深入解答这些问题,带您从理论到实... 目录观点与案例结合观点1观点2观点3观点4观点5高级调试技巧详解实战案例断点调试:定位变量错误性能分

Python中 try / except / else / finally 异常处理方法详解

《Python中try/except/else/finally异常处理方法详解》:本文主要介绍Python中try/except/else/finally异常处理方法的相关资料,涵... 目录1. 基本结构2. 各部分的作用tryexceptelsefinally3. 执行流程总结4. 常见用法(1)多个e

使用docker搭建嵌入式Linux开发环境

《使用docker搭建嵌入式Linux开发环境》本文主要介绍了使用docker搭建嵌入式Linux开发环境,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录1、前言2、安装docker3、编写容器管理脚本4、创建容器1、前言在日常开发全志、rk等不同

JavaScript中比较两个数组是否有相同元素(交集)的三种常用方法

《JavaScript中比较两个数组是否有相同元素(交集)的三种常用方法》:本文主要介绍JavaScript中比较两个数组是否有相同元素(交集)的三种常用方法,每种方法结合实例代码给大家介绍的非常... 目录引言:为什么"相等"判断如此重要?方法1:使用some()+includes()(适合小数组)方法2

504 Gateway Timeout网关超时的根源及完美解决方法

《504GatewayTimeout网关超时的根源及完美解决方法》在日常开发和运维过程中,504GatewayTimeout错误是常见的网络问题之一,尤其是在使用反向代理(如Nginx)或... 目录引言为什么会出现 504 错误?1. 探索 504 Gateway Timeout 错误的根源 1.1 后端

GO语言短变量声明的实现示例

《GO语言短变量声明的实现示例》在Go语言中,短变量声明是一种简洁的变量声明方式,使用:=运算符,可以自动推断变量类型,下面就来具体介绍一下如何使用,感兴趣的可以了解一下... 目录基本语法功能特点与var的区别适用场景注意事项基本语法variableName := value功能特点1、自动类型推

GO语言中函数命名返回值的使用

《GO语言中函数命名返回值的使用》在Go语言中,函数可以为其返回值指定名称,这被称为命名返回值或命名返回参数,这种特性可以使代码更清晰,特别是在返回多个值时,感兴趣的可以了解一下... 目录基本语法函数命名返回特点代码示例命名特点基本语法func functionName(parameters) (nam