构建可扩展的应用(一)
Chris Sells 翻译:Jackal L. Faven
译注:已对原文进行了部分删节
很多开发者都钟情于组件化的设计和开发思想。在这种思想的指引下,我们可以很方便的向业已存在的应用中添加新的组件以得到新的功能。他们都期望着使用组件化编程来构建可在运行期扩展的应用。然而,设计阶段的组件和最终用户所使用的组件还是有很大不同的。
开发者可以使用相当多的现成工具来利用ActiveX控件开发应用软件或是网页:比如MFC, VC, VB, Java, VBScript, IE3等等。不过很不幸的是,这些工具都不能反过来帮助其他的同类工具使用你所开发出来的应用软件。这样的话,即使用户得到了你的应用的源代码,也只有很少一部分人拥有足够的资金去得到一份Mircorsoft Developer Studio,并将他所刚买的一个组件嵌入你的应用之中。为了提供可扩展性,你需要将自己的应用程序设计的像小钉板,提供孔洞以便让使用者能够钉上他们任何想要钉上的小钉。当然,我们还必须给出这块钉板的小孔的规格,以使钉板的使用者可以选择恰当的钉子。
在Windows平台下,你有两种可供选择的流行方法来提供这种可扩展的能力,就是使用动态连接库(DLL, Dynamic Link Libraries)或使用COM。首先,让我们来回顾一下所谓的经典方法(即使用动态连接库),并看看其中蕴藏着那些弊端。然后,再让我们仔细的研究一下应用COM的方法,再我们研读完一个例子之后,你就有足够的能力来构建你自己的可扩展应用了。
你需要组建管理器(Component Category Manager)、包含于最新版的Win32 SDK之中的comcat.h、VC++ 4.2以及ActiveX SDK来编译本文所使用的例子。
(所有程序均已在Windows 95 OSR2和Windows NT 4.0下编译测试通过。)
经典的方法
你可能已经对这种经典的方法很熟悉了:几乎与所有的Windows程序员都不可避免的要和DLL打交道。一个DLL可以诸如提供封装,模块化以及其他一些面向对象的功能优势,同时还可以在任何时候,被需要它的应用进程载入内存。当一个DLL被编译近一个应用之后,它就会将自己的所有接口显示的暴露出来。正如你所料到的,在Windows下的存在这样一个很普遍的事实,应用软件一些的功能被搁置在了其之外的DLL中。
这种扩展方式的经典例子便是很好使但已过时的拼写检查。它可以在你的商品化的应用中被广泛的应用,当有更好的组件出来时,你还可以替换它,以此来增加软件分发商的在价格战中的竞争实力。
这就是所有的好处,我们来看一个独立的例子:姓名检查器。
NameChecker是一个可扩展的软件,它可以允许用户输入一系列的姓名,并通过用户指定的检查引擎来检查它们。NameChecker知道该如何找到一个可供利用的检查引擎,显示出一系列的描述姓名,同时记住该文档与检查引擎的关联情况。
你可能希望将Mac Wizards和Unix gurus分开,那么就请分别使用合适的引擎。这种设计的关键在于每种检查引擎都必须就是建立在相同的、标准化的接口之上。
使用经典的DLL解决方案,那么接口看起来应该像是这个样子:
//NameCheckEngine.h
BOOL WINAPI CheckName(LPCSTR szName);
void WINAPI SuggestName(LPCSTR szName, LPSTR szSuggestion);
任何暴露出这样的全局功能的DLL都可以以如下这种方式载入并调用:
typedef BOOL (WINAPI *PFNCHECKNAME)(LPCSTR);
typedef void (WINAPI *PFNSUGGESTNAME)(LPCSTR, LPSTR);
void CheckNames(const char* pszEngine, char* rgszNames, int nNames)
{
HINSTANCE hinst = LoadLibrary(pszEngine);
if( hinst )
{
PFNCHECKNAME
pfnCheckName = (PFNCHECKNAME)
GetProcAddress(hinst, "CheckName");
PFNSUGGESTNAME
pfnSuggestName = (PFNSUGGESTNAME)
GetProcAddress(hinst, "SuggestName");
//重新遍历姓名表
//调用pfnCheckName()和pfnSuggestName()
FreeLibrary(hinst);
}
}
LoadLibrary()和GetProcAddress()正是这种方法的核心所在。许多Windows程序都以这种方式工作——比如Microsoft Office的导入导出引擎,Adobe Photoshop和图像过滤,等等。Windows的外壳(shell)也应用这种方式来加载控制面板的小程序。更甚于操作系统本身也使用这一种方式来装载设备驱动程序。每一个这种可扩展程序都有其独立的API,并且独立的寻找已“注册”的组件。这些都可以包含在一个注册项数据库中,但也同时意味着一需要把这些DLL放在正确的目录下,并保证正确的命名规则。
从理论上讲,这种设计没有任何问题。当一个组建被恰当的加入后,用户就可以得到一个新的功能。但实际上,会有很多问题暴露出来,其中有:
大量的GetProcAddress()代码
版本混乱(过后请看看你自己的系统)
脱离了面向对象编码方式,而使用的是模块化的API编程
组建随着应用一起被不正确的注册进了系统
必须用特定的语言来实现编程接口(通常是C或C++)
必须用DLL来实现这一编程接口(试想一下在网络上它还可行吗?)
唉,有了如此多的问题,别担心,我们可以庆幸的是我们还有COM方法。
译注:我将原文的APPLICATION翻译成了应用,而非软件。