真刀真枪模块化25君子协定

在本系列的前一篇文章《真刀真枪模块化(2)——图解Service模型》中,我们介绍了一种模块化封装的模型——Service模型。该模型的设计理念实际上服务于一个叫做“黑盒子哲学”的设计思维,其核心思想是:

将模块视作一个黑盒子:模块的设计者不用向外透露黑盒子的实现细节;同时模块的使用者也无法看到黑盒子的内部。

模块的设计者和模块的使用者完全通过“接口”来进行约定和沟通。这里所有的接口约定都是通过接口头文件来进行描述和传递的。

接口(及接口头文件)遵循“最小信息公开原则”,即,任何跟使用模块所提供的服务无关的、或者非必要的(可有可无)信息都应该从接口头文件中删除。

实践中,要想实现黑盒子,我们实际上要完成两大任务:

如何隐藏模块的实现,或者说隐藏源代码;

接口头文件中数据结构的保护,或者说如何阻止用户绕开模块所提供的API而直接访问关键结构体的内部(私有)成员;

对于第一条来说,我们只需要把模块编译成library,连同接口头文件一起提供给客户使用就可以做到;而对于第二条要想实现起来却并非那么简单——虽然我们常常说C语言可以通过结构体来模拟类的概念,但它却无法像C++的类那样提供对私有(private)和受保护(protected)成员的隐藏。换句话说,在实践“最小信息公开原则”的时候,如果用户调用服务的时候,确实需要用到结构体(这个结构体是最小信息),如何防止结构体的定义信息被“非法使用”,就成了一个切实的难题。

为了让后续的讨论更为清晰,我们不妨具体的定义一下我们的任务:

只允许用户使用结构体的大小和对齐信息——这样用户可以自由的定义变量,或是通过malloc这样的函数进行动态分配;以某种“通过实际手段强制了的君子协定”的形式——仅在语法层面——阻止用户直接访问结构体的成员。要想同时做到以上两点,离不开今天索要介绍的主角:掩码结构体(MaskedStructure)。要想理解掩码结构体,抛开复杂和抽象的文字描述,我们不妨来看一个具体的例子:假设我们做了一个字节队列的模块,其中最核心的结构体byte_queue_t的定义如下:

typedefstructbyte_queue_tbyte_queue_t;structbyte_queue_t{uint8_t*pchBuffer;uint16_thwSize;uint16_thwHead;uint16_thwTail;uint16_thwCount;};针对这一结构体(或者叫类)我们提供一系列API(或者叫类的方法),比如:

typedefstructbyte_queue_cfg_t{uint8_t*pchBuffer;uint16_thwSize;}byte_queue_cfg_t;externbyte_queue_t*byte_queue_init(byte_queue_t*ptObj,byte_queue_cfg_t*ptCFG);externboolbyte_queue_enqueue(byte_queue_t*ptObj,uint8_tchByte);externboolbyte_queue_dequeue(byte_queue_t*ptObj,uint8_t*pchByte);externuint_fast16_tbyte_queue_count(byte_queue_t*ptObj);为了保证模块的正常工作,防止运行期间,用户为了自身的便利,直接”外科手术式的“访问byte_queue_t的成员导致不必要的问题(比如用户说:我知道你遵循的是最小信息公开原则,也就是说,只要你放了结构体在接口头文件里,我当然理解为我可以任意使用咯?),我们想将整个byte_queue_t都保护起来——这就好比,我们试图引入一个“蒙版”,遮住结构体的成员信息然后在客户的耳边念起魔咒:你什么都看不到,你看到了也没法用……你什么都看不到,你看到了也没法用……你什么都看不到,你看到了也没法用……...要想实现这样的“蒙版效果”其实并不困难,只需要知道要屏蔽的部分实际占用memory的大小,再根据这一大小来定义数组即可,因此,我们可以修改对应的定义为:

typedefstructbyte_queue_tbyte_queue_t;struct__byte_queue_t{uint8_t*pchBuffer;uint16_thwSize;uint16_thwHead;uint16_thwTail;uint16_thwCount;};structbyte_queue_t{uint8_tchMask[sizeof(struct__byte_queue_t)];};

