lianzhong008 发表于 2015-7-23 21:07:16

C语言封装技术

        最近在研究RT_Thread的源码,发现i2c驱动里有个地方使用的封装的技术,很少见,又感觉很是精妙。所以特意写个测试模拟程序,并贴上来和大家分享。
个人觉得它非常经典。它使用的模拟的i2c。代码如下:
#include "stdafx.h"

typedef signed int rt_int32_t;
typedef unsigned int rt_uint32_t;

struct rt_i2c_bit_ops
{
    void *data;            /* private data for lowlevel routines */
    void (*set_sda)(void *data, rt_int32_t state);
    void (*set_scl)(void *data, rt_int32_t state);
    rt_int32_t (*get_sda)(void *data);
    rt_int32_t (*get_scl)(void *data);

    void (*udelay)(rt_uint32_t us);

    rt_uint32_t delay_us;/* scl and sda line delay */
    rt_uint32_t timeout;   /* in tick */
};

//结构体功能函数的使用 它的精妙之处在这里,用宏把结构体函数使用起来
#define SET_SDA(ops, val)   ops->set_sda(ops->data, val)
#define SET_SCL(ops, val)   ops->set_scl(ops->data, val)
#define GET_SDA(ops)      ops->get_sda(ops->data)
#define GET_SCL(ops)      ops->get_scl(ops->data)
//结构体功能函数的使用
#define SDA_L(ops)          SET_SDA(ops, 0)
#define SDA_H(ops)          SET_SDA(ops, 1)
#define SCL_L(ops)          SET_SCL(ops, 0)

//提供对外使用
inline void i2c_delay(struct rt_i2c_bit_ops *ops)
{
    ops->udelay((ops->delay_us + 1) >> 1);
}

inline void i2c_delay2(struct rt_i2c_bit_ops *ops)
{
    ops->udelay(ops->delay_us);
}
//以下为rt_i2c_bit_ops指针函数
void i2c_set_sda(void *data, rt_int32_t state)
{
        if(state)
        {
                printf("sda->H\r\n");
        }else
        {
                printf("sda->L\r\n");
        }
}
void i2c_set_scl(void *data, rt_int32_t state)
{
        if(state)
        {
                printf("scl->H\r\n");
        }else
        {
                printf("scl->L\r\n");
        }
}

rt_int32_t i2c_get_sda(void *data)
{
        printf("sda return 1\r\n");
        return 1;
}
rt_int32_t i2c_get_scl(void *data)
{
        printf("scl return 1\r\n");
        return 1;
}
void udelay(rt_uint32_t us)
{
        printf("udelay %d us\r\n",us);
}
//以上为rt_i2c_bit_ops指针函数


//模拟i2c的其他信号可以类似i2c_restart一样调用
static void i2c_restart(struct rt_i2c_bit_ops *ops)
{
    SDA_H(ops);
    i2c_delay(ops);
    SDA_L(ops);
    i2c_delay(ops);
    SCL_L(ops);
}

int main(int argc, char* argv[])
{
        rt_i2c_bit_ops i2c_ops;
      i2c_ops.data = (void *)0x88;
        i2c_ops.delay_us = 5;
        i2c_ops.udelay = udelay;
        i2c_ops.get_scl = i2c_get_scl;
        i2c_ops.get_sda = i2c_get_sda;
        i2c_ops.set_scl = i2c_set_scl;
       i2c_ops.set_sda = i2c_set_sda;


        printf("==========TEST===========!\n");

      以上设置完专门的i2c接口后,类似与i2c_restart这样的i2c发送和接收的业务逻辑函数基本上不用修改了,直接按不同的i2c接口传参即可完成i2c的功能。
        i2c_restart(&i2c_ops);
        return 0;
}

upli 发表于 2015-7-23 21:20:39

函数指针使用的巧妙,高

bbglx 发表于 2015-7-23 21:48:27

顶顶,待会研究研究

CHEN1986 发表于 2015-7-23 23:03:17

语法太高深啦,不是太看得明白.

机器人天空 发表于 2015-7-23 23:19:31

不错,收藏了

qhshilin 发表于 2015-7-23 23:23:33

谢谢了帮助很大 我的项目刚好可以用上 及时雨

xurenhui 发表于 2015-7-23 23:28:10

谢谢你的分享,写得精妙,

mainbp 发表于 2015-7-23 23:34:51

太晚了,明天研究

wayne11235 发表于 2015-7-24 10:00:30

