精华区 [关闭][返回]

当前位置:网易精华区>>讨论区精华>>编程开发>>C/C++>>COM技术>>Re:使用COM进行编程 (1-8)

主题:Re:使用COM进行编程 (1-8)
发信人: bennycyb(梦竹)
整理人: wenbobo(2002-12-27 15:51:54), 站内信件
1、什么是COM对象?
  COM对象基本上是一只黑盒子,应用程序用它来完成一个或多个任务。他们的大多数实现一般是DLL(动态连接库)。正如通常的DLL一样,COM对象提供方法给应用程序调用并执行所支持的任务。应用程序于COM对象交互有些类似于他们与C++对象交互的同样方式。但是有一些显著的区别:

与C++对象相比,COM对象强调更加严格的封装。你不能简单的创建对象和调用任何公共方法。一个COM对象的公共方法被组合进一个或多个接口。要使用一个方法,你必须创建此对象并从对象获得适当的接口。一个典型的接口包括方法的一个相关集合,它提供对对象的特殊特性的访问途径。例如,IDirect3DCubeTexture8接口包含允许你操纵立方体材质资源的方法。任何不属于接口的方法都是不可访问的。 
COM对象的创建方式与C++对象不同。创建COM对象有数种方式,但都包含COM规范的技术。微软DirectX API包括多种辅助函数和方法来简化大多数DirectX对象的创建。 
你必须使用COM规范技术来控制对象的生命周期。 
COM对象并不需要显式的加载。COM对象的特色是包含在DLL中。当然,为了使用COM对象,你不需要显示的载入DLL或者连接到静态连接库。每个COM对象都有一个唯一注册的标识符,用来创建对象。COM自动载入正确的DLL。 
COM是二进制规范。COM对象可以由多种语言来编写和访问。你不需要知道关于此对象的源代码的任何事情。例如,微软的Visual Basic应用程序照样使用由C++编写的COM对象。 
  1.1 对象和接口
  理解对象和接口的区别是至关重要的。作为偶然用法,有时一个对象可以用它的基本接口的名字来表示。然而,严格说来,这两个术语是不可互换的。

一个对象可以暴露任何数目的接口。例如,当所有对象必须暴露IUnknown接口时,它们通常至少多暴露一个接口,并且可以更多。要使用一个特定的方法,你不仅必须创建对象,而且必须获得正确的接口。 
不止一个对象暴露同样的接口。一个接口是一组执行(操作的一个特定集合)的方法。接口定义仅仅指定方法的语法和它们的一般功能。任何COM对象都需要支持一个操作的特殊集合,对象通过暴露一个适当的接口来做到这一点。一些接口是高度专用的,并且仅仅暴露一个单一的对象。其他接口暴露许多对象,这在多种环境中是很有用的。极端的例子是IUnknown接口,所有的COM对象都暴露它。

  注意:如果一个对象暴露一个接口,它必须支持接口定义中的每个方法。换句话说,你能够调用任何方法并且确保它存在。但是,关于如何实现一个特定方法的细节则可能因不同对象而有所不同。例如不同对象可以使用不同算法来达到最终结果。也无法保证以值得的方式来支持一个方法。有时一个对象暴露一个常用的接口,但是仅仅需要支持该方法的一个子集。你将仍然能够成功调用余下的其他方法,但它们将返回E_NOTIMPL。你应该查阅相关文档,以了解一个特别的对象如何实现一个接口。
  COM标准要求一个接口定义在发布之后就必须保持不变。例如,你绝对不能往一个已经存在的接口中加入一个新的方法。你必须替代创建一个新的接口。由于接口中有些什么方法没有任何限制,通常的做法是在下一代接口中包括旧接口中的所有方法,增加一些新的方法。
  一个接口有好几代版本并不少见。特别的是,所有版本完成本质上是同样全部的任务,但是它们在细节上可以不同。通常,一个对象将暴露接口的所有版本。由此允许老的应用程序继续使用此对象的旧接口,而新的应用程序则能够获益于新接口的特性。典型的,一系列接口会有同样的名字,附加一个整数指明它的版本。例如,如果原始接口命名为IMyInterface,那么后二代将命名为IMyInterface2和IMyInterface3。微软DirectX典型的用DirectX版本数字来标示接口的连续几代。

  1.2 GUIDs
  全球唯一标识符(GUID)是COM编程模型的关键部分。最基本的是,一个GUID是一个128位结构。然而,GUID以一种保证两个GUID不相同的方式来创建的。COM广泛使用GUID又两个主要目的:

用来唯一的标识一个特定COM对象。一个指派给一个COM对象的GUID称为一个类标识(Class ID(CLSID))。当你要创建相关COM对象的一个实例时使用CLSID。 
用来唯一的标识一个特定的COM接口。一个指派给一个COM接口的GUID称为接口标识(Interface ID(IID))。当你从一个对象请求一个特定接口时使用IID。一个接口的IID须是相同的,无论是什么对象暴露此接口。 
  注意:为了方便,文档通常用一个描述性的名字如IDirect3D8来指代对象和接口。在文档的上下文中,很少有混淆的危险。当然,严格说来,不能保证另一个对象或接口不具备同样的描述性名字。指示一个特定对象或接口的唯一准确方式是它的GUID。
  虽然GUID是结构,它们经常被表示为一个等价字符串。字符串形式的GUID的一般格式是“{VVVVVVVV-WWWW-XXXX-YYYY-ZZZZZZZZZZZZ}”,这里每个字母对应一个十六进制的整数。例如表示IDirect3D8的接口标识符IID的字符串形式是:

