使用COM(using COM) -翻译

来源:互联网 发布:电脑软件没了 编辑:程序博客网 时间:2024/05/17 03:38

最近用到DirectShow程的一些知,看DirectX9.0SDK候,便翻了些内容,部分是SDK于使用COM象的一些知

 

using COM

 

COM是一种被众多程序所使用的面向对象的编程模式。由于Microsoft® DirectX®运行时时以一种COM对象的形式运行的,所以所有的DirectX开发者都需要至少了解一些基本的COM原理和编程方法。虽然COM拥有一个困难和复杂的名声,但是对于大部分的DirectX应用程序来说,COM编程还是很简单的。

COM编程有很明显的两种类型:

  • 使用即存的COM对象,这个不比使用c++对象困难多少。
  • 封装自己的COM对象,这个工作可能是一个复杂的艰难的工作。COM编程的复杂和困难的部分原因就是因为这种编程模式。

大部分DirectX程序仅仅需要使用DirectXCOM对象,而不需要封装自己的COM对象,换句话说,DirectX开发者只需要了解一些简单,最初级的COM编程就可以了。

 

《一个COM对象究竟是什么?(What is a COM Object?)

 

COM对象基本上来说就是一个黑盒子, 可以被用来完成一个或多个任务。COM对象多被封装成一个动态链接库(DLL)。和通常的DLL一样,COM对象也通过暴漏一些方法(Method)来实现一些功能。COM对象和C++的对象一样和应用程序互相影响,但是却也有一些截然不同的特性。

  • COM对象有比C++对象更严格的封装机制。你不能仅仅是去创建一个对象,然后调用对象内所有的public方法。一个COM对象把所有的public方法分组分装成一个或者多个接口(interface)。如果想用一个方法(Method),你就必须创建一个对象,并且从这个对象获得相应的接口(interface)。这个接口(interface)拥有一组关联的方法来访问这个COM对象的详细的特性。举个例子:接口(interfaceIDirect3DCubeTexture9拥有一些方法来操作立体纹理资源。任何不属于这个接口的方法是不能被访问的。
  • COM对象的创建和C++对象的创建时不一样的。虽然有好几种方法来创建一个COM对象,但是都离不开一种技术COM-Specific。在Microsoft® DirectX® API中,包含一些函数和方法来帮助我们简单的建立一些DirectX对象。
  • 你必须使用COM-Specific技术来控制一个对象的生命周期。
  • COM对象并不学要被明确的调用。虽然COM对象一般都存在于一个DLL中,但是你没有必要显示的调用DLL或者为了使用COM对象而Link一个静态库。每个COM对象有一个唯一的用来创建对象的注册标示符,COM对象会自动的调用合适的DLL
  • COM是用二进制来描述的,COM对象可以被多种语言来写和访问。你不需要知道任何关于COM对象的代码。举个例子:Microsoft Visual Basic®应用程序可以使用用C++写的COM对象。

主要分以下几个方面介绍:

 

1 对象和接口(Objects and Interfaces)

 

理解对象和接口的区别是非常重要的。有时,一个对象可以被它的接口所代替,但是本质上来讲,接口和对象是不可互换的。

  • 一个对象可能暴漏很多个接口。譬如,当所有的对象必须暴漏IUnKnown的时候,他们通常都会至少再暴漏一个接口,或者更多。为了使用特定的方法,你不但要创建一个对象,你还必须获得正确的接口。
  • 多个对象可能会暴漏同样的接口。一个接口是完成一组特定操作的一组方法。接口仅仅定义了方法的语法和他们实现的功能。任何的COM对象都需要通过暴漏一组合适的接口来完成特定的操作。有些接口被高端的抽象只用来被一个对象使用,而其它大部分在不同的环境下都可以被很多对象所使用。最特殊的是IUnKnown接口,它必须被所有的COM对象所暴露。

Note:如果一个对象暴漏一个接口,这个对象就必须支持接口中定义的所有方法。换句话说,你可以调用所有的方法并且你要确信它一定存在。但是怎样实现一个方法的细节每个对象是不一样的。譬如:不同的对象会使用的不同的算法去达到预期的目的。有时一个对象暴露了一个经常使用的接口,但是却只支持一部分方法,这是你仍然能够成功的执行这些方法,但是他们会返回E_NOTIMPL,这时候你就要返回到文档里去看看一个接口是怎么被所有明确的对象实现的。

COM标准需要一旦一个接口定义被发布,它就不能被改变。你不能在一个即存的接口中追加一个新的方法,你必须替换它,创建一个新的接口。当这个接口对有什么样的方法没有限制的时候,一个普遍的做法就是生成下一代的接口,在新的接口中要包含以前的接口的方法,还要包含新的方法。

一个接口具有很多代是很不寻常的。通常来说,所有代的接口本质上实现的任务是相同的, 不同在于实现的细节。经常,一个对象会暴露所有代的接口,为了保证老的应用程序继续使用老的接口,新的应用可以使用接口的新特性的。通常,一个接口族拥有一个名字,然后在后面加上代数。譬如:如果原始的接口叫IMyInterface,那么下两代的接口就叫做IMyInterface2IMyInterface3DirectX也这样命名。

 

2 GUIDs

 

全局唯一标示符是COM编程模式的一个关键部分。最基础的,一个GUID是一个128位的Structure。任何两个GUID都不会被创建为相同的。COM广泛的使用GUID有两个主要的目的:

  • 唯一的识别一个独特的COM对象。被分配给一个COM对象的GUID被叫做类别标示符(CLSID),当你想创建一个关联COM对象的实例时,你可以使用一个CLSID
  • 唯一的识别一个独特的COM接口。被分配给一个COM接口的GUID被叫做接口标示符(IID),当你请求从一个对象要求一个接口的时候,你可以使用IID。一个接口的IID总是同一个,不管是哪个对象暴露出来的。

NOTE为了方便,文档中通常涉及到对象和接口时都用像IDirect3D9这样的描述名字来表示。在文档中,这样的描述方式肯定没有问题,但是,坦白的说,不能保证2个对象或者接口是不是用同一个名字来描述了。唯一不产生这种含糊不清的方法就是用GUID来描述一个对象或是接口。

虽然GUIDs是结构体,但是他们经常被表示成一个等价的字符串,用32个十六进制数来表示,格式是"{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}",每个x表示一个16进制数。

Example:

接口IDirect3D9IID被表示为{1DD9E8DA-1C77-4D40-B0CF-98FEFDFF9512}

因为实际的GUID在使用的时候很笨拙并且容易弄错,所以就用表示相同意义的名字来代替。在你调用某些函数的时候(CoCreateInstance)你可以用这个名字来代替实际的GUID结构体。这些名字通常都以IID_或者CLSID_来作为接口或者对象的描述名。

Example:接口IDirect3D9街口的IID就用IID_IDirect3D9来表示。

 

3 HRESULT Values

 

所有的COM方法都返回一个32位的integer,被称为HRESULT。对于大部分方法来说,HRESULT从本质上来说是包含以下两个重要信息的结构体:

  • 这个方法调用是否成功
  • 这个操作所产生的其他详细信息。

一些方法返回的HRESULT值仅仅是在Winerror.h中定义的标准值,但是方法可以返回附带一些特殊信息的自定的HRESULT值。这些值通常在方法的参考页中被记述。

NOTE:在方法的参考页中所描述的HRESULT列表仅仅是可能被放回的一部分。只有对于这个方法来说是特殊的或者具有特殊意义的标准HRESULT才会被列出来。你应该假定一个方法可以返回任何的HRESULT值,虽然他们没有被明确的记述。

HRESULT通常被用来返回错误信息,但是你不应该认为他们是错误码。事实上标志成功和失败的位和标示详细信息的位是分开保存的,这样HRESULT就可以保存任意的成功和失败码。按照惯例,成功码用S_prefix来定义,错误码用E_prefix来定义。

Example:最常用S_OKS_FAIL分别被用来表示成功和失败。

实际上COM方法会返回很多的成功码或者失败码,这就意味着你必须很小心的处理返回的HRESULT。举个例子,假定一个方法,文档中说当这个方法成功是返回S_OK,没有成功则返回S_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

}