这里,我们实际上是给原来的类型重命名为__byte_queue_t,并建立了一个内部只使用数组来“滥竽充数”的替身——也就是我们所说的掩码结构体。

如果你看过我之前的文章《漫谈C变量——对齐(3)》,你会注意到,上述替身实际上丢失了结构体__byte_queue_t的对齐信息——容易注意到struct__byte_queue_t的结构体整体是对齐到4字节的,而掩码结构体中数组chMask本身是对齐到字节的——这会导致当用户使用掩码结构体来定义变量时,由编译器分配的空间可能无法满足原结构体对对齐的要求,造成非对齐访问——轻则性能下降,重则hardfault。要解决这一问题也并不复杂,只需要借助GCC扩展的运算符__alignof__()提取目标类型的对齐信息,再使用__attribute__((aligned()))来设置掩码数组的对齐要求就可以了:

typedefstructbyte_queue_tbyte_queue_t;struct__byte_queue_t{uint8_t*pchBuffer;uint16_thwSize;uint16_thwHead;uint16_thwTail;uint16_thwCount;};structbyte_queue_t{uint8_tchMask[sizeof(struct__byte_queue_t)]__attribute__((aligned(__alignof__(struct__byte_queue_t))));};

至此,掩码结构体byte_queue_t拥有了和原本的结构体struct__byte_queue_t一样的尺寸和对齐;同时还在“语法”层面阻止了用户直接访问结构体成员的可能(当然,这也只能防君子不防小人),我们原本设立的两个目标都已成功达成。然而,聪明的你会在脑海里浮现出一个疑问——要想掩码结构体能正常工作,上述信息都必须放置到接口头文件中,难道用户是傻子,看不到结构体__byte_queue_t么?

借助宏的力量,我们可以成功的隐藏住struct__byte_queue_t的存在。下面的宏只是为了演示一种简单的实现方法,暂时的打消你的疑虑,而实际在后面我们将要介绍的PLOOC模板中所使用的技法则更为复杂。由于本文只是着重于实际工程实践中如何简单的应用掩码结构体,而不在于介绍复杂的宏技巧,因此我们将不在讨论PLOOC的实现细节。

#definedeclare_class(__name)\typedef__name__name;#definedef_class(__name,...)\struct__##__name{\__VA_ARGS__\};\struct__name{\uint8_tchMask[sizeof(struct__##__name)]\__attribute__((aligned(\__alignof__(struct__##__name)\)));\};/*这只是一个为未来预留的语法糖*/#defineend_def_class(...)#defineclass_internal(__obj_ptr,__ptr,__type)\struct__##__type*__ptr=\(struct__##__type*)(__obj_ptr)

借助上述宏,我们可以将接口头文件byte_queue.h中代码简化为:

...declare_class(byte_queue_t)def_class(byte_queue_t,uint8_t*pchBuffer;uint16_thwSize;uint16_thwHead;uint16_thwTail;uint16_thwCount;)end_def_class(byte_queue_t)...

而模块源代码中,则可以使用class_internal()来获取原本的结构体类型:

...#include"./byte_queue.h"...#undefthis#definethis(*ptThis)boolbyte_queue_enqueue(byte_queue_t*ptObj,uint8_tchByte){/*initialise"this"(i.e.ptThis)toaccessclassmembers*/class_internal(ptObj,ptThis,byte_queue_t);...if((this.hwHead==this.hwTail)(0!=this.hwCount)){//!queueisfullreturnfalse;}...}PLOOC是ProtectedLow-overheadObject-OrientedprogrammingwithANSI-C的英文缩写,意为:为(类)提供保护的、低开销的、面向对象C语言开发。它是我在Github上的一个开源项目(



转载请注明地址:http://www.sanbaicaoasb.com/sctx/6886.html
  • 上一篇文章:
  • 下一篇文章:
  • 热点文章

    • 没有热点文章

    推荐文章

    • 没有推荐文章