DirectX8.1 01-使用COM

这是我翻译的DirectX8.1SDK文档,第一次翻译,有些词汇翻译的不太准。

1.  什么是COM对象

COM对象同C++对象的不同:

A.            COM对象比C++对象有强制性的更严格的封装。COM对象的方法被一个或多个接口组织起来。要使用一个方法,必须创建一个对象并且从对象中得到合适的接口。

B.             COM对象不像C++对象那样创建。有几种方式创建COM对象,但都使用了特定的COM技术。

C.            必须通过特定的COM技术控制COM对象的生存期。

D.            COM对象不需要显式地加载。

E.             COM是一种二进制规范。

 

 

对象与接口:

A. 一个对象可以展示出任意数目的接口。

B. 多个对象可能展示出同样的接口。

 

 

COM对象要求接口的定义在其被发布后就不能被改变。

 

 

GUIDsCOM大量使用GUID是为了:

A. 标志一个唯一的特定COM对象。

B.  标志一个唯一的特定接口。

 

 

HRESULT,所有的COM方法都返回一个32位整数HRESULTHRESULT中包含了两个信息:

A. 方法成功还是失败。

B. 方法提供的操作的结果的详细信息。

应该用SUCCESS / FAILED 宏来进行方法返回值的判断。

 

 

指针的地址

IDirect3DDevice8** ppReturnedDeviceInterface

不同于C++,你不能直接访问COM对象方法。取而代之,应该取得一个展示了该方法的接口指针。

二重指针的原因是你不能直接创建接口指针。必须调用某种Create方法取得接口指针。

声明一个变量指向该接口,并将改变量的地址传递给Create方法。

 

 

2.  创建COM对象

有几种方法创建COM对象,最常用的两种是:

A.            通过给函数CoCreateInstance传递对象的CLSID直接创建。该函数创建一个对象的实例,并且返回所指定的接口指针。

B.             通过调用DirectX创建对象的方法或函数间接创建。该方法创建一个对象并且返回对象上的一个接口指针。如果通过这种方法创建对象的话,通常不能指定返回哪个接口指针。

 

 

在创建对象之前,必须调用CoInitializeCOM初始化。如果间接创建对象,由对消创建方法处理初始化。如果需要通过CoCreateInstance创建对象,则必须显式调用CoInitialize。当结束的时候,必须通过调用CoUninitialize进行卸载(uninitialize)。如果调用了CoInitialize则必须调用CoUninitialize进行匹配。典型地,应用程序在启动过程中显式初始化COM,并在清理过程中卸载COM

      

       直接创建:

               IDirectPlay8Peer* g_pDP = NULL;

               ...

               CoInitialize(NULL );

               ...

               hr = CoCreateInstance( CLSID_DirectPlay8, NULL, CLSCTX_INPROC_SERVER,

                                        IID_IDirectPlay8Peer, (LPVOID*) &g_pDP);

               

               if (FAILED (hr)) 

               {

                            MessageBox (NULL, TEXT ("Failed Creating IDirectPlay8Peer. "),

                 TEXT ("DirectPlay Sample"), MB_OK | MB_ICONERROR);

                               return FALSE;

               }

      

       间接创建:

        IDirect3DDevice8 *g_pd3dDevice = NULL;

               ...

               if (FAILED (g_pD3D->CreateDevice(D3DADAPTER_DEFAULT,

                                 3DDEVTYPE_HAL,

                                 hWnd,

                                 D3DCREATE_SOFTWARE_VERTEXPROCESSING,

                                 &d3dpp,

                                 &g_pd3dDevice)))

               return E_FAIL;

 

 

3.  使用COM接口

当对象创建后,创建函数返回一个接口的指针。可以通过这个指针访问接口的方法。语法上与通过指针访问C++方法一样。

               IDirectPlay8Peer* g_pDP = NULL;

               ...

               CoInitialize (NULL);

               ...

               hr = CoCreateInstance ( CLSID_DirectPlay8, NULL, CLSCTX_INPROC_SERVER,

                               IID_IDirectPlay8Peer, (LPVOID*) &g_pDP);