虽然大部分COM方法会返回一个HRESULT值,但是也有少部分方法制返回一个简单数字的HRESULT。从而这些方法显得总是正确的。如果你用SUCCESSDED宏来验证这些方法的返回值,则会发现他们总会返回TRUE。一个通常的例子就是IUnknown::Release方法,这个方法会一个一个减少一个对象的引用个数同时返回现在的引用个数。

 

4 指针的地址(The Address of a Pointer

 

如果你看过一些COM方法的定义,你大概会发现他们都像下面的定义:

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

对于c/c++开发者来讲,通常的指针是非常熟悉的,但是COM通常使用指向指针的指针来表示,这种方法通常被用跟在类型后面的**来定义,并且名字一般都以pp作为前缀。譬如ppReturnedDeviceInterface作为指向IDirect3DDevice9接口的指针的地址。

不像c++,你不能直接的访问一个COM对象的方法,而是要通过获得一个接口的指针来暴楼这个方法。为了调用这个方法,其实本质上说你用了和c++一样的方法。举个例子,

你如果想调用IMyInterface::DoSomething方法,你会用下面的调用过程:

 

IMyInterface *pMyIface;

...

pMyIface->DoSomething(...);

为什么要用指向指针的指针,这是因为你不是直接来创建一个指向接口的指针的。你必须调用像CreateDevice这样的方法来获得一个指向接口的指针,你定义了一个指向预期接口的指针,并且把这个变量的地址传入那个方法。其实就是你传入了指针的地址。当这个方法返回的时候,这个变量就会指向被要求的那个接口,这样你就可以通过这个指针来调用你想调用的这个接口的方法了。

原创粉丝点击