ooc思想啊,值得学习。

Okar 发表于 2015-7-24 10:19:20

超核K60库的作者yandld就是使用了这种方法封装SPI设备啊{:lol:}

dxzky 发表于 2015-7-24 10:24:46

用指针和结构体,感觉确实要巧妙很多~

32MCU 发表于 2015-7-24 10:37:17

收藏!学习。

cdh 发表于 2015-7-24 10:56:50

高,实在是高。
freeMODBUS里也有类似的做法。

newcanking 发表于 2015-7-24 11:00:56

不错,下来学习一下,

jinchenaquarius 发表于 2015-7-24 11:16:01

正在学习,帮顶

yaoyutaoTom 发表于 2015-7-24 11:17:04

我也上一段吧!
struct PolyFunc
{
void (*oneParam)( struct PolyFunc *, int);
int (*withReturn)( struct PolyFunc *);
};

void someFunc( struct PolyFunc * pf )
{
int r = (pf->withReturn)( pf );
(pf->oneParam)( pf, r );
}

struct PolyBase
{
struct PolyFunc interfacePolyFunc;
int value;
};

void PolyBase_oneParam( struct PolyFunc * pf, int a );
int PolyBase_withReturn( struct PolyFunc * pf );

void init_PolyBase( struct PolyBase * pb )
{
pb->interfacePolyFunc.oneParam = &PolyBase_oneParam;
pb->interfacePolyFunc.withReturn = &PolyBase_withReturn;
}

struct PolyBase pb;
init_PolyBase( &pb );
pb.value= 123;
someFunc( &pb.interfacePolyFunc );

int PolyBase_withReturn( struct PolyFunc * pf )
{
struct PolyBase * this = (struct PolyBase*)pf;
return this->value;
}

struct PolyDerived
{
struct PolyBase base_PolyBase;
char prefix;
};

void PolyDerived_oneParam( struct PolyFunc * pf, int a );

void init_PolyDerived( struct PolyDerived * pd )
{
init_PolyBase( (struct PolyBase*)pd );
pd->base_PolyBase.interfacePolyFunc.oneParam = &PolyDerived_oneParam;

strcpy( pd->prefix, "ABC:" );
}

struct PolyDerived pd;
init_PolyDerived( &pd );
pd.base_PolyBase.value= 123;
someFunc( (struct PolyFunc*)&pd );

// Virtual Table

struct PolyBase
{
struct PolyFunc * interfacePolyFunc;
int value;
};

struct PolyFunc PolyBase_VTable =
{
&PolyBase_oneParam,
&PolyBase_withReturn
};

void init_PolyBase( struct PolyBase * pb )
{
pb->interfacePolyFunc = &PolyBase_VTable;
}

void someFunc( struct PolyFunc * * pf )
{
int r = ((*pf)->withReturn)( pf );
((*pf)->oneParam)( pf, r );
}

void calling()
{
struct PolyBase pb;
init_PolyBase( &pb );
someFunc( &pb.interfacePolyFunc );
}


lianzhong008 发表于 2015-7-25 07:13:21

yaoyutaoTom 发表于 2015-7-24 11:17
我也上一段吧!
struct PolyFunc
{


