📄 205001.htm
字号:
HRESULT Submit();}</span><span id=Layer67></pre></font></div><p><font size=2 color=#3c3c3c face=arial>使用IDL是定义一个COM介面最有弹性的方式,不过它也同样是最复杂的。通常使用的人都是C++的程式设计师,他们都是能忍受痛苦的中间份子。然而Visual Basic的门槛比较低,它也允许定义COM介面不须要学习IDL,除非你希望控制介面定义的每一个部份。因为IDL并不是个简单的语言,所以这也算是一件好事。</span><span id=Layer68></font></p><p><font size=2 color=#3c3c3c face=arial>用其他方式来定义介面也是行得通的</span><span id=Layer69></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>用其他方式来定义介面也是行得通的</span><span id=Layer70></font></p><hr><p><font size=2 color=#3c3c3c face=arial>不管它们是如何定义的,COM介面共享相同的特色:一旦定义就是永久。换句话说,一旦介面在这个世界上出现之後,只要它有了客户端,COM的规则禁止你对这介面进行任何的变动。当然在开发的过程中,介面是可以进行改变的,不过一旦被使用後,就不能够再更改。到目前为止,被当做特定介面IID的GUID将永远用来辨识这个介面。若新增一个新的method,或新增一个新参数,则需定义一个全新的介面,与新的IID(不会有定义旧介面的新版本这种情况发生)。一开始你可能会觉得这个规则很愚蠢,不过它似乎已成为COM版本控制的中心要素,这也是本章稍後要谈论的。</span><span id=Layer71></font></p><p><font size=2 color=#3c3c3c face=arial>一旦COM介面被使用时,就无法再改变了</span><span id=Layer72></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>一旦COM介面被使用时,就无法再改变了</span><span id=Layer73></font></p><hr><p><font size=2 color=#3c3c3c face=arial><font size=2 face=arial color=#3e80d7><b> 介面继承</span><span id=Layer74> </b></font>COM允许介面之间相互继承。这代表了不须要再明确地重复定义既存的介面以成为新介面,新介面的定义者可以参考既有的介面,而它的method就会自动地包含进来了。这样将会带来很大便利性,我们将会看到,这也是一种很基本的案例。</span><span id=Layer75></font></p><p><font size=2 color=#3c3c3c face=arial>新的COM介面可以从既有的介面继承下来</span><span id=Layer76></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>新的COM介面可以从既有的介面继承下来</span><span id=Layer77></font></p><hr><p><font size=2 color=#3c3c3c face=arial>COM只支援单一继承,这代表的含意是一个介面一次只能从一个介面继承下来。同样地,只能继承此介面的method定义,而不是继承这个介面method真正实作的部份。这代表COM并不支援实作继承。</span><span id=Layer78></font></p><p><font size=2 color=#3c3c3c face=arial>COM只允许单一继承</span><span id=Layer79></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>COM只允许单一继承</span><span id=Layer80></font></p><hr><p><font size=2 color=#3c3c3c face=arial>当一个程式设计师撰写一个继承自其它介面的程式码时,这个程式设计师必须提供所有介面上所有method的程式码。当然,内建实作继承的语言,如C++可用来达到这个目的,但COM本身仍旧不支援method真正实作的继承。不管是好是坏,Visual Basic,Microsoft平台上最广泛使用的语言,提供有限的COM介面继承能力。这个事实大幅影响这个工具未来的发展性。</span><span id=Layer81></font></p><p><font size=2 color=#3c3c3c face=arial><font size=2 face=arial color=#3e80d7><b> 属性</span><span id=Layer82> </b></font>COM物件显露的介面包含method,再也没其它东西了。不过某些程式开发语言,尤其是Visual Basic,广泛地依赖属性的使用。一个属性是一个物件能包含的值,能够进行读取或是修改的动作。举例来说,一个代表订单的COM物件正要被提交了,它可能包含一些属性,如Customer与Order Number。若想要在Visual Basic的环境中运作良好,COM物件必须能够支援这个主意。当然,它们是可以的。</span><span id=Layer83></font></p><p><font size=2 color=#3c3c3c face=arial>Visual Basic广泛地使用属性</span><span id=Layer84></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>Visual Basic广泛地使用属性</span><span id=Layer85></font></p><hr><p><font size=2 color=#3c3c3c face=arial>实作在COM中某个介面的一个可读取属性,可透过一个method存取属性的值。若这个属性也可以进行修改,则介面将会包含其它的method允许进行修改的动作。从COM物件的观点来看,一个属性就是指它包含的资料,它的值可透过呼叫适当的method来读取或修改,这没什麽特别的。不过允许使用Visual Basic撰写的客户端透过自己原生的属性来检视这些method,会让Visual Basic的程式设计师生活更为容易了。</span><span id=Layer86></font></p><p><font size=2 color=#3c3c3c face=arial>一个COM物件也可以拥有属性</span><span id=Layer87></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>一个COM物件也可以拥有属性</span><span id=Layer88></font></p><hr><p><font size=2 color=#3c3c3c face=arial>在今天,常常看到以程式语言专属的method与属性来描述COM物件,特别在给Visual Basic程式设计师看的文件上。有些甚至支援COM IDL以定义method来读取及设定属性的值。虽然有些号称纯粹派的艺术家可能会抱怨,但在COM中,Visual Basic导向属性的存在,实际上是另一种挑战,尤其在建立系统物件模型时,它能够有效地在许多程式语言中使用。</span><span id=Layer89></font></p><font color=#3e72d7 face=arial size=4><b>类别(Class)</span><span id=Layer90></b></font><p><font size=2 color=#3c3c3c face=arial>在物件的技术中,一个类别通常被视为一个范本,以建立许多相似的实例。举例来说,Bridge类别可能有很多实例,如Golden Gate、Brooklyn与Pont Neuf。COM中的类别就很像这个。一个COM类别是由一些程式码实作出来的,可用来建立一个或多个COM物件的实例,每一个COM物件的类别都是相同的。</span><span id=Layer91></font></p><p><font size=2 color=#3c3c3c face=arial>每个COM物件都是某些类别建立的</span><span id=Layer92></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>每个COM物件都是某些类别建立的</span><span id=Layer93></font></p><hr><p><font size=2 color=#3c3c3c face=arial>大部份COM类别都会指派一个类别识别码,通常写成CLSID。一台电脑上可供一般使用的COM类别与类别的CLSID都列示在机器上的系统登录。有了CLSID,便可查看系统登录项目以找寻包含实作这个类别的程式码档案所在的位置。</span><span id=Layer94></font></p><p><font size=2 color=#3c3c3c face=arial>一个COM类别通常会被指派一个CLSID</span><span id=Layer95></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>一个COM类别通常会被指派一个CLSID</span><span id=Layer96></font></p><hr><p><font size=2 color=#3c3c3c face=arial>CLSID就是GUID,因此不会有两个类别拥有相同的名称这种情况发生。让每个类别都拥有唯一的名称是很重要的,因为就我们所视,任何公司建立的类别可能存在同一台机器上,或在一个公用分散的环境中。若这些类别以简单的字元名称来命名,则两个完全不同的类别,它们的名称就可能会相同。因为建立类别的许多实例时,命名冲突可能会产生问题。我们将於下一节中描述。</span><span id=Layer97></font></p><p><font size=2 color=#3c3c3c face=arial>CLSID就是GUID,它代表它们是全域唯一的</span><span id=Layer98></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>CLSID就是GUID,它代表它们是全域唯一的</span><span id=Layer99></font></p><hr><p><font size=2 color=#3c3c3c face=arial>同时,对程式设计师来说,使用CLSID并不是很方便的,因为16位元组的GUID并不亲切。为了简化这个问题,COM同样也允许为每个类别指定一个程式识别码,较常被称呼为ProgID。ProgID只是字元字串,因此程式设计师可以很容易使用、记忆。同时在必要时,它们也可以转换为CLSID。这个转换动作依赖於系统登录,它包含了安装在这台电脑上所有的COM类别其ProgID到CLSID的对照表。ProgID并不能确保一定是唯一的,不过通常都会使用命名原则,不让冲突的情况发生。(如所有的ProgID都以公司的商标开始命名)在任何一种情况下,ProgID只是为了方便而使用的,一个类别真正的名字是它的CLSID。</span><span id=Layer100></font></p><p><font size=2 color=#3c3c3c face=arial>一个类别也可以以人类可读取的ProgID命名</span><span id=Layer101></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>一个类别也可以以人类可读取的ProgID命名</span><span id=Layer102></font></p><hr><p><font size=2 color=#3c3c3c face=arial>特定COM类别的物件一般都会实作一些特殊的介面。举例来说,以CLSID_X 识别的COM类别之所有物件都可能实作IOrderEntry与IOrderStatistics介面。然而随着时间消逝,更改特定COM类别的物件所支援的介面是合法的,你可以新增一个介面,或丢弃一个旧介面。这类的变动并不需要更改CLSID。因此一个COM类别实际上是指一组特定的程式码,必要的动作只是更改程式码所要做的事。当某类别的实例是由修改过的程式码而建立时,这个实例将会反应出目前实作的功能。</span><span id=Layer103></font></p><p><font size=2 color=#3c3c3c face=arial>实作一个COM类别的程式码可以包装成一个单独的执行档,通常称为 EXE档案,或者包装成一个动态连结程式库(DLL)。这两者在目前都很常见,虽然因为某些在本书讨论到的理由,以及COM+的到来,使得将类别制作成DLL较为吸引人。实作成EXE的COM物件永远执行在与客户端不同的行程中。然而实作成DLL的COM物件无法执行在自己的行程中。这类的物件将会执行在与它客户端相同的行程中,或者藉由「代理程序(surrogate)」的协助,执行在其它的行程中。</span><span id=Layer104></font></p><p><font size=2 color=#3c3c3c face=arial>COM类别的程式码可以是一个DLL或一个EXE</span><span id=Layer105></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>COM类别的程式码可以是一个DLL或一个EXE</span><span id=Layer106></font></p><hr><p><font size=2 color=#3c3c3c face=arial>一个实作成DLL的COM类别称为一个in-process(或只称in-proc)伺服器,而实作成EXE的COM类别,若它执行在与客户端相同的机器,则称作本机伺服器(local server);若执行在其它的机器上,则称为远端伺服器(remote server)。总共有叁种可能性,图5-2展示QwickBank网域中使用两台电脑的情形。在此还有一个术语很重要,哪一个是元件。在此我得说明一下,在软体世界中并没有一个广为使用的字眼以定义之,也无法让人认同,但在COM中,一个元件就是一个档案,可能是一个DLL或是一个EXE,它包含一个或多个COM类别的程式码。实际上你可以把「元件」这个字与「伺服器」划上等号。目前并没有什麽花俏或抽象的定义,同时它也未涵盖在我们的工业中所有的术语,不过对COM来说,它是很精确的。</span><span id=Layer107></font></p><p><font size=2 color=#3c3c3c face=arial>COM元件可以执行在与客户端相同的行程,或另外的行程中</span><span id=Layer108></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>COM元件可以执行在与客户端相同的行程,或另外的行程中</span><span id=Layer109></font></p><hr><p><font size=2 color=#3c3c3c face=arial>虽然图中并没有显示,执行在一个代理行程中的DLL同样可以透过远端存取。就</span><span id=Layer110> <a target='_new' href=208.htm#>第八章</span><span id=Layer111></a> 所描述,这在COM+中非常重要。</span><span id=Layer112></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b>附注 </b></font><p><font size=2 color=#3c3c3c face=arial>虽然图中并没有显示,执行在一个代理行程中的DLL同样可以透过远端存取。就</span><span id=Layer113> <a target='_new' href=208.htm#>第八章</span><span id=Layer114></a> 所描述,这在COM+中非常重要。</span><span id=Layer115></font></p><hr><br><center><a target=_new href=imagesh/5-2.gif><img border=0 src='imagesl/5-2.gif'></a></center></span><span id=Layer116><center><table border=0 ><td align=center><font color=#3c3c3c face=arial size=2><font size=2 face=arial color=#3e80d7><b> 图 5-2</span><span id=Layer117> </b></font>COM物件可以是in-proc、本机或远端伺服器。</span><span id=Layer118></td></table></font></center><font color=#3e72d7 face=arial size=4><b>建立物件实例</span><span id=Layer119></b></font><p><font size=2 color=#3c3c3c face=arial>在典型的物件导向语言中,程式设计师定义一个类别,然後在执行时期,使用某些语言内建的操作来建立类别的实例。举例来说,在C++中呼叫new运算子再加上类别的名称,将会建立此类别的新实例。COM是一个系统物件模型,而不是一个语言专属的方案,不过它同样允许客户端呼叫适当的函数建立COM类别的实例 COM物件。这些函数的长相则依赖使用的程式语言不同而不同,因为每个语言都有自己与COM的对照。我将从C++程式设计师的观点来讨论COM物件如何建立,然後再从Visual Basic或Java程式设计师的观点来探讨之。</span><span id=Layer120></font></p><p><font size=2 color=#3c3c3c face=arial><font size=2 face=arial color=#3e80d7><b> 物件建立的基本概念</span><span id=Layer121> </b></font>若要建立特定COM类别的新实例,一个C++的程式设计师可以呼叫两个标准的函数:CoCreateInstance 或 CoCreateInstanceEx。这两者都是由COM runtime函式库实作的,更常被称为runtime,它是Windows 2000与Microsoft其它作业系统标准的一部份。CoCreateInstance与CoCreateInstanceEx两者都可以被视为C++ New运算子的同义字。虽然CoCreateInstanceEx可以在单机使用,但经证明它和DCOM一起使用时特别有用,我们将在稍後描述。因此在此我们只使用CoCreateInstance。</span><span id=Layer122></font></p><p><font size=2 color=#3c3c3c face=arial>一个C++客户端可以呼叫CoCreateIns-tance来建立一个COM类别的新实例</span><span id=Layer123></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>一个C++客户端可以呼叫CoCreateIns-tance来建立一个COM类别的新实例</span><span id=Layer124></font></p><hr><p><font size=2 color=#3c3c3c face=arial>当一个客户端呼叫CoCreateInstance,它传入许多的参数,最有趣的部份展示於图5-3步骤一。客户端最重要的便是传入一个CLSID,指明欲建立新实例的类别。客户端也会传入一个IID,指明新建立的物件之第一个想要使用的介面指标。</span><span id=Layer125></font></p><p><font size=2 color=#3c3c3c face=arial>CoCreateInstan-ce在COM+目录中查看特定的类别</span><span id=Layer126></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>CoCreateInstan-ce在COM+目录中查看特定的类别</span><span id=Layer127></font></p><hr><p><font size=2 color=#3c3c3c face=arial>当这个呼叫进行後,COM runtime以这个特定的CLSID搜寻本机的COM+目录,如图5-3的步骤二。COM+目录是一个逻辑上的项目,它实际上包含两个不一样的东西:系统登录与一个资料库,称RegDB,它和COM+都是Windows 2000新增的。包含在COM类别的所有元件只要是能够使用CoCreateInstance来建立的都会列在系统登录中,同时所有已设定元件(configured component) 的许多属性都会储存在RegDB。</span><span id=Layer128> <a target='_new' href=208.htm#>第八章</span><span id=Layer129></a> 中将会描述,一个已设定元件可以使用COM runtime提供的额外服务。然而在本章中,我们关心的是这些被称做未设定元件(unconfigured component)的东西。在Windows 2000发行之前,只有一种可能的COM元件,当然程式设计师仍有建立这类传统COM软体的自由。RegDB并没有储存关於未设定元件的资讯,这些资讯都是储存在系统登录之中。因此图5-3中显示,CLSID_Y并没有RegDB项目。</span><span id=Layer130></font></p><p><font size=2 color=#3c3c3c face=arial>接下来COM runtime粹取出储存在系统登录中CLSID_Y的档案名称,在这个案例下是CLSID_Y。这个档案中包含实作这个类别的程式码,因此COM runtime将载入这个档案。一旦完成这个动作,COM runtime就会引导这个新载入的程式码建立特定类别的实例,如图5-3步骤叁所示。然後新建立的物件便会将介面指标回传,这个介面就是客户端在CoCreateInstance呼叫时传入的IID指明的,如步骤四所示。最後runtime将把这个介面指标回传到客户端,然後客户端便呼叫新物件上的method,如步骤五所示。</span><span id=Layer131></font></p><p><font size=2 color=#3c3c3c face=arial>物件建立後,便回传一个介面指标到客户端</span><span id=Layer132></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>物件建立後,便回传一个介面指标到客户端</span><span id=Layer133></font></p><hr><br><center><a target=_new href=imagesh/5-3.gif><img border=0 src='imagesl/5-3.gif'></a></center></span><span id=Layer134><center><table border=0 ><td align=center><font color=#3c3c3c face=arial size=2><font size=2 face=arial color=#3e80d7><b> 图5-3</span><span id=Layer135> </b></font>一个客户端可以呼叫CoCreateInstance来建立COM类别的实例。</span><span id=Layer136></td></table></font></center><p><font size=2 color=#3c3c3c face=arial>很可能这个基本的结构会有许多的变形。举例来说,若所要求的类别之程式码载入後,COM可以允许物件建立的要求由这个执行中的复本掌控,并非每个要求都需要重新载入这个元件。同样地,在Windows 2000中,建立任何的COM物件同样会建立这个物件的context。Context对於已设定元件特别有用,因此我们将在</span><span id=Layer137> <a target='_new' href=208.htm#>第八章</span><span id=Layer138></a> 中讨论。</span><span id=Layer139></font></p><p><font size=2 color=#3c3c3c face=arial>在Visual Basic与Java中,物件建立的过程就和C++中的一样,最後呼叫CoCreateInstance,搜寻COM+目录等等,但程式设计师看起来有点不同。在Visual Basic中,客户端可以呼叫CreateObject函数,而不必呼叫CoCreateInstance,传入一个ProgID而非一个CLSID。Visual Basic runtime系统会使用系统登录把ProgID转换成CLSID,然後执行一些基本上相同的处理过程,如图5-3所示。一个Visual Basic的客户端也可以使用Visual Basic的NEW运算子来建立一个COM物件。若要达到这个目的,Visual Basic的程式设计师可以宣告一个适当的变数,然後将这个变数的值指派成新呼叫的结果。回传哪一个物件的介面指标则由这个变数的类型而决定。</span><span id=Layer140></font></p><p><font size=2 color=#3c3c3c face=arial>Visual Basic客户端可以使用CreateObject或new建立COM物件</span><span id=Layer141></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>Visual Basic客户端可以使用CreateObject或new建立COM物件</span><span id=Layer142></font></p><hr><p><font size=2 color=#3c3c3c face=arial>在Java中事情甚至变得更容易了。程式设计师只要永远呼叫Java的New运算子,没有特殊的COM物件建立函数。因为Java对物件的看法与COM相当雷同,这两者都允许物件拥有多个独立的介面,举例来说,Microsoft JVM能对照标准Java语言的语法以便与COM物件一起运作,或呼叫COM物件。</span><span id=Layer143></font></p><p><font size=2 color=#3c3c3c face=arial>Java程式设计师可以呼叫New来建立一个COM物件</span><span id=Layer144></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>Java程式设计师可以呼叫New来建立一个COM物件</span><span id=Layer145></font></p><hr><p><font size=2 color=#3c3c3c face=arial><font size=2 face=arial color=#3e80d7><b> Class Factory</span><span id=Layer146> </b></font>当一个客户端呼叫CoCreateInstance (或其中一个对应到Visual Basic或Java的语法,最後都会变成同样的东西),如前所描述,COM runtime会建立某个COM类别的实例被建立。对於客户端来说,如何发生这件事是个魔术 它只是发出要求,然後物件就存在了。然而对於撰写这个COM类别程式码的人,它可能不是个魔术。程式设计师可能得明确地实作一个Class Factory。</span><span id=Layer147></font></p><p><font size=2 color=#3c3c3c face=arial>物件的建立通常都是依赖於Class Factor</span><span id=Layer148></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>物件的建立通常都是依赖於Class Factor</span><span id=Layer149></font></p><hr><p><font size=2 color=#3c3c3c face=arial>一个Class Factory是一个COM物件实作了标准的IClassFactory介面。当一个COM runtime建立一个COM类别的新实例时, 实际上它呼叫了IClassFactory介面上一个叫 CreateInstance的method。C++的客户端也有可能要求这个介面的指标,然後直接建立新实例,不过这个选项目前不适用於Visual Basic或Java的程式设计师。</span><span id=Layer150></font></p><p><font size=2 color=#3c3c3c face=arial>每个Class Factory物件都实作了IClassFactory 介面</span><span id=Layer151></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>每个Class Factory物件都实作了IClassFactory 介面</span><span id=Layer152></font></p><hr><p><font size=2 color=#3c3c3c face=arial>一个在C++建立COM类别的程式设计师,可以撰写程式码手动取得所要求的Class Factory。C++总是提供给COM程式设计师最大的权力与痛苦。或者C++的程式设计师也可以使用工具,如ATL,提供Class Factory标准的实作。若说这是在今天主要C++程式设计师的第二条路真是一点也不为过,因为撰写Class Factory并不是特别有趣的东西。使用Visual Basic或Java的程式设计师从来就不需要烦恼Class Factory这些语言的执行时期环境会在它们需要时自动地提供。然而,不管Class Factory是由何而来,它扮演着建立COM物件基本要素的角色。</span><span id=Layer153></font></p><p><font size=2 color=#3c3c3c face=arial>程式设计师很少需要自行撰写Class Factory</span><span id=Layer154></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>程式设计师很少需要自行撰写Class Factory</span><span id=Layer155></font></p><hr><p><font size=2 color=#3c3c3c face=arial><font size=2 face=arial color=#3e80d7><b> 在一个物件模型中建立物件</span><span id=Layer156> </b></font>使用CoCreateInstance 建立客户端需要的每个COM物件是太过夸张了。有时,若只是要建立一个物件,使用标准的物件建立函数是很简单的,然後让物件提供自己的机制以建立相同类别或不同类别的物件。当透过这种方式使用COM物件时,有时称这种模型为物件模型。</span><span id=Layer157></font></p><p><font size=2 color=#3c3c3c face=arial>并非所有的物件都是使用CoCreateIns-tance建立的</span><span id=Layer158></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>并非所有的物件都是使用CoCreateIns-tance建立的</span><span id=Layer159></font></p><hr><p><font size=2 color=#3c3c3c face=arial>举例来说,Microsoft Excel提供给使用者的服务,同样也可以让其它的应用程式来存取。为了达到这的目的,Excel实作一组不同的COM类别,每个类别提供某些method。不过一个客户端使用CoCreateInstance(或对照到Visual Basic或Java的语法)只建立一个物件的实例。这个物件的method可以将介面指标回传到其它由不同类别新建立的COM物件。这些物件的建立并不依赖於系统登录,这些类别也没有CLSID的必要。这个物件模型定义成一个阶层状,树状结构中下层的物件都是由较高阶层的物件建立的。客户端仍旧握住根物件下物件直接的介面指标,不过牵涉到物件建立过程的系统登录只有根物件的而已。运作的过程如图5-4。</span><span id=Layer160></font></p><br><center><a target=_new href=imagesh/5-4.gif><img border=0 src='imagesl/5-4.gif'></a></center></span><span id=Layer161><center><table border=0 ><td align=center><font color=#3c3c3c face=arial size=2><font size=2 face=arial color=#3e80d7><b> 图5-4</span><span id=Layer162> </b></font>在某些情况下,客户端使用CoCreateInstance来建立COM物件阶层中的根物件,然後使用物件自己的method来建立其它的物件。</span><span id=Layer163></td></table></font></center><p><font size=2 color=#3c3c3c face=arial>这些method都是透过dispinterface显露的。只要在Visual Basic或其它语言中撰写简单的程式,使用内嵌在Excel中的COM物件,就有可能自动处理某些工作,而不必手动进行。这是早期dispinterface的其中一种应用,这也就是为什麽有时称做自动化(automation)的原因了。</span><span id=Layer164></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b>附注 </b></font><p><font size=2 color=#3c3c3c face=arial>这些method都是透过dispinterface显露的。只要在Visual Basic或其它语言中撰写简单的程式,使用内嵌在Excel中的COM物件,就有可能自动处理某些工作,而不必手动进行。这是早期dispinterface的其中一种应用,这也就是为什麽有时称做自动化(automation)的原因了。</span><span id=Layer165></font></p><hr><font color=#3e72d7 face=arial size=4><b>全域的介面: IUnknown</span><span id=Layer166></b></font><p><font size=2 color=#3c3c3c face=arial>还有许多重要的问题尚未回答呢。首先,我们看到当物件建立时,客户端取得新建立的物件之介面指标。不过因为这个COM物件总是支援多重介面,那麽客户端如何取得物件上其它介面的介面指标呢?第二,我已描述COM物件是如何建立的,不过它们是如何被毁灭的呢?因为COM在今日已广被使用,也因为我们机器的记忆体中并没有充满旧的COM物件,一定有某种让它们消失的机制。</span><span id=Layer167></font></p><p><font size=2 color=#3c3c3c face=arial>这些问题都可以透过IUnknown中的method解决。IUnknown是COM中最基本的介面。每个COM物件都必须支援IUnknown,它也是为何所有的COM物件实际上支援两个或多个介面的原因:一个只提供IUnknown介面的物件并没有多大的用处。实际上,每个COM物件上的每个介面都必须继承自IUnknown,它代表只要有任何COM物件的介面指标,客户端永远都可以呼叫IUnknown中的method。当然就如它的重要性,这个介面中只有叁个method:QueryInterface、AddRef,与Release。接下来将讨论这叁者中的第一个method允许客户端要求一个COM物件的新介面,而最後一个则是用来控制COM物件的生命周期的。</span><span id=Layer168></font></p><p><font size=2 color=#3c3c3c face=arial>每个COM物件的每个介面都继承自IUnknown</span><span id=Layer169></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>每个COM物件的每个介面都继承自IUnknown</span><span id=Layer170></font></p><hr><p><font size=2 color=#3c3c3c face=arial><font size=2 face=arial color=#3e80d7><b> 取得一个物件的新介面指标</span><span id=Layer171> </b></font>当物件建立时,客户端可以取得新物件的第一个介面指标。一旦取得这个指标,它便可以呼叫物件的IUnknown::QueryInterface指标来取得这个物件可能支援的其它介面。图5-5展示这个运作的过程。</span><span id=Layer172></font></p><p><font size=2 color=#3c3c3c face=arial>QueryInterface可让你从物件获得新的介面指标</span><span id=Layer173></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>QueryInterface可让你从物件获得新的介面指标</span><span id=Layer174></font></p><hr><br><center><a target=_new href=imagesh/5-5.gif><img border=0 src='imagesl/5-5.gif'></a></center></span><span id=Layer175><center><table border=0 ><td align=center><font color=#3c3c3c face=arial size=2><font size=2 face=arial color=#3e80d7><b> 图5-5</span><span id=Layer176> </b></font>客户端呼叫IUnknown的QueryInterface method以获得一个COM物件上的新介面指标</span><span id=Layer177></td></table></font></center><p><font size=2 color=#3c3c3c face=arial>回顾一下每个介面都继承自IUnknown,如此代表所有IUnknown的method都可以在任何介面指标上被呼叫。当一个客户端呼叫QueryInterface,它会传入介面的IID。若COM物件实作了这个特定的介面,QueryInterface的呼叫将会回传一个指向这个介面的介面指标。若物件并没有实作这个介面,QueryInterface就会回传Null。</span><span id=Layer178></font></p><p><font size=2 color=#3c3c3c face=arial>跟往常一样,这个呼叫看起来的样子是依赖於用来撰写客户端的程式语言。一个C++的客户端可以直接呼叫QueryInterface。然而,Visual Basic与Java客户端则不行。这两个语言取代的做法是在必要时,在幕後呼叫这个method。不变的是,COM在任一种情况下都是一样的。不同之处在於,COM的机制要如何对应到每一个程式开发语言。</span><span id=Layer179></font></p><p><font size=2 color=#3c3c3c face=arial>QueryInterface是一个非常简单的method。仅管它有简单性,但它是构成COM处理物件版本的基础。因为它允许客户端在呼叫一个物件介面上的method之前,先安全地判断物件是否支援某个介面,客户端可以使用旧版本的物件,使用这个支援较少介面的物件,而不会发生问题。客户端可能从旧物件接收到比实际上要少一些的服务,同时并不强制客户端呼叫特定的method(或许会毁损)来判断物件是否支援之。</span><span id=Layer180></font></p><p><font size=2 color=#3c3c3c face=arial>QueryInterface对版本控制来说是很重要的</span><span id=Layer181></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>QueryInterface对版本控制来说是很重要的</span><span id=Layer182></font></p><hr><p><font size=2 color=#3c3c3c face=arial><font size=2 face=arial color=#3e80d7><b> COM物件如何被毁灭</span><span id=Layer183> </b></font>因为客户端明确地建立物件,因此必须有某种方法可以毁灭它们。某些程式语言,如C++,一旦物件建立後,允许客户端明确地删除物件。然而,COM的方式则有所不同。在COM中并没有标准的机制可允许客户端直接毁灭一个物件。取代的做法,COM物件的生命周期是由一项技术所控制的,称参考计数(reference counting)。</span><span id=Layer184></font></p><p><font size=2 color=#3c3c3c face=arial>COM依赖参考计数</span><span id=Layer185></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>COM依赖参考计数</span><span id=Layer186></font></p><hr><p><font size=2 color=#3c3c3c face=arial>这个概念很简单:每个COM物件会追踪它拥有多少个介面指标,然後将这个值储存在它的参考计数中。当一个客户端第一次建立一个COM物件,物件将会拥有一个介面指标,因此参考计数中的值将会是一。若这个客户端使用QueryInterface取得这个物件的其它介面指标,物件便会增加它的参考计数值为二。当一个客户端使用完介面指标後,它必须在这个介面指标上呼叫IUnknown::Release。当它接收到这个呼叫时,物件便从它的参考计数中减去一。一旦客户端在它握住的此物件之所有介面指标上呼叫了IUnkown::Release,则物件的参考计数值便变成零。当这个情况发生时,通常物件会自我毁灭。</span><span id=Layer187></font></p><p><font size=2 color=#3c3c3c face=arial>当使用完COM物件後,客户端必须呼叫Release</span><span id=Layer188></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>当使用完COM物件後,客户端必须呼叫Release</span><span id=Layer189></font></p><hr><p><font size=2 color=#3c3c3c face=arial>然而对於物件的客户端来说,将一个介面指标传到其它的客户端是完全合法的。若没有任何人告诉物件发生什麽事,它的参考计数将不会正确地反应出它使用的介面指标数目。此外,每当介面指标被复制後,例如当指标被传送到其它客户端时,则必须呼叫物件的AddRef method。如此将会让物件将参考计数的值增加一,正确地为新的介面指标负起责任。同时,这个物件使用完毕後,新介面指标的使用者必须呼叫Release。</span><span id=Layer190></font></p><p><font size=2 color=#3c3c3c face=arial>当一个介面指标被复制时,客户端必须呼叫AddRef</span><span id=Layer191></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>当一个介面指标被复制时,客户端必须呼叫AddRef</span><span id=Layer192></font></p><hr><p><font size=2 color=#3c3c3c face=arial>参考计数是个好主意,也能够聪明地控制COM物件的生命周期,特别是在没有一个客户端可以可靠地知道何时摧毁物件是安全的这种环境中。对於使用C++撰写的客户端,程式设计师必须正确地撰写呼叫Release与AddRef的程式码。对於使用Visual Basic 与Java撰写的客户端而言,这些呼叫的动作是不必要的。这两个程式语言的runtime环境会在必要时呼叫Release与AddRef。是的,对非C++的程式设计师而言,生活是较为简单的。</span><span id=Layer193></font></p><font color=#3e72d7 face=arial size=4><b>呼叫Method</span><span id=Layer194></b></font><p><font size=2 color=#3c3c3c face=arial>在预设情况下,呼叫COM物件上的method是同步的操作。换句话说,客户端发出呼叫,执行method,然後这个呼叫便回传了,客户端必须有耐心地等待整个过程。直到呼叫回传为止,呼叫这个method的客户端执行绪是被绑死的。</span><span id=Layer195></font></p><p><font size=2 color=#3c3c3c face=arial>大部份的me-thod都是以同步的方式呼叫</span><span id=Layer196></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>大部份的me-thod都是以同步的方式呼叫</span><span id=Layer197></font></p><hr><p><font size=2 color=#3c3c3c face=arial>在Windows 2000,COM客户端也可以以非同步方式呼叫method。非同步呼叫并不会在客户端等待回应时被绑死不能往下执行,而是这个呼叫会马上回传。当这个呼叫在执行时,允许客户端继续执行它的工作。虽然对程式设计师而言这较为复杂,但是这种呼叫的风格有时会显着地增进执行效能。不管是非同步或使用传统的风格进行,客户端也可以取消进行中的呼叫。本节将描述这两种选项,先从取消开始吧。</span><span id=Layer198></font></p><p><font size=2 color=#3c3c3c face=arial>你可以以同步或非同步方式进行呼叫,或取消呼叫</span><span id=Layer199></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>你可以以同步或非同步方式进行呼叫,或取消呼叫</span><span id=Layer200></font></p><hr><p><font size=2 color=#3c3c3c face=arial><font size=2 face=arial color=#3e80d7><b> 取消呼叫</span><span id=Layer201> </b></font>假设一个客户端对某些COM物件发出一个非同步的呼叫。特别是这个物件是执行在其它的行程或其它机器上,这个呼叫有可能会花费比客户端预期等待还要久的时间执行。类似这样的情况,若有某些方式可以取消进行中的呼叫是很有用的。</span><span id=Layer202></font></p><p><font size=2 color=#3c3c3c face=arial>Windows 2000增加一个很简单的机制以达到这个目的。(每当某些执行绪发出一个非同步呼叫时,便建立一个Cancel物件,此物件实作了ICancelMethodCalls介面。若要取消这个呼叫,客户端的第二条执行绪便可以呼叫CoGetCancelObject,传入发出呼叫的这条执行绪之识别码。如此将会为这个未完成的呼叫回传一个指向Cancel 物件ICancelMethodCalls介面的介面指标。若要取消这个呼叫,第二条执行绪只要呼叫Cancel物件上的ICancelMethodCalls::Cancel。</span><span id=Layer203></font></p><p><font size=2 color=#3c3c3c face=arial>呼叫Cancel物件上的me-thod可以取消进行中的呼叫</span><span id=Layer204></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>呼叫Cancel物件上的me-thod可以取消进行中的呼叫</span><span id=Layer205></font></p><hr><p><font size=2 color=#3c3c3c face=arial>对於C++程式设计师来说,至少证据显示这个机制的目标。</span><span id=Layer206></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b>附注 </b></font><p><font size=2 color=#3c3c3c face=arial>对於C++程式设计师来说,至少证据显示这个机制的目标。</span><span id=Layer207></font></p><hr><p><font size=2 color=#3c3c3c face=arial><font size=2 face=arial color=#3e80d7><b> 非同步呼叫</span><span id=Layer208> </b></font>同步呼叫本来就很容易理解,但很悲哀地,非同步呼叫就不容易了。支不支援是选择性的,若一个COM物件接收任何非同步呼叫,它实作了ICallFactory,这个介面只包含一个method,CreateCall。当客户端希望以非同步方式呼叫时,它呼叫目标物件上的ICallFactory::CreateCall,传入欲呼叫的method所在之介面的介面识别码(IID)。这个呼叫回传一个指向Call物件之介面指标,这个物件是客户端想要对其发出呼叫的物件。</span><span id=Layer209></font></p><p><font size=2 color=#3c3c3c face=arial>非同步呼叫依赖一个Call物件</span><span id=Layer210></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>非同步呼叫依赖一个Call物件</span><span id=Layer211></font></p><hr><p><font size=2 color=#3c3c3c face=arial>非同步呼叫是根据每个介面来定义的,介面中每一个呼叫不是非同步呼叫就是全部为同步呼叫。若要让一个介面可以使用非同步呼叫,它的建立者必须将它标识为一个特定的IDL属性。如此可让Microsoft IDL (MIDL)编译器产生这个介面的非同步定义部份,而每个介面的method将会一分为二。当客户端希望呼叫method时,便呼叫第一个method,指派一个以「Begin_」开始的名称。这个method包含原始method中所有的输入参数。当客户端希望取回method的结果时,便可以呼叫这一对method中的第二个method,指派一个以「Finish_」开始的名称。举例来说,回想本章提及的IOrderEntry介面。若这个介面标识为非同步,这些method的每一个都会切割成一对method。举例来说, Add method将会切割为Begin_Add与Finish_Add。而Submit method将会切割为Begin_Submit与Finish_Submit。虽然说一个特殊的呼叫物件一次只能由一个非同步的呼叫来处理客户端,但是在Begin method与对应的Finish method可自由地进行任何作业,它不须要等待。</span><span id=Layer212></font></p><p><font size=2 color=#3c3c3c face=arial>非同步介面将每个method切割为二</span><span id=Layer213></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>非同步介面将每个method切割为二</span><span id=Layer214></font></p><hr><p><font size=2 color=#3c3c3c face=arial>对於实作非同步呼叫的物件来说,非同步method与其它的method并没有什麽不同。一旦被呼叫後,method便执行,然後回传一些结果。然而一旦这个呼叫回到客户端的行程中,Begin method的proxy便会通知Call物件这个method已执行完毕。若要判断何时非同步呼叫method的动作是否完成,客户端可以呼叫call物件实作的ISynchronize介面上之Wait method。若这个method回传的值指明呼叫已完成,客户端可以呼叫适当的Finish method 取回执行的结果。如果愿意的话,客户端可以呼叫ICancelMethodCalls介面的Cancel method以便取消执行中的非同步呼叫。</span><span id=Layer215></font></p><p><font size=2 color=#3c3c3c face=arial>客户端可得知非同步呼叫完成或在完成之前取消时</span><span id=Layer216></font></p><hr><font face=Arial Black color=#3e77d7 size=3><b></b></font><p><font size=2 color=#3c3c3c face=arial>客户端可得知非同步呼叫完成或在完成之前取消时</span><span id=Layer217></font></p><hr><p><font size=2 color=#3c3c3c face=arial>非同步呼叫有一些限制。第一,它们只能和vtable介面一起运作,在dispinterface与dual interface上的method无法使用这种方式呼叫。第二,在使用上,非同步呼叫仍旧比一般的同步呼叫要复杂些。不过它们所带来的好处有时是值得努力的。</span><span id=Layer218></font></p><p><font size=2 color=#3c3c3c face=arial>Windows 2000同样也提供一种或多种方式来呼叫COM物件上的method,而不须等待回应,称做为伫列元件(Queued Co
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -