原文地址: http://www.techvanguards.com/com/concepts/serversandobjects.asp
标题: 服务器和对象
原文标题: Servers and Objects
原文作者: Binh Ly
翻译: 木野狐(Roger Chen)
译者注:
鉴于我个人的喜好,有一些术语可能和习惯稍有不同。比如 object(对象) 我喜欢称之为 “物体”。 这个请读者阅读时注意。
正文:
---------------------------------------------------------------------------------------------------------
简单的说,Server 是 Objects 的容器。Server 可以被实现为二进制的 DLL (有时称为进程内(in-process) Server, 因为 DLL 运行在 Client 的地址空间内). 也可以实现为二进制的 EXE(进程外(out-of-process) Server, 因为 EXE 运行在和 Client 不同的进程内)。
创建 DLL Server 的好处:
运行模式为 in-process 的 DLLs 和客户程序运行在同一个地址空间。于是相互的沟通是最优的(最大性能)。
当你要创建某些基于用户界面的功能时,最好使用 DLLs. 因为创建带有窗口的 EXE 的 Server 没什么道理。
DLL servers 通常比 EXE servers 更容易测试/调试。因为 EXE 的有需要远程环境下调试的潜在可能性。而 DLL 通常都是在一个机器上和客户程序一起调试就行了。
有时候用 DLL servers 本身就有优势。比如用于给 web server 使用的必须扩展名为 dll 的时候,或者你的 server 需要和 MTS 集成的时候。
创建 EXE servers 的好处:
EXE servers 在错误隔离方面更加健壮。 使用 EXE servers 的时候,如果客户崩溃,不会影响到 server. 而 DLL 的话,不管是客户还是 server 崩溃,所有东西都会一起完蛋。
EXE servers 更适合着重考虑远程部署以及安全性的场合。
有时候 EXE 二进制架构本身有优势。比如要创建 NT 服务的时候。
虽然 DLL servers 通常和客户一起运行在进程内,但如果给他加个 wrapper 的 EXE 也是可能的。这个 EXE 相当于他的 host 或者说代理(surrogate). 通过这种方式,任何 DLL servers 都可以当作 EXE 使用,仅仅因为 host 是在进程外执行的。MTS 就是一个 DLL host application 的例子。
DLL servers 随着 Windows 2000 和 COM+ 中 MTS 和 COM 的集成,变得原来越重要了。你将要写的 servers 都是可以被 属于 COM+ runtime 的某个 host 执行的。
Server 的激活
现在我们在讨论 client 如何联系到 server, 然后请求一个 server 上的特定的物体。client 到底是如何做的?他是用 ShellExecute 来加载 server 吗?当加载完 server 之后, client 如何进入到 server 并请求一个物体的?
假设 client 通过调用 Win32 API ShellExecute 来启动 server. ShellExecute 需要一个完整的路径(或者至少你的程序必须在系统目录下)。这表示如果你把 server 移到另一个地方,你需要修改路径并重新编译 client. 解决这个问题的一个办法也许是把路径存到注册表里去。客户始终从注册表读取 server 的路径。但迟早有一天,server 会越来越庞大,以至于我们必须把他放到其他的机器上去。这时候就再也不能工作了。因为 ShellExecute 不可以调用另一台机器上的程序。现在我们需要开发一个能够在机器之间执行 ShellExecute 的程序。当然,这个问题应当由那些专家们来解决。 这样我们只需要简单的使用就可以了。
Server location 是一个我们不愿浪费太多时间说明的细节。COM 在这方面通过定义如下的标准来解决这个问题。
1) servers 如何告诉 clients 他们在哪里的。 2) clients 如何不考虑 location 问题就可以激活 servers 的。
注册
一个 server 通过一个叫做“注册”(registration) 的过程来告诉这个世界它自己在哪里。比如 server 给 COM 接口关于他自己的位置。在 Windows 里这个是存在注册表的。实际上,client 对 server 本身不感兴趣。它感兴趣的是 server 里的那些物体。所以我们实际上需要存储的是 server objects. 并且在每一个 object 下,有一个子键(subkey) 指向 server 文件的物理路径。
这样,client 如果需要 Foo 这样的一个物体,它会说: hi, COM, 我需要一个叫做 Foo 的物体! 然后 COM 就去查注册表,并且如果找到了,就在他的子键下查找实际的 server 文件名。然后启动 server. 这正是 COM 中 server 激活的主要机制。
让我们来看看上面描述的激活机制的优点:
client 不需要处理注册的细节。这个步骤由 server 做了。这表明 server 里必须有一些 code 来注册信息到 COM. 这个过程通常包括写入信息到 Windows 注册表。
如果 server 重新定位了,你只需要简单的重新注册 server 即可。这样,你的注册表里会包含更新过地址信息的 server 注册信息。下次 client 请求物体的时候, COM 知道到哪里去找到 server.
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. 注册信息大致上有点像这样:
<CLSID registry section>
... other CLSIDs ...
CLSID of Foo
CLSID of Foo\ServerLocation = "c:\FooServer.exe"
CLSID of Bar
CLSID of Bar\ServerLocation = "c:\FooServer.exe"
...other CLSIDs...
client 如果想要创建 Foo, 只需要传递 Foo 的 CLSID. COM 完成扫描注册表,定位并激活 FooServer 的工作。client 也可以用友好的名称来调用。server objects 的友好名称按如下规则:<ServerName>.<ObjectName> 比如 "FooServer.Foo" 和 "FooServer.Bar." 友好的名称术语叫做 programmatic identifiers 或 PROGIDs.
为了使 client 能够通过 PROGIDs 来创建物体,PROGIDs 也注册了。他们的注册信息大致像这样:
<PROGID registry section>
... other PROGIDs ...
FooServer.Foo = CLSID of Foo
FooServer.Bar = CLSID of Bar
... other PROGIDs ...
位置透明性(Location transparency) 是用来表示 COM 处理这过程的特征的一个术语。
物体创建
激活 server 后,接下来要做的事情是进入到 server 并请求物体。COM 再一次的定义了让 server 造出物体给 client 的一个协议。这个协议很简单,其工作原理大致是这样的:
客户请求一个物体,COM 查找注册表、定位并激活 server, 如前面所述。
一旦 server 启动了,COM 要求 server 提供一个 "物体创建者" (object creator) 。物体创建者就是用来创建其他物体的物体。特别的情况是,物体创建者直接创建出 client 需要的真正的物体。
物体创建者暴露一个基本的接口。该接口包含一个方法 CreateInstance. 用来创建真正的 server 物体。然后 COM(或者 client) 调用这个 CreateInstance 方法来创建 server 物体,并得到一个接口的指针。然后把这个接口指针传回给 client.
Figure: 基本的服务器物体创建协议(Standard server object creation protocol)
类工厂
前面提到的这个“物体创建者” 在 COM 中称之为“类工厂”(class factory)(很多人认为称为“物体工厂”更合适,因为创建的是物体而不是类)。类工厂暴露一个基本的接口叫做 IClassFactory, 该接口包含 CreateInstance 方法,以及其他一些方法。
IClassFactory = interface
procedure CreateInstance;
...
end;
这个类工厂协议是 COM 强制规定的,这表示所有的 server 必须:
正确的实现一个类工厂功能。每一个 server 中的物体都需要一个自己的类工厂。这样当 client 要求某个物体的时候 server 才能把他给创建出来。
像实现其他普通的 server 物体一样实现类工厂。或者说,既然类工厂导出 IClassFactory 接口,它是一个 bonafide COM 物体。这表示类工厂必须进行引用计数,并且支持 vtable binding.
为了让我们对这个过程了解的更具体些,我们来看看当 client 请求 FooServer 创建一个 Foo 物体的时候,到底发生了什么:
Client 请求 COM "给我创建一个 a FooServer.Foo 物体"
COM 到注册表里去找 "FooServer.Foo" 这个 ProgId, 并把他翻译为 Foo 的 CLSID.
COM 激活 FooServer.exe
COM 进入到 FooServer.exe,请求 "给我一个用于这个物体的类工厂", 并传入 Foo 的 CLSID 以便 FooServer 能得知给他哪一个类工厂。记住,每一个在 FooServer 里的物体都有一个对应的类工厂。比如有一个 Foo 的类工厂以及一个 Bar 的类工厂。
通过 Foo 的 CLSID, FooServer 查找对应的类工厂,如果找到,传递这个类工厂的 IClassFactory 指针给 COM.
COM 调用 IClassFactory.CreateInstance 来创建 Foo 物体的实例。该方法的调用返回一个 IFoo 指针。
COM 把这个 IFoo 指针拿回来并传给 client.
Figure: Server object creation using the Class Factory
顺带说一下,COM 同时也提供了一个机制。让 client 直接请求物体的类工厂。这种情况下, COM 会进行相同的步骤到第5步。第 5 步之后,COM 返回给 client IClassFactory 指针。然后 client 自己手工的调用 IClassFactory.CreateInstance 方法来创建出 server 物体的实例。
创建类工厂是非常乏味的工作。幸运的是,现在的开发环境隐藏了一大半的牵涉到创建类工厂的细节。比如,VB 就完全隐藏了类工厂创建协议, 以至于绝大部分的 VB 程序员都不知道有类工厂这个概念。
我们在哪里?
我们刚刚看到了 server, server 激活, 以及物体是如何创建的基础知识。这些概念都是非常重要的,是让你成为一个厉害的 COM 开发者的一个里程碑。
进一步阅读:
Understanding ActiveX and OLE by David Chappell
Inside COM by Dale Rogerson
Essential COM by Don Box