关于registry-free COM的几点局限

最近在工作中用到了这项registry-free COM技术,也就是免注册COM。

我们提供的COM组件,会被多个产品用到,而这多个产品可能:

  • 安装在同一台机子上
  • 使用的COM组件是不同版本的

这样,如果使用普通的注册的COM组件,就会因相互覆盖而产生冲突,轻则功能出错,重则直接崩溃。这就是著名的DLL Hell.

什么是Registry-Free COM

免注册COM,一般也可以叫做Private Assembly, Side-by-side Assembly,Isolated Application, 它不是注册在注册表里的全局共享的组件,而是专门用于某个Application的。看看MSDN的说法:

Registration-free COM is a mechanism available on the Microsoft Windows XP (SP2 for .NET-based components) and Microsoft Windows Server 2003 platforms. As the name suggests, the mechanism enables easy (for example, using XCOPY) deployment of COM components to a machine without the need to register them.

以前加载一个COM组件是从注册表入手,找到某个COM class对应的DLL并进行初始化,那么用免注册COM是如何做到这一点的呢?其实很简单,把这些接口,COM类以及对应的DLL, Tlb等这些以前放在注册表里的信息写入一个manifest文件,然后把这个文件链接进使用这个COM组件的Application中,操作系统内部的那套COM机制就会从这个manifest信息来加载COM组件 - 很明显,这涉及到对COM机制的更新,所以只有XP之后的系统才支持registry-free COM.

如果想深入的了解registry-free COM的工作机制,可以参考MSDN的权威说明:

Isolated Applications and Side-by-side Assemblies

这里还有一个比较全的例子教你一步一步创建一个registry-free的COM组件:

Registration-Free Activation of COM Components: A Walkthrough

另外,这里也有篇不错的文章:

Simplify App Deployment with ClickOnce and Registration-Free COM

Registry-Free COM的优点

主要有两点吧:

  • 避免了COM组件的全局共享,从而避免了DLL Hell
    MS还有个叫法是DLL Version Conflict:
    Compatibility problem. Assembly sharing problems occur when an application installs a version of a shared assembly that is not backward compatible with the previously installed version. A solution to DLL versioning conflicts is to use side-by-side assembly sharing and isolated applications. With side-by-side assembly sharing, multiple versions of the same Windows assembly can run simultaneously. Developers can choose which side-by-side assembly to use.
  • 因为不用写注册表,Applicaiton安装的时候管理员权限就不是必须的了 - 还记得Vista中那万恶的UAC吗?

Registry-Free COM的几点局限

应该说,微软提供的这种免注册COM对于一般应用已经是很完善了,也有效的解决了在COM组件发布时的那些"顽疾",但因为我们的使用比较深入,难免还是遇到一些问题,也可以说是它的一些缺陷吧:

  • 在一个进程中将一个Registry-Free COM的对像存入ROT(Running Object Table),却无法在另外一个进程中得到这个对象。
    ROT是一个全局的<CLSID=>Object>的map,在进程间通信时会用的比较多。一般就是在一个进程中把需要共享的对象用RegisterActiveObject注册到ROT,然后在另外一个进程中用通过GetActiveObject拿到那个对象。但是这在registry-free COM中却行不通,通过查看ROT,你根本看不到你注册的那个CLSID。怎么回事?下面是来自MS的解释:
    We have registered vs. configured CLSIDs.  In non-sxs COM, generally the registered CLSID matches the configured CLSID.  In SXS-based COM (plus some other instances, like certain kinds of DCOM) the two are different.  Because the same registered CLSID can appear twice in the same process from two different versions of a sxs component, the SXS machinery inserts two configured CLSIDs into the COM class DB for the processs.  Each sxs activation context has a map of {regclsid => conf-clsid-a} plus a lookup like {conf-clsid-a => {dll name, registered-clsid}}. They still represent the same object - if you did CoCreateInstance or GetActiveObject or any other mechanism - then you'd get the right instance back based on the active context at the time of the call. If inserting and fetching from the ROT pieces of code are in the "the same application", they use the same activation context and hence it works, but this is not true for cross-application as now the activation context are different and that's why we run into those errors and it doesn't support cross-process activation.

    简单来说,就是放到ROT里的CLSID已经不是你所定义的那个CLSID了,为了允许同一COM组件的不同版本共存于一个进程中,COM机制在内部把CLSID做了个修改,使其基于对象所在的DLL和定义的CLSID,这样,同一CLSID不同版本的对象,由于DLL名字的不同,就能共存于同一进程中了,从而也能在ROT中区分开来了。但是这里能在ROT中区分开并没有什么意义了,因为ROT存在主要目的是跨进程,而此时在另外一个进程中,我永远无法知道你修改过的CLSID是什么样的,也就无法得到所需要的对象了。针对对这种情况,目前没有比较好的解决方案,只有一个workaround:RegisterActiveObject时用一个全新的CLSID去注册COM对象(即使他们没有任何关系),这样的CLSID不会被修改,从而就能在另一个进程中从ROT中成功的取得这个对象了。

  • 微软同时提供了进程内COM组件(DLL)和进程外COM组件(Exe),但是在registry-free的世界里,现有资料和实验都表明,只有进程内COM组件才是被支持的。这就给我们带来了很大的难题,进程外COM Server的存在是有其理由的,不是每个都能转化为进程内COM组件的,比如在64位系统上的用来运行32位DLL的32位进程外COM Server - 这个比较复杂,恐怕需要一篇独立的文章来讨论。
posted @ 2009-10-31 11:39  lzprgmr  阅读(1550)  评论(0编辑  收藏  举报

黄将军