COM中的服务及其对象(译文)
正文:
简单的说,服务应用是(Server)对象(Object)的容器。服务可以被实现为二进制的动态链接库(DLL)服务,有时被称为进程内(n-process),因为动态链接库运行在客户应用地址空间内。也可以实现为二进制的执行程序(EXE)服务,有时被称为进程外(Out-Of-Process)因为可执行程序运行在和客户端不同的进程内。
注:下面动态链接库我就称为DLL。可执行程序我就称之为EXE。服务应用我就称之为Server。客户应用我就称之为Client。
一、创建动态链接库服务的优势:
1、运行模式为In-Process的DLLs和Client运行在同一个地址空间内。于是相互的沟涌是最优越的(性能最佳,不必进程外调用)
2、当你要创建某些基于用户界面的功能时,最好使用DLLs。因为创建带有窗口的EXEServer没什么必要。
3、DLL Server通常比EXE Server更容易调试。因为EXE有时候需要远程环境下调这种潜在的可能性。而DLL通常都是在一个机器上和Client一起调试就行了。
4、有时候用DLL Server本身就有优势。比如用于给Web Server做扩展,或者你的Server需要和MTS集成的时候。
二、创建EXE Server的优势:
1、EXE Servers在错误隔离方面更健壮。使用EXE Server的时候如果Client崩溃,则不会影响到Server。而 DLL 的话,不管是Client还是server崩溃,所有东西都会一起挂掉。
2、EXE server 更适合着重考虑远程部署以及安全性的场合。
3、有时候EXE二进制架构本身有优势,比如要创建 NT 服务的时候。
虽然 DLL Servers 通常和Client一起运行在进程内,但如果给他加个wrapper的EXE也是可能的。这个EXE 相当于他的host 或者说代理(surrogate). 通过这种方式,任何 DLL Servers 都可以当作 EXE 使用,仅仅因为 host 是在进程外执行的。MTS 就是一个 DLL host application 的例子,还有像Svchost,NT系统有许多服务都是由Svchost这个EXE代理的。
DLL servers随着Windows 2000 和COM+ 中MTS 和COM 的集成,变得原来越重要了。你将要写的Servers 都是可以被 属于COM+ runtime 的某个host 执行的。
三、Servers的激活:
现在我们在讨论Client 如何联系到Server, 然后请求一个Server上的特定的物体。Clients到底是如何做的?他是用 ShellExecute 来加载Server 吗?当加载完Server之后,Clients如何进入到Server并请求一个物体的?
假设Client 通过调用Win32 API ShellExecute 来启动Server. ShellExecute 需要一个完整的路径(或者至少你的程序必须在系统目录下)。这表示如果你把Server 移到另一个地方,你需要修改路径并重新编译Client. 解决这个问题的一个办法也许是把路径存到注册表里去。客户始终从注册表读取Server 的路径。但迟早有一天Server 会越来越庞大,以至于我们必须把他放到其他的机器上去。这时候就再也不能工作了。因为 ShellExecute 不可以调用另一台机器上的程序。现在我们需要开发一个能够在机器之间执行 ShellExecute 的程序。当然,这个问题应当由那些专家们来解决。 这样我们只需要简单的使用就可以了。
Server location 是一个我们不愿浪费太多时间说明的细节。COM 在这方面通过定义如下的标准来解决这个问题。
1、 Server如何告诉Clients他们在哪里的。
2、Client如何不考虑location问题就可以激活Server的。
四、Server的注册:
一个server通过一个叫做“注册“(registration)的过程来告诉这个世界它自己在哪里。比如server 给COM 接口关于他自己的位置。在Windows里这个是存在注册表的。实际上,Client对Server 本身不感兴趣。它感兴趣的是Server里的那些物体。所以我们实际上需要存储的是Server Objects。并且在每一个 Object 下,有一个子键(subkey) 指向Server文件的物理路径。
这样,Client 如果需要Foo这样的一个物体,它会说:hi,COM, 我需要一个叫做Foo的物体!然后COM 就去查注册表,并且如果找到了,就在他的子键下查找实际的Server 文件名。然后启动Server。 这正是 COM 中 server 激活的主要机制。
让我们来看看上面描述的激活机制的优点:
1、Client 不需要处理注册的细节。这个步骤由Server做了。这表明Server里必须有一些code来注册信息到COM。这个过程通常包括写入信息到Windows注册表。
2、如果Server重新定位了,你只需要简单的重新注册Server 即可。这样,你的注册表里会包含更新过地址信息的Server 注册信息。下次Client请求物体的时候,COM 知道到哪里去找到Server.
3、Client和Server是分开的。Client只要简单的告诉COM需要什么物体,扫描注册表,激活Server这些动作由COM来完成。如果Server 需要移到远程机器的情况也是一样。注册信息里将会包含指向远程的地址,COM 负责建立远程连接,激活物体等操作,Client 完全不要关心Server 在哪里。
为了让Server能注册他的Objects。必须有一个机制来标志这些Objects . 和我们前面讨论过(Previous discussion)的接口一样每一个物体也都有一个友好的、一个丑陋的名称。丑陋的名称(GUID)是数字的序列以保证唯一性;友好的名称为了让Client容易识别。GUID在COM中是利用一个统计上应该唯一的机制产生的。接口的GUID常称为Interface identifiers或IIDs,而物体的GUID称为Class identifiers或CLSIDs。
回到注册的步骤。Server通过这个方式注册自己:它填写拥有的那些Objects的CLSID. 在注册表中每一个CLSIDs包含一个指向Server文件路径的子键。比如你有一个叫做FooServer.exe的Server(假设这个文件在C:\下),而该Server包含两个物体Foo和Bar注册信息大致上有点像这样:













Client 如果想要创建Foo,只需要传递Foo的CLSID.COM完成扫描注册表,定位并激活FooServer的工作。Client也可以用友好的名称来调用。Server Objects的友好名称按如下规则:<ServerName>.<ObjectName> 比如 "FooServer.Foo" 和 "FooServer.Bar." 友好的名称术语叫做programmatic identifiers 或PROIDs.
为了使 client 能够通过 PROGIDs 来创建物体,PROGIDs 也注册了。他们的注册信息大致像这样:












注:位置透明性(Location transparency)是用来表示COM处理这过程特征的一个述评。
五、物体创建:
激活Server后,接下来要做的事情是进入到Server并请求物体。COM再一次的定义了让Server造出物体给Client的一个协议。这个协议很简单,其工作原理大致是这样的:
1、客户请求一个物体,COM查找注册表、定位并激活Server,如前面所述。
2、一旦Server 启动了,COM要求Server提供一个”物体创建者“(object creator)。物体创建者就是用来创建其他物体的物体。特别的情况是,物体创建者直接创建出Client需要的真正的物体。
3、物体创建者暴露一个基本的接口。该接口包含一个方法CreateInstance.用来创建真正的server 物体。然后COM(或者 client)调用这个CreateInstance方法来创建Server物体,并得到一个接口的指针。然后把这个接口指针传回给Client。
六、类工厂:
前面提到的这个“物体创建者”在COM中称之为“类工厂”(class factory)(很多人认为称为“物体工厂”更合适,因为创建的是物体而不是类)。类工厂暴露一个基本的接口叫做IClassFactory,该接口包含CreateInstance方法,以及其他一些方法。





这个类工厂协议是COM强制规定的,这表示所有的Server必须:
1、正确的实现一个类工厂功能。每一个Server中的物体都需要一个自己的类工厂。这样当Client要求某个物体的时候Server才能把他给创建出来。
2、像实现其他普通的Server物体一样实现类工厂。或者说,既然类工厂导出IClassFactory接口,它是一个bonafide COM物体。这表示类工厂必须进行引用计数,并且支持vtable binding。
为了让我们对这个过程了解的更具体些,我们来看看当Client请求FooServer创建一个Foo物体的时候,到底发生了什么:
1、Client请求COM“给我创建一个a FooServer.Foo物体”。
2、COM到注册表里去找"FooServer.Foo"这个ProgId,并把他翻译为Foo的CLSID。
3、COM激活FooServer.exe。
4、COM进入到FooServer.exe,请求“给我一个用于这个物体的类工厂”,并传入Foo的CLSID以便FooServer能得知给他哪一个类工厂。记住,每一个在FooServer里的物体都有一个对应的类工厂。比如有一个Foo的类工厂以及一个Bar的类工厂。
5、通过Foo的 CLSID.FooServer查找对应的类工厂,如果找到,传递这个类工厂的IClassFactory指针给COM。
6、COM调用IClassFactory.CreateInstance来创建Foo物体的实例。该方法的调用返回一个IFoo指针。
7、COM 把这个 IFoo 指针拿回来并传给Client。
顺带说一下,COM同时也提供了一个机制。让Client直接请求物体的类工厂。这种情况下,COM会进行相同的步骤到第5步。第5步之后,COM返回给Client IClassFactory指针。然后Client自己手工的调用IClassFactory.CreateInstance方法来创建出 Server物体的实例。
创建类工厂是非常乏味的工作。幸运的是,现在的开发环境隐藏了一大半的牵涉到创建类工厂的细节。比如,VB就完全隐藏了类工厂创建协议,以至于绝大部分的VB程序员都不知道有类工厂这个概念。