        yaoyutaoTom,这段代码展现了c语言的封装、继承、多态还利用上了虚函数表技术。对于学习c语言的抽象设计,这段代码可以起到启发的作用。稍微加下注解
// struct01.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <string.h>

#if 0
//接口封装
struct PolyFunc
{
void (*oneParam)( struct PolyFunc *, int);
int (*withReturn)( struct PolyFunc *);
};
//使用demo
void someFunc( struct PolyFunc * pf )
{
int r = (pf->withReturn)( pf );
(pf->oneParam)( pf, r );
}
//接口 + 属性 数据
struct PolyBase
{
struct PolyFunc interfacePolyFunc;
int value;
};

void PolyBase_oneParam( struct PolyFunc * pf, int a );
int PolyBase_withReturn( struct PolyFunc * pf );

//初始化接口
void init_PolyBase( struct PolyBase * pb )
{
pb->interfacePolyFunc.oneParam = &PolyBase_oneParam;
pb->interfacePolyFunc.withReturn = &PolyBase_withReturn;
}

//接口实现
int PolyBase_withReturn( struct PolyFunc * pf )
{
struct PolyBase *PolyBaseS= (struct PolyBase*)pf;
printf("Get Value\r\n");
return PolyBaseS->value;
}
//接口实现
void PolyBase_oneParam( struct PolyFunc * pf, int a)
{
struct PolyBase *PolyBaseS= (struct PolyBase*)pf;
printf("Set Value\r\n");
PolyBaseS->value = 5;
}

//继承PolyBase
struct PolyDerived
{
struct PolyBase base_PolyBase;
char prefix;
};

//多态 重写PolyDerived PolyDerived_oneParam
void PolyDerived_oneParam( struct PolyFunc * pf, int a)
{
struct PolyBase *PolyBaseS= (struct PolyBase*)pf;
printf("Derived Set Value\r\n");
PolyBaseS->value = 5;
}
//初始化PolyDerived
void init_PolyDerived( struct PolyDerived * pd )
{
init_PolyBase( (struct PolyBase*)pd );
pd->base_PolyBase.interfacePolyFunc.oneParam = &PolyDerived_oneParam;

strcpy( pd->prefix, "ABC:" );
}

//测试demo
int main(int argc, char* argv[])
{       
        //封装
        {
                struct PolyBase pb;
                init_PolyBase( &pb );
                pb.value= 123;
                someFunc( &pb.interfacePolyFunc );
        }
        //继承
        {
                struct PolyDerived pd;
                init_PolyDerived( &pd );
                pd.base_PolyBase.value= 123;
                someFunc( (struct PolyFunc*)&pd );
        }

        return 0;
}
#endif



// Virtual Table //虚函数表技术
struct PolyBase
{
struct PolyFunc * interfacePolyFunc;
int value;
};
//对外提供接口
struct PolyFunc
{
void (*oneParam)( struct PolyFunc **, int);
int (*withReturn)( struct PolyFunc **);
};
//实现接口
int PolyBase_withReturn( struct PolyFunc ** pf )
{
struct PolyBase *PolyBaseS= (struct PolyBase*)(*pf);
printf("Get Value\r\n");
return PolyBaseS->value;
}
//实现接口
void PolyBase_oneParam( struct PolyFunc ** pf, int a)
{
struct PolyBase *PolyBaseS= (struct PolyBase*)(*pf);
printf("Set Value\r\n");
PolyBaseS->value = 5;
}

//初始化虚拟函数表
struct PolyFunc PolyBase_VTable =
{
&PolyBase_oneParam,
&PolyBase_withReturn
};
//初始化接口
void init_PolyBase( struct PolyBase * pb )
{
pb->interfacePolyFunc = &PolyBase_VTable;
}

//demo 调用
void someFunc( struct PolyFunc * * pf )
{
int r = ((*pf)->withReturn)( pf );
((*pf)->oneParam)( pf, r );
}

//test demo
void calling()
{
struct PolyBase pb;
init_PolyBase( &pb );
someFunc( &pb.interfacePolyFunc );
}

int main()
{
        calling();
        return 1;
}


chliken 发表于 2015-7-25 08:04:07

设计的相当精妙,学习了

deadline2012 发表于 2015-7-25 08:04:09

学习了,谢谢分享

技术宅 发表于 2015-7-25 09:02:12

一开始没理解这里#define SET_SDA(ops, val)   ops->set_sda(ops->data, val),   后来查了一下,了解到宏定义函数是没有类型的, 任何参数类型都可以。所以传数据的时候一定要注意参数类型。   学习了。

lianzhong008 发表于 2015-7-25 09:27:47

技术宅 发表于 2015-7-25 09:02
一开始没理解这里#define SET_SDA(ops, val)   ops->set_sda(ops->data, val),   后来查了一下,了解到 ...

        是的,感觉这里用的非常的巧妙,就贴出来和大家分享,看看大伙的意见,一来可以加深自己的理解和印象,二来可以收获不同坛友的不同角度的看法。

Mingrui 发表于 2015-7-25 11:47:41

是的,要好好研究一下!

Ray______ 发表于 2015-7-25 13:11:07

咬着牙啃完了,不容易吸收啊

yikuang 发表于 2015-7-25 13:54:09

不错,谢谢分享!

男小北 发表于 2015-7-25 14:05:54

学习了      

amigenius 发表于 2015-7-25 14:11:09

这是常识,做过大规模代码的都会这么干。

lianzhong008 发表于 2015-7-25 14:13:48

amigenius 发表于 2015-7-25 14:11
这是常识,做过大规模代码的都会这么干。

