嵌入式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

相关文章

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

浅谈主机加固,六种有效的主机加固方法

在数字化时代,数据的价值不言而喻,但随之而来的安全威胁也日益严峻。从勒索病毒到内部泄露,企业的数据安全面临着前所未有的挑战。为了应对这些挑战,一种全新的主机加固解决方案应运而生。 MCK主机加固解决方案,采用先进的安全容器中间件技术,构建起一套内核级的纵深立体防护体系。这一体系突破了传统安全防护的局限,即使在管理员权限被恶意利用的情况下,也能确保服务器的安全稳定运行。 普适主机加固措施:

webm怎么转换成mp4?这几种方法超多人在用!

webm怎么转换成mp4?WebM作为一种新兴的视频编码格式,近年来逐渐进入大众视野,其背后承载着诸多优势,但同时也伴随着不容忽视的局限性,首要挑战在于其兼容性边界,尽管WebM已广泛适应于众多网站与软件平台,但在特定应用环境或老旧设备上,其兼容难题依旧凸显,为用户体验带来不便,再者,WebM格式的非普适性也体现在编辑流程上,由于它并非行业内的通用标准,编辑过程中可能会遭遇格式不兼容的障碍,导致操

科研绘图系列:R语言扩展物种堆积图(Extended Stacked Barplot)

介绍 R语言的扩展物种堆积图是一种数据可视化工具,它不仅展示了物种的堆积结果,还整合了不同样本分组之间的差异性分析结果。这种图形表示方法能够直观地比较不同物种在各个分组中的显著性差异,为研究者提供了一种有效的数据解读方式。 加载R包 knitr::opts_chunk$set(warning = F, message = F)library(tidyverse)library(phyl

透彻!驯服大型语言模型(LLMs)的五种方法,及具体方法选择思路

引言 随着时间的发展,大型语言模型不再停留在演示阶段而是逐步面向生产系统的应用,随着人们期望的不断增加,目标也发生了巨大的变化。在短短的几个月的时间里,人们对大模型的认识已经从对其zero-shot能力感到惊讶,转变为考虑改进模型质量、提高模型可用性。 「大语言模型(LLMs)其实就是利用高容量的模型架构(例如Transformer)对海量的、多种多样的数据分布进行建模得到,它包含了大量的先验

【北交大信息所AI-Max2】使用方法

BJTU信息所集群AI_MAX2使用方法 使用的前提是预约到相应的算力卡,拥有登录权限的账号密码,一般为导师组共用一个。 有浏览器、ssh工具就可以。 1.新建集群Terminal 浏览器登陆10.126.62.75 (如果是1集群把75改成66) 交互式开发 执行器选Terminal 密码随便设一个(需记住) 工作空间:私有数据、全部文件 加速器选GeForce_RTX_2080_Ti

荣耀嵌入式面试题及参考答案

在项目中是否有使用过实时操作系统? 在我参与的项目中,有使用过实时操作系统。实时操作系统(RTOS)在对时间要求严格的应用场景中具有重要作用。我曾参与的一个工业自动化控制项目就采用了实时操作系统。在这个项目中,需要对多个传感器的数据进行实时采集和处理,并根据采集到的数据及时控制执行机构的动作。实时操作系统能够提供确定性的响应时间,确保关键任务在规定的时间内完成。 使用实时操作系统的