{1DD9E8DA-1C77-4D40-B0CF-98FEFDFF9512} 

  由于实际的GUID有点笨拙不便使用,还容易搞错,因此通常提供一个等价的名字。你可能使用这个名字来代替真正的结构,当你调用函数例如CoCreateInstance时。习惯的命名约定是在接口或对象的描述性名字前加上前缀IID_或者CLSID_。例如IDirect3D8接口的IID的名字是IID_IDirect3D8。

  1.3 HRESULT Values
  所有COM方法返回一个叫做HRESULT的32位整数。对于大多数方法,HRESULT等价于一个结构,此结构包含两部分独立信息:

此方法返回成功与否。 
更详细的信息 — 此方法所支持的操作结果。 
  一些方法仅仅从Winerror.h定义的标准集合中返回HRESULT值。然而,方法可自由的返回自定义HRESULT值来携带更详细的信息。这些值通常在方法的参考页面中说明。
  注意:你在方法的参考页面中找到的HRESULT值列表通常只是可能返回值的一个子集。此列表通常仅仅覆盖那些比较特殊的值和那些标准值(有一些特别于方法的含义)。你应该假定:一个方法可以返回多种标准的HRESULT值,即使它们没有明确的说明。
  虽然HRESULT值经常用来放回错误信息,你也不应该认为它们是错误代码。一个事实是指示成功或失败的bit是独立存储在包含详细信息的bits之外,这将导致HRESULT值的成功和失败代码可能是任何数字。根据约定,成功代码的名字具有S_前缀,失败代码具有E_前缀。例如,两个最常用的代码是S_OK和E_FAIL,分别简单的表明成功或者失败。
  既然事实上COM方法返回的成功或者失败代码可能是变化的,这将意味着,你必须仔细考虑你如何测试HRESULT值。例如,假设一个方法被说明为成功时返回S_OK,失败返回E_FAIL。但是,记住此方法也可能返回其他的成功或失败的代码。下面的代码段举例说明了这种使用简单测试的危险性。hr的值时此方法返回的HRESULT值。

  if(hr == E_FAIL)
  {
    // Handle the failure
  }
  else
  {
    // Handle the success
  }

  仅在此方法只有返回E_FAIL才表示失败的时候,这样的测试才能正确工作。然而,此方法还可以返回错误值如E_NOTIMPL或者E_INVALIDARG。这样的值被认为是成功,可能因此导致你的程序失败。
  如果你需要关于此方法的输出结果的详细信息,你需要测试每个相关的HRESULT值。不过,你可能只关心此方法是否成功。一个有效的测试方法是,HRESULT值表明成功与否,将传递给下面的定义在Winerror.h头文件中的一个宏: 
如果代码成功,宏SUCCEEDED返回TRUE;失败则返回FALSE。 
如果代码失败,宏FAILED返回TRUE;成功则返回FALSE。 
  你可以用FAILED宏修正上面的处理代码段:

  if(FAILED(hr))
  {
    // Handle the failure
  }
  else
  {
    // Handle the success
  }

  这段代码将正确的把E_NOTIMPL和E_INVALIDARG处理为失败。
  虽然大多数COM方法返回结构化的HRESULT值,但是还有少数使用HRESULT来返回一个简单整数。这暗示着,这些方法总是成功的。如果传送这样的HRESULT给SUCCESS宏,此宏将总是返回TRUE。一个常用的例子是IUnknown::Release方法。此方法使一个对象的引用计数减一,并返回当前的引用计数。请看“管理对象的生命周期”中关于引用计数的讨论。

  1.4 指针的地址
  如果你看过一些COM方法的参考页,你将可能遇到类似如下的一些内容:

  HRESULT CreateDevice(
    ...,
    IDirect3DDevice8** ppReturnedDeviceInterface
  );

  由于普通指针对C/C++开发者来说使非常熟悉的,COM经常使用一种二级指针(间接附加级,间接指针)。这种二级指针由类型声明后面跟着一个“**”来指明的,并且变量名字特别地带“pp”前缀。对于上面给出的例子,ppReturnedDeviceInterface参数特别指向一个IDirect3DDevice8接口指针的地址。
  和C++不一样的是,你不能直接访问一个COM对象。取而代之的是,你必须会的一个暴露此方法的接口的指针。要调用此方法,你使用同样的语法,等效于调用一个指针访问C++方法。例如,要调用IMyInterface::DoSomething方法,你使用下面的语法:

  IMyInterface *pMyIface;
  ...
  pMyIface->DoSomething(...);

  需要一个间接二级指针,是由于你并没有直接创建此指针。你必须调用多种方法中的一个,例如上面所说的CreateDevice方法。用这个方法获得一个接口指针,你声明一个变量作为指向该接口的指针,并传递此变量的地址给此方法。换句话说,你传递一个指针的地址给此方法。当此方法返回时,此变量将指向所要求的接口,并且你能够使用这个指针来调用此接口的任何方法。请看“获取并使用COM接口”以深入讨论如何使用接口指针。




----
生活把人的灵魂打倒,压迫
但艺术将其修复,使你仍能拥有美好的灵魂    
    

[关闭][返回]