        看来这位朋友的代码阅历比较丰富,我要不是碰到了RT_Thread的,还真的没有机会体会到这种感觉。

wkman 发表于 2015-7-25 15:02:04

这常识,,,搞mcu的多是 独行侠,,,,很少用上啊{:lol:}学习了{:victory:}

jathenal 发表于 2015-7-25 16:45:56

这有点OO中多态的意思,驱动代码体现了“要做什么”,至于“如何做”,交给指针函数完成

lianzhong008 发表于 2015-7-25 17:26:55

jathenal 发表于 2015-7-25 16:45
这有点OO中多态的意思,驱动代码体现了“要做什么”,至于“如何做”,交给指针函数完成 ...

        多态只是其中的一部分,我比较欣赏的是这里宏对结构体函数的调用封装,这部分业务逻辑基本上不用再改。不同的i2c接口可以通用调用。
//模拟i2c的其他信号可以类似i2c_restart一样调用
static void i2c_restart(struct rt_i2c_bit_ops *ops)
{
    SDA_H(ops);
    i2c_delay(ops);
    SDA_L(ops);
    i2c_delay(ops);
    SCL_L(ops);
}

liuzhijun2008 发表于 2015-7-25 21:51:21

学习了不错

ganjinglory 发表于 2015-7-26 19:13:57

好东西。

nhztb 发表于 2015-7-28 16:57:16

还没有领会到,具体的好处,哪位高手普及一下?

cat_li 发表于 2015-7-28 17:10:26

没看懂,还需要努力啊

zjlizy3344 发表于 2015-7-28 23:21:47

说白了就是函数指针的妙用,UCGUI中大量使用

abcdzhy 发表于 2015-7-29 08:13:23

这种封装方式好,以后再开发就方便了,顺便问下楼主有没有关于C语言封装的书呢?

lianzhong008 发表于 2015-7-29 11:40:41

abcdzhy 发表于 2015-7-29 08:13
这种封装方式好,以后再开发就方便了,顺便问下楼主有没有关于C语言封装的书呢? ...