hr = g_pDP->Initialize( NULL, DirectPlayMessageHandler, 0 );

 

 

       请求另外的接口

       在许多时候,从创建方法得到的接口指针正好是你所需要的。事实上,从一个对象只导出一个IUnknown以外的接口并不常见。许多对象导出多个接口,你有可能需要其中的一些。如果你需要的不仅仅是从创建方法返回的那一个接口的话,并不需要创建一个新对象。取而代之的是使用对象的IUnkonwn::QueryInterface方法请求其他的接口指针。

如果使用CoCreateInstance创建对象,可以申请一个IUnknown接口指针,并通过调用IUnkonwn::QueryInterface申请需要的接口。然而,这种方法当只需要一个接口的时候并不方便,并且在创建不允许指定返回特定指针的时候一点也没有用。实际上,并不需要显式的包含一个IUnknown接口指针,因为所有COM接口都继承了或扩展了IUnknown接口。

扩展一个接口类似于继承一个C++类。子接口展示了所有父接口的方法,并且加上了一个或多个自己的方法。事实上,经常看到用“继承自”代替“扩展”。需要记住的是继承是内嵌在对象中的。你的程序不能继承或扩展一个对象的接口。然而,可以用一个子接口调用子或父的方法。

因为所有的接口都是IUnknown的子接口,你可以用已经拥有的对象的任何一个接口调用QueryInterface。这个时候,必须提供所申请的接口的IID以及一个接口指针的地址。

               IDirectSoundBuffer8* pDSBPrimary = NULL;

               IDirectSound3DListener8* pDSListener;

               ...

               if(FAILED(hr = g_pDS->CreateSoundBuffer( &dsbd, &pDSBPrimary, NULL )))

                 return hr;

 

 

               if(FAILED(hr = pDSBPrimary->QueryInterface(IID_IDirectSound3DListener8,

                                           (LPVOID *)&pDSListener)))

               return hr;

4.  管理一个COM对象的生存期

当创建好一个对象时,系统分配给必要的内存资源。当不再需要对象时,应该将其销毁掉(destroy)。系统就能将那些内存用在别的地方。对C++对象而言,可以直接通过newdelete操作符控制对象的生存期。COM不允许直接创建或销毁对象。原因是同一个对象可能被许多程序使用。如果一个程序破坏了一个对象,其他的程序可能就会失败。取而代之的是COM用一套引用计数的系统控制对象的生存期。

一个对象的引用计数是该对象的接口被申请的次数。每当一个接口被申请的时候,就增加引用计数。当应用程序不再需要接口的时候释放该接口,就减少引用计数。只要引用计数大于零,对象就保留在内存中。当引用计数为零时,对象销毁自身。你无需知道一个对象的引用计数。只要适当的取得或释放接口,对象就有合适的生存期。

注意:适当的处理引用计数是COM编程中很重要的一部分。处理得不好很容易导致内存泄露。COM程序员最常见的错误就是没有释放接口。这样的话,引用计数永远不会为零,对象就会一直驻留在内存中。

 

 

增加或减少引用计数

当取得一个新的接口指针的时候,必须通过调用IUnknown::AddRef来增加引用计数。然而,你的应用程序无需调用这个方法。当通过一个对象创建方法,或是通过IUnknown::QueryInterface取得对象接口指针时,对象会自动增加引用计数。然而,当你通过别的方法创建接口指针的时候,比如说拷贝一个接口指针,必须显式调用IUnknown::AddRef。否则,当你释放原始指针的时候,尽管你可能仍需使用拷贝指针,对象也可能被销毁。

IDirectSoundBuffer8* pDSBPrimary = NULL;

IDirectSound3DListener8* pDSListener = NULL;

