编写可扩展的应用程序






4.09/5 (8投票s)
通过使用简单的进程内 COM 对象,您可以使应用程序易于扩展,而无需重新编译主应用程序。
引言
有时,你可能只看到树木而忽略了森林。你知道使用 COM 很好,但该如何使用它?如何编写一个使用 COM 的应用程序?如何从IApe
和所有教科书示例过渡到实际应用?本文旨在成为一个简单的 COM 使用指南。在你掌握了基础知识并希望编写一个实际的、可工作的应用程序时,可以尝试一下。通过将关键代码块编写为 COM 对象,然后允许用户在运行时使用组件类别来配置要使用的对象,您可以编写易于扩展的应用程序。我试图使本文与其他“看看如何用 ATL 做这个做那个”的文章有所不同,如果这些文章能解决你急切的问题,那它们就很有价值,但它们对于初级 COM 程序员编写第一个实际应用程序的帮助不大……
您不需要精通所有这些臃肿、复杂、微软的接口就能编写出优秀的 COM 应用程序。您需要思考您想要什么,然后利用 COM 来实现它。
问题所在
需求总是在变化。这是生活中的事实。您即将发布应用程序时,突然需要支持更多新功能。没有这些功能,您的应用程序将寸步难行,因为竞争对手已经超越了您的发布候选版本……不幸的是,这种情况时有发生。无论您的开发流程多么快速,或者应该说是狂热,需求总会在最不合适的时候发生变化。能够在开发后期集成需求变更,似乎是当今的首要需求。幸运的是,COM 可以帮助解决这类问题。您需要在前期付出一些额外的努力,但如果做到了,那是值得的。如果那些最有可能发生变更的关键功能被简单的进程内 COM 服务器提供,那么当业务需求需要时,您就可以轻松地插入不同的功能。此外,由于 COM 接口是不可变的,如果您最初的设计是正确的,那么新功能就可以在不改变应用程序其他部分的情况下添加。这会将变更限制在 COM 接口防火墙后面——这意味着更少的重新测试。如果您使用进程内服务器完成了这一切,您几乎不会遇到性能损失,而且如果您需要,COM 始终可以允许您远程访问应用程序的某些部分。
您甚至可以更进一步,允许您的应用程序用户自行配置他们所需组件——您甚至可以发布详细的接口规范,让其他人为您开发替换组件!
从清晰的设计开始
当然,要获得所有这些好处,您必须从一开始就付出努力。您需要分析您的问题领域,并确定哪些区域最有可能受到需求蔓延的影响。接下来,您需要确定一种清晰的方式,将有风险的代码块从主应用程序中分离出来,并将其呈现为清晰且可扩展的接口。这需要练习。有时,需要大量的练习……我发现很多潜在的组件都来自于您在设计过程中建立的对象模型。寻找那些与应用程序其余部分通过清晰接口进行通信的大块功能。例如,如果您有驱动外部硬件的代码,但又不复杂到需要真正的设备驱动程序,那么这些就是将它们从应用程序中分离出来并将其实现为 COM 对象的绝佳候选。如果您有一个区域,您目前通过抽象基类使用一系列多态对象,那么很可能您可以将每个对象制成一个独立的 COM 对象,并通过它们共同的接口来访问它们。也许您的应用程序支持拼写检查、图像渲染、压缩、加密?也许您知道时间紧迫,在发布日期前无法提供 100% 的功能。将有风险的代码转换为 COM 对象,然后发布第一个版本中可以实现的功能,之后再发布额外的组件!
不要停留在您找到的第一层组件。应用程序常常可以充当连接组件的粘合剂。在我曾经的一个信用卡生产系统中,设备驱动程序是 COM 对象,它们可以呈现代表其功能的接口(编码磁条、压印卡片、在卡片上打印、编码芯片卡)。卡片的类型也是一个 COM 对象(标准磁条信用卡、专有格式磁条、芯片卡等)。卡片和设备都向应用程序呈现固定的接口,但设备可以向卡片呈现它喜欢的任何接口。由卡片来判断应用程序尝试与之匹配的设备是否支持它所需的接口。卡片类型知道如何通过所需的接口制作卡片,它还知道如何找出设备是否支持这些接口,并就功能降级做出明智的决定,例如,磁条卡可以在一台能够编码磁条的机器上制作,或者一台可以打印和编码的机器上制作。芯片卡需要芯片卡功能。应用程序本身并不知道卡片对象和设备对象之间使用的接口。这使我们能够在最后阶段添加需要新类型机器和新接口的新类型卡片,而无需更改应用程序……您是否突然需要制作一种照片卡,在您从未见过的新机器上打印?没问题:为新机器编写设备驱动程序,为新的照片打印功能开发新接口,编写一个知道如何通过新接口打印照片的卡片,然后将整个东西插入到应用程序的其余部分,而无需更改任何内容……
让您的对象与其他对象进行通信,让应用程序负责介绍它们,然后就退出吧!
使用组件类别
编写组件固然很好,但当您将所有内容紧密地绑定在一起,以至于您就好像在编译时永久链接了这些组件一样,您就失去了许多基于组件开发的好处。切勿使用硬编码的 GUID 来绑定组件。使用更灵活的方法(即使只是从注册表键中读取 GUID…)。更好的是,使用组件类别。让用户从实现您的应用程序所需类别的任何对象中进行选择。通过使用标准 COM 类别管理器接口的简单包装器,您可以编写代码,轻松列出所需类别中的所有对象。将它们放入列表框,让用户决定,并将决定存储在注册表中。当您提供改进的功能时,只需发送一个新对象,一旦注册,它就会出现在列表中,并允许轻松升级功能。
持久化配置
用户可配置的复杂对象应该能够自行持久化,这样用户就不必一遍又一遍地重复其配置。您不必深入研究文档,试图弄清楚使用哪个IPersist
接口!只需自己创建一个即可,一个适用于您的应用程序和情况的接口。您可以让对象直接持久化到注册表,但这可能会引起问题,如果应用程序突然决定允许用户配置该对象的两个实例。为了使对象尽可能灵活,最好持久化到简单的东西,例如一个返回给应用程序的字节缓冲区,但该缓冲区采用仅对象能理解的格式是相当安全的。对象可以随时将自身保存和恢复到缓冲区,应用程序可以随心所欲地将字节持久化到任何地方。归根结底,IPersistStream
是一个相当不错的选择。它易于从组件和应用程序两端使用,并且它赋予了应用程序更改数据流存储位置的能力,而不会影响组件如何写入其配置。
为您的组件构建框架
COM 对程序员来说很棒,它很复杂,需要很长时间才能学会,而且总是在变化——这意味着我们能从中获得高薪。愿这种情况继续下去,但有时您必须采取务实的态度。虽然编写需要完全精通《OLE 内部》才能调整的对象固然很好,但更重要的是构建一个框架,让那些对 COM 了解有限甚至完全不了解的人也能为您创建新组件。如果您是您工作场所现有的 COM 专家,这一点尤其重要。想一想,您真的想成为唯一一个能够为您系统的其余组件编写代码的人吗?在可能的情况下,编写一两个特定类别的组件,然后尽可能多地泛化代码,并将其分解为样板代码或库代码,其他人可以通过编写普通的 C++ 对象来使用。然后,当需要编写第十个非常枯燥的串行卡压印设备驱动程序时,您可以将其交给任何了解 C++ 的人。不幸的是,我发现 ATL 和所有向导都使这类事情更加困难。除非您编写一个生成您 90% 组件的向导……问题是,向导不会鼓励您考虑如何编写第十个几乎完全相同但通过线路传输的协议略有不同的驱动程序。复制粘贴的方式行不通。
总结一下……
使用 COM 不必很困难。许多项目可以通过向系统中添加一些可替换的元素来增加灵活性。您不必深入研究并尝试使用所有 COM 技巧来获得一些好处。简单的用法有其存在的价值,并帮助您习惯于使用该技术。有关最新更新,请参阅 Len 主页上的文章。