        有一本<c嵌入式编程设计模式>,但是像我发的帖子这样的封装技术,是在国人开发RT_Thread嵌入式操作系统上学来的。

coslight_dt 发表于 2015-7-29 16:08:27

TMD,没看明白,太笨了,还是去焊板子了

jiang887786 发表于 2015-7-29 16:29:18

道行潜!实在是看不懂!境界不够!ops->set_sda(ops->data, val),这里的ops在哪?

374184600 发表于 2015-7-29 16:31:41

lianzhong008 发表于 2015-7-25 07:13
yaoyutaoTom,这段代码展现了c语言的封装、继承、多态还利用上了虚函数表技术。对于学习c语言的抽象设计 ...

醍醐灌顶啊!
真是好东西。

retome 发表于 2015-7-29 16:38:42

看看 学习了 多谢

tianzhiying 发表于 2015-7-29 16:41:39

刚好最近需要iic,顶一下,拿来试试

mthgh0818 发表于 2015-7-29 17:14:22

rt-thread借鉴了c++的封装继承多态,在开发说明里有写的。上面这种写法在大的项目里用的比较多,要是全是自己写就用不上了。我去年就看过这种方法了,到现在都没用上

istars2005 发表于 2015-7-29 17:37:23

不错,这几天正在看java,非常羡慕java里面的封装方式,一直想在C里面实现,看来还真可以模拟实现

Elec_Ramble 发表于 2015-8-1 09:59:36

尤其是在多人开发项目中很有用。

waymcu 发表于 2015-8-11 09:07:17

没有怎么看明白怎么把CLK   SDLIO口与程序怎么好应起来 。

jzkn 发表于 2015-8-11 09:24:35

waymcu 发表于 2015-8-11 09:07
没有怎么看明白怎么把CLK   SDLIO口与程序怎么好应起来 。

IO驱动在有段打印信息那里完善,这个方式的好处就是I2C的时序是固定的,也就是逻辑部分不用改,移植时把IO驱动改了就完了。
这个例子的意义在于提供一种思路,自己做I2C时用不着这么麻烦。

yick 发表于 2015-8-11 09:25:58

这样的代码很方便,看起也很漂亮,不过我暂时是没精力搞这些了,只能是留待以后了。

waymcu 发表于 2015-8-11 09:34:30

jzkn 发表于 2015-8-11 09:24
IO驱动在有段打印信息那里完善,这个方式的好处就是I2C的时序是固定的,也就是逻辑部分不用改,移植时把I ...

IO好像与void *data 这个地址 对应起来了?

jzkn 发表于 2015-8-11 13:25:43

waymcu 发表于 2015-8-11 09:34
IO好像与void *data 这个地址 对应起来了?

差不多就这意思吧,他也没写死,Data还可以做其他用途 ,看你实现得时候得需求了。

william_rain 发表于 2015-8-11 16:24:20

学习一下

孤独的凯 发表于 2015-9-1 13:47:58

mark一下

yulongkui 发表于 2015-9-1 21:33:48

回来在程序里试一下

Yvan 发表于 2016-6-8 10:59:18

值得借鉴!!

ckhf 发表于 2016-6-12 16:32:03

用过,不过不多,换不同MCU时就直接可以移植,再调节IO就可以了

奋斗的小傲 发表于 2016-6-12 19:11:29

果然还是见识太短

myin4 发表于 2016-6-12 19:34:54

好东西,mark

YS126 发表于 2016-6-12 21:38:49

封装的好漂亮~~~

mvpgpz 发表于 2016-6-13 13:40:14

帅啊,真是不错。谢谢楼主

zhuozz 发表于 2016-6-30 11:22:05

最近也在开始看RTT

niba 发表于 2016-12-19 15:18:07

Okar 发表于 2015-7-24 10:19
超核K60库的作者yandld就是使用了这种方法封装SPI设备啊

贴出来 大家学习下呀

Okar 发表于 2017-1-1 08:18:59

niba 发表于 2016-12-19 15:18
贴出来 大家学习下呀

感兴趣的肯定自己会去搜了,网上大片的资料。

darkness27 发表于 2017-1-1 09:55:55

是不是STM32的驱动库也是类似的包装。之前用那些库函数都直接调用,没仔细研究。

zhuzi1441 发表于 2017-1-1 11:05:16

不错,收藏了

stdio 发表于 2017-1-1 13:21:58

linux内核驱动就是这样的。

eliterxzgxu 发表于 2017-1-1 15:15:26

感谢楼主分享

ITOP 发表于 2017-1-1 22:16:42

标记慢慢看,最近一直在焊板子,很少写代码了!

rain73 发表于 2017-1-1 23:07:04

C本是面向过程的语言,把C当C++来用,不如直接用面向对象的C++。

NFotxb 发表于 2017-1-2 07:47:58

很精妙,学习了。

BrightWang 发表于 2017-1-2 11:55:18

rain73 发表于 2017-1-1 23:07
C本是面向过程的语言,把C当C++来用,不如直接用面向对象的C++。

说的是!

diannaoza 发表于 2017-1-2 13:58:28

rain73 发表于 2017-1-1 23:07
C本是面向过程的语言,把C当C++来用,不如直接用面向对象的C++。{:smile:}

stdio 发表于 2017-1-2 15:49:20

rain73 发表于 2017-1-1 23:07
C本是面向过程的语言,把C当C++来用,不如直接用面向对象的C++。

层主不妨试试用C++写一下楼主位的代码,看看是什么效果。
注意,主楼代码的结构内指针是虚的,即可以指向不同的实现实体。
拭目以待。。

rain73 发表于 2017-1-3 00:03:46

stdio 发表于 2017-1-2 15:49
层主不妨试试用C++写一下楼主位的代码,看看是什么效果。
注意,主楼代码的结构内指针是虚的,即可以指向 ...

有段时间没动代码了,一下子还真写不出。不过要说有什么C能实现而C++不能实现的,那是不太可能!
C++的对象多得实在是太复杂了,相比之下上面几个概念简直是小儿科。

stdio 发表于 2017-1-3 12:29:45

本帖最后由 stdio 于 2017-1-3 14:15 编辑

c++比较复杂。

McuY 发表于 2017-2-22 22:01:01

mcu用io模拟多路有什么简洁的写法?

McuY 发表于 2017-2-22 22:02:28

mcu用io模拟多路i2c有什么简洁的写法?

zhongsandaoren 发表于 2017-5-31 13:23:49

RT中很多组件都是这样做的,把应用层和底层分开来,中间层封装和衔接设备,并注册设备,然后就可以在不管底层的情况下使用应用层了,也便于分开协同开发。

ljt80158015 发表于 2017-5-31 13:39:55

linux驱动都是这么玩的!

cqx99992008 发表于 2017-7-21 11:22:50

涨知识了,要实际练习一下。
页: [1]
查看完整版本: C语言封装技术