IDirectSound3DListener8* pDSListener2 = NULL;

...

//Create the object and obtain an additional interface.

//The object increments the reference count.

if(FAILED(hr = g_pDS->CreateSoundBuffer( &dsbd, &pDSBPrimary, NULL )))

  return hr;

             

if(FAILED(hr=pDSBPrimary->QueryInterface(IID_IDirectSound3DListener8,

                                           (LPVOID *)&pDSListener)))

  return hr;

             

//Make a copy of the IDirectSound3DListener8 interface pointer.

//Call AddRef to increment the reference count and to ensure that

//the object is not destroyed prematurely

pDSListener2 = pDSListener;

pDSListener2->AddRef();

...

//Cleanup code. Check to see if the pointers are still active.

//If they are, call Release to release the interface.

if(pDSBPrimary != NULL)

{

  pDSBPrimary->Release();

  pDSBPrimary = NULL;

}

if(pDSListener != NULL)

{

  pDSListener->Release();

  pDSListener = NULL;

}

if(pDSListener2 != NULL)

{

  pDSListener2->Release();

  pDSListener2 = NULL;

}

             

5.  使用C访问COM对象

尽管C++是最常用的COM编程语言,你仍然可以使用C访问COM对象。通过相对直接但是需要某些更加复杂的语法。

A.            在所有方法的参数列表的开始要添加一个额外的参数。该参数必须设成接口指针。

B.             必须显式引用对象的vtable

每一个COM对象都有一个vtable,包含了对象展示的方法指针的列表。一个接口指针指向vtable中合适的位置,vtable中依次包含了指向所调用的方法的指针。Vtable在文档的其他地方并没有被提到,因为对C++而言,vtable是不可见的。然而,如果要通过C调用COM方法,必须包含一个显式引用vtable的间接层。

 

 

C++:

g_pDP->Initialize( NULL, DirectPlayMessageHandler, 0 );

C:

g_pDP->lpVtbl->Initialize(g_pDP,NULL, DirectPlayMessageHandler, 0);

 

 

某些组件在其头文件中包含了一些宏定义,用于决定正确的调用约定。详情请参阅“使用宏调用DirectX COM方法”。

 

 

6.  使用宏调用DirectX COM对象

许多Microsoft® DirectX®接口都为每个方法定义了宏,使在应用程序使用这些方法更方便。可以在接口声明的同一个头文件中找到这些宏的定义。这些宏设计成被CC++程序使用。要使用C++宏,必须定义_cplusplus。否则,将使用C宏。宏的语法在两种语言中是一样的,但是在头文件中包含了扩展成合适的调用约定的独立的宏定义集。

       #if !defined(__cplusplus) || defined(CINTERFACE)

      

...

#define IDirect3D8_GetAdapterIdentifier(p,a,b,c) (p)->lpVtbl->GetAdapterIdentifier(p,a,b,c)

...

#else

...

#define IDirect3D8_GetAdapterIdentifier(p,a,b,c) (p)->GetAdapterIdentifier(a,b,c)

...

#endif

要使用这些宏中的一个,首先必须取得关联的接口的指针。宏的第一个参数必须设置为该指针。其他的参数影射到方法的参数。宏的返回值为方法所返回的HRESULT值。

pD3DIDirect3D8接口指针:

hr = IDirect3D8_GetAdapterIdentifier(pD3D,

                                     Adapter,

                                     dwFlags,

                                     pIdentifier);

 

 

7.  DirectX COM文档约定

根据约定,在本文档中提到的COM方法及接口都是通过相应的C++类名来引用。因此,接口IDirectPlay8PeerInitialize方法为IDirectPlay8Peer::Initialize。这种约定的主要原因是,不同的接口可能导出同样名字但是功能及语法完全不同的方法。比如,许多接口都有InitInitialize方法,但是功能以及参数可能十分不同。使用C++类名是一种唯一标识方法的捷径。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值