- 序言
-
手边的项目不多,但是偶尔有各种日常应用上的小需求,常需要些非常个人化的临时小工具。因此三不五时就会新建个工程来处理相关的任务。
虽然这类小工具非常简陋,也无需考虑太多其他意见(自己就是并且是唯一的用户),写起来倒是没什么问题。不过次数多了,有很多重复性的问题就一直干扰着开发的意念。
次数多了,就开始思索这一现象,并尝试用更简单的方法解决。
这能解决一部分问题,但是不是全部。因为封装意味着固定了接口,也就是一套用途。而重用的环境则很少有完全相同的需求。随着每次关注重点的不同,每次重用都要重新修改封装的代码,如果代码公用的话,又会造成交叉影响。世事难两全啊。
这一问题在应用程序配置文件上有位突出。
每次写小工具,一开始都是用固定的地址或者常数,大致完成后就希望更灵活些,于是读取和保存配置文件的需求总是无可避免。
配置文件的读取,本身是如此的简单。调用GetPrivateProfileString 的一组API即可。如果调用CWinApp:GetProfileString, 还能更简单些。(不才崇尚绿色,注册表之类从不考虑。)
但是就这么一个简单的东西,却有一点点的不完善,结果就造成了大大麻烦。
- 不完善的地方1, 调用参数有点多,类型还需要转换。这不是大问题但是也是个小麻烦。
- 不完善的地方2,C的访问方式,只能是简单的堆叠, 10个参数就要重复写10回,100个就要重复100回。当然这怪不了他,但是这个特性确实是大量重复的噩梦。
在一个中小规模的项目里面,配置参数的数目很容易就能达到几十百来个。 想想看,同样几乎雷同的代码,重复几十遍会是什么感觉。这种重复是非常没有技术含量的,没有任何难度但其实是个极大的风险,敲起来也索然无味。代码重复上50行,难免会看花眼。 据我的经验,在这种高度雷同,代码重复的地方,马失前蹄的概率是很高的。经验丰富的资深程序员,可能不会犯一些技术性的错误,甚至从未犯过高难度的错误。但是他免不了偶尔犯一回错误的时候,最有可能就是在类似这种最基础最基础的地方看走了眼或者敲错了个字。还很难检查。如果项目组有多个人成员,更是个安全隐患。
所以可以理解的是,一个考虑成熟的项目。其配置参数的访问是封装起来的。这是最基本的手段,至少让代码很有条理,易于维护。
封装的层次有很多种。就大多数情况来说,如果当前项目易于使用,则其他项目很难公用。所有项目有容易公用的时候,用起来又不是那么顺手了。
我就是个对代码逻辑很挑剔的人。
这种封装结构非常有通用性,几乎所有的项目都可以来过来就用,而且用起来也很简单。但就如果前面我说的,这种通用性造成了对特定项目来说的不方便。如果配置项比较多,这种方式仍有不够理想的地方-- 他缺乏层次。
也就是所有的配置参数都属于同一个层次。而.ini 配置文件本身是有两个层次的 section 和 key。举4个配置参数的例子来说:窗口位置,用户帐号。 .ini配置文件的层次可以是
[Window]
PosX=100
PosY=200
[Profile]
UserName=Zhangsan
UserAddress=Bei san huan xi lu
可以看到.ini 是有两个层次的,第一层分为 Window 和 Profile,然后再细分成第二层。这种分层的好处是显而易见的。但是到了代码里面,由于通用类库的原因,只能写成:
class CGlobalConfig
{
CConfigItem m_PosX;
CConfigItem m_PosY;
CConfigItem m_UserName;
CConfigItem m_Address;
};
完全丧失了层次结构。
如果将结构设计成
class CGlobalConfig
{
class CSectionWindow
{
CConfigItem m_PosX;
CConfigItem m_PosY;
};
class CSectionProfile
{
CConfigItem m_UserName;
CConfigItem m_Address;
};
}
可以看到,逻辑结构立刻清楚了不少,如果配置参数项增多的话,优势是非常明显的。但是这个结构固化了这个项目需要的结构层次,几乎不可能也适用于其他项目。也就是说,没有任何通用性而言,每个项目都要单独构建,失去了类库的意义。
在遭遇到多次的类库通用性和特定项目的针对性的矛盾后,我意识到单纯依赖类库是无法避免这个矛盾的。于是而开始考虑以一种崭新的方式来处理这个问题......
例如,在文件层上的封装(读取和写入配置),可以简化很多的读写操作,对所有项目都有很好的公用意义。但是这种封住不能解决访问层上的问题,项目在访问配置参数时,仍然不是最简便的。 例如,一个最简单的实现:
CConfApp: 负责文件的封装。
CConfSection: 负责 节的封装。
CConfItem: 负责参数的封装,可以内置常见的类型转换,并配合 CConfApp, CConfSection 自动化读取和写入。
使用时
class CGlobalConfig
{
CConfigItem m_cfgClientWIdth;
CConfigItem m_cfgClientHeight;
CConfigItem m_cfgShowSplashWindow;
};
这可以大大的简化配置的维护。 例如,使用配置参数可以简化成 int n = global.m_cfgClientWidth; BOOL b = global.m_cfgShowSplashWindow;
保存配置参数时也可以简化成 global.m_cfgShowSplashWindow = TRUE;
对大多数的项目来说,这样子的封装应该是足够用了。 除非你对代码逻辑非常挑剔。
最基本的解决办法就是建立出自己独特的类库,相信很多人都有这样子的经验:将自己的代码封装一下,作为成果累积起来。
本来小工具是为了简化工作而写的,但是写小工具本身如果太浪费时间或者太麻烦的话,就适得其反了,有时候就干脆凑活凑活,或者用现成的不是很顺手的工具解决。
-
- 架构的选型
-
从序言里面的例子可以看出,类库的实现方式已经非常实用了。所以一开始我仍然是在这个基础上进行突破。首先想到的当然是模板技术。
利用模板技术,貌似相关的问题都可以解决:既保持通用性,同时还能保持特定的结构。但是这一种方式并未尝试多久就放弃了。 原因很简单,我主要用VC6,而VC6对模板的支持不是很好;而且模板虽然厉害,但并不是所有的项目都用它,单独的引入模板也引入复杂因素,这和我的至简理念是不符的。
接下来尝试的就是宏定义了。我用宏定义和预编译进行了很多的努力,效果也还行,能达到通用性和独立性的要求。但是......
我是个对代码逻辑非常挑剔的人。看到初步结果后我就希望能实现更加简单方便的功能,如代码自动完成提示,等等......
宏定义毕竟是宏定义,VC6也毕竟太老了。尽管在以后的VC版本中能自动分析并提示宏定义中的内容,但是我仍然希望以VC6为目标。
宏定义的时候,用到了一些预编译(cl /P /EP)的东西。效果不错,这让我有了预先完整编译一次的想法。 这就是ConfEngine的基础 -- 编译 配置定义文件长生代码; 然后在编译代码产生二进制应用程序。
到此为止,基本上确定了这一解决方案的大体架构就是: 先写一个配置定义文件,定义特定项目需要的结构层次,参数配置等等信息; 然后将配置定义文件编译成特定项目可用的C++代码。
还没确定的是,用什么方式来实现这一编译过程。
-
- 配置文件应该是什么样子
- 首先应该是文本形式的
- 既要程序读写起来简单,不会引入庞大的处理流程
- XML可以暂不考虑了,尽管他很强。
- 又要人工阅读时简洁清晰,并且易于修改
- 既要程序读写起来简单,不会引入庞大的处理流程
- 其次,他应该是有多个层次的。
- 如 Ini文件就有 Section和Key两层。
- 第三,应该具备默认值
- 第四,应该可以多重设定,并且新的设定覆盖旧的设定。
- 这一目的是为了更加方便的个性化。
- 如应用程序本身定义了一大堆的参数,而客户想要更改一两个。
- 他要做的是添加者一两个参数,而不是更改程序本身定义的参数,这样子如果修改错误,就可以安全的回到原来的程序定义的状态。
- 首先应该是文本形式的
- ini配置文件有哪些问题
- ini配置文件的优点是显而易见的:简单,一般情况足够用。
- 相应的缺点也很明确。
- 因为简单而导致功能不足
- 只有(Section和Key)两个层次,稍微复杂就不够用。
- Section的组织方式过于简陋,必须一次完成。同样的Section无法分布在多个段,因而无法多次完成。
- C访问方式,使用时需要封装。否则每次光是类型转换就够累赘的了。
- 因为简单而导致功能不足
- ConfEngine 实现了哪些特点
- 支持基本的整型、字符型、布尔型的数据类型
- 支持无限多的层次
- 支持默认值
- 支持恢复到默认值。
- 支持注释。
- 代码和配置文件中均有。配置文件具有高可读性。
- 编译后的代码,加入到工程后,在VC6中支持自动提示。
- 支持覆盖,可以不修改原来的设定而直接加入新的设定以覆盖旧的设定。
- 支持多层配置
-
支持全局定义配置和用户定义配置文件。
如果多个配置文件定义的项有重复,按照先后顺序覆盖。
- 这里假定一个复杂而且考虑完善的配置系统,以体现多重定义的覆盖意义。
- 1. 假定应用程序为 sample.exe。
- 2. sample.exe默认从sample.conf读取固定的全局配置项,该配置项应该是安全稳固的。
- 3.sample.exe接着从 raw.conf读取部署性的配置项,如OEM修改的信息,某个Release修改的特定信息。 以便更好的针对某一客户群体,或者某些性能优化。
- 4. sample.exe接着从client.conf读取最终用户修改的个性化喜好信息。
- 5. sample.exe接着从%home%/%user%/sample.conf,读取针对不同登录用户的个性化配置信息。
- 这个假定的系统里,按照先后顺序,安排有多个配置文件。每个后面的配置文件都可以全部重新定义每个配置项,或者仅仅是定义自己关心的一两个配置项。
- 这种做法,简化了修改配置的工作。并且使修改显而易见(如果仅仅是定义自己修改的配置项的话)。
- 并且最重要的,他不会影响到原始的设定,任何时候觉得自己的修改不满意,就可以删除自己的修改,就回到原样了。不会出现把配置文件改得面目全非,无法挽回的地步。
一个应用程序中可以包含多个配置系统(每个配置定义文件生成一个配置系统),每一个配置系统可以保存在多个配置文件中。
可以定义任意多的用户配置文件,(包括全局配置文件)每个配置文件都可以定义全部,或者仅仅是一部分配置项。
- 这里假定一个复杂而且考虑完善的配置系统,以体现多重定义的覆盖意义。
-
关于应用程序配置文件的一点思索
-- ConfEngine(配置文件引擎) 的起源