DirectX 中的COM 使用技术

COM 是一个面向对象编程模型,已被众多的应用程序所使用。DirectX 也是一种COM 对象。所有DirectX开发者需要知道基本的COM原则,虽然COM是复杂的胆识使用COM却是很直白的。 这里两种不同COM编程方法:使用已经存在的COM 对象. 和使用C++ 对象几乎一样。实现自己的COM对象,这个复杂的并且很苛刻的任务I许多DirectX 应用程序需要的仅仅使用COM对象,不必实现自己的COM 对象,换句话说就是DirectX 要去确保使用简单这节提供了一个使用COM的大概流程主要是给COM 初学者的,一个更详细的讨论如何使用COM 对象,或者实例你自己的COM对象。 COM 对象是一个黑盒子中提供了操作一个或者多个任务的能力。他们通常被作为动态连接库实现,像一个常规的DLL一样,COM 对象通过向应用程序暴露方法的这种方式来支持应用程序完成任务。应用程序与COM对象的交互在一定程度上和C++ 对象一致。她们不同之处: COM对象是一种比较严格的C++ 对象封装,一个COM对象公有方法一般分为几个接口,为了使用这些方法,你必须创建对象和提供适当的接口,一个接口包含了对相关方法的访问的集合,例如: IDirect3DCubeTexture9 这个接口包含的方法能够使用去渲染立体材质资源。 COM 对象不同与C++ 对象的创建过程。可以有几种方式去创建COM对象但是COM 技术. Microsoft DirectX  (API)包含了大量的有用函数和方法这些可以简化DirectX 对象的创建。你必须使用COM 技术文档中的技术去控制一个对象的生存期。COM 对象不必去显示的加载,COM 对象典型的被封装在DLL中,不管怎样,你不必显示的加载DLL,或者静态连接库。每个COM 对象都有一个唯一的注册标识,可以使用它去创建一个对象,COM 会自动的加载DLL的。COM 符合二进制规范. 各种语言都可以写COM 对象,你不必知道对象的源码,例如VB使用了VC 写的COM对象。 文档包含下列章节.Objects and Interfaces GUIDs HRESULT Values The Address of a PointerObjects andInterfaces这节主要是了解对象和接口的区别。 对象可以暴露任何数量的接口 。所有的对象必须暴露 IUnknown 这个接口, 但是通常至少暴露一个额外的接口,通过这个可以获得其它接口。为了使用具体方法,禁止仅仅通过创建对象来获得,应该使用接口来获得。 很多对象都会暴露相同的接口。一个接口是一组指定操作的集合,接口仅仅指定方法的语法和一般的功能,COM对象需要去支持一个具体的操作集合,一些接口有高度的特殊化,并且是一个对象暴露出来的。另一些是多个对象暴露出来的,最极端的情况是 IUnknown是所有对象都要暴露的接口。Note  如果一个对象暴露一个接口,那么它必须支持这个接口的所有方法,换句话说就是你调用接口任何方法,它都是存在的。 一个具体方法实现细节可以不同,例如:不同的对象是用不同的算法去获得最终的结果,并没有保证一个方法以何种方式来支持这个功能,很多时候,一个对象暴露一个一般情况使用的接口,但是仅仅需要的是这个接口中的一个部分,你仍然可以使用这些接口方法,但是他们将返回NOTIMPL。 你应该查阅文档关于接口在实际对象中实现了几个方法。 COM 标准要求一个接口定义的方法在公开发布之后,就不能改变了,你不能添加一个新的方法到一个已经存在的接口,你必须创建一个新的接口来替代这种方式。通常的做法是下一代接口包含所有旧接口的方法,并将新的方法添加进去。一个接口有多代那个是很正常的,一般地所有代都含有全部的任务,但是他们的不同细节上。通常,一个对象将导出每一代的接口的方法。 这样做允许比较老的程序可以继续使用对象叫老的接口。新的应用程序可以利用新接口的特性。典型的,一个接口的家族,应该有同样的名字,加上一个整形数字来表示它是那一代,例如:原始的接口命名为IMyInterface, 那么下两代接口的命名就是 IMyInterface2 和 IMyInterface3. DirectX 一般使用版本号作为继承标记。GUIDs全局唯一标识(GUIDs) 是COM 编程模型的一个重点,.一个 GUID 是一个128 bit 的结构体。GUID的产生方法确保了不会出现两个一样的GUID。  COM 使用GUIDs 在两个方面: 用来唯一的表识一个COM对象。一个GUID 被赋予给一个COM 对象,那么就叫做CLSID,当然你可以使用 CLSID 当你想要创建一个COM对象。用来唯一标识一个具体COM 接口。GUID 关联一个接口的时候被叫做接口标识符。你可以使用接口表示符请求一个具体的接口,不同的对象可以导出相同表示符的接口。Note  为了方便,文档一般涉及到的对象和接口都是以名字来描述的例如IDirect3D9. 在文档的内容中基本上不存在混淆的情况, 严格的讲,不能保证名字是不相同的。虽然 GUIDs 是一个结构体,但是他们通常表示为一个等价的字符串, 一般格式的GUIDS是一个32个16 进制数 8-4-4-4-12., "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}," 这里的X 对应一个十六进制的数, 例如IDirect3D9 的IID字符串是: {1DD9E8DA-1C77-4D40-B0CF-98FEFDFF9512} 因为实际上GUID 使用上还是有些笨重了,通常是用一个等价的名字来获取对象,你可以在调用CoCreateInstance的时候,使用名字来代替。习惯将名字转成预先准备的IID或者CLSID, 例如IDirect3D9 的 IID是 IID_IDirect3D9.HRESULT Values所有的COM 方法都会返回一个32 bit 的整数,这个整数的类型是HRESULT,包含了两个部分的信息:这个方法成功或者失败。这个方法对操作的支持信息。一些方法返回一个HRESULT 的值,这些值定义在 Winerror.h 中。方法可以自由的返回客户自定的HRESULT 值,这些值描述在 方法相关的文档中。 The Address of aPointer如果你使用了COM 方法参考,你大概浏览过:HRESULT CreateDevice(..., IDirect3DDevice9**ppReturnedDeviceInterface);这里的指针十分类似C++ 的指针, ppReturnedDeviceInterface 是一个典型的地址引用指针来获得IDirect3DDevice9接口。不象 C++, 你不能直接访问COM 对象的方法。取而代之的是你必须获得一个指向一个导出接口的指针,来调用相应的方法, 例如: IMyInterface *pMyIface;...pMyIface->DoSomething(...);Creating a COM Object 有多种方式去创建一个组建对象,一般使用两种通用的方式来创建DirectX 对象:  直接方式:通过传递一个对象类的标识符CLSID, 给CoCreateInstance 函数,这个函数就会创建一个实例,并且返回一个你指定的接口指针。 间接方式: 通过调用一个DirectX方法或者函数来创建一个对象,并且返回一个接口的指针,当你以这种方式去创建一个对象的时候,你通常能指定你要的接口。 在你创建对象之前,COM 必须进行初始化,通过调用CoInitialize 函数。如果你间接的创建对象,那个对象的创建方法就会自己完成创建任务。如果使用 CoCreateInstance, 你必须显示调用 CoInitialize。当程序退出的时候,必须释放COM 资源,通过调用 CoUninitialize来完成。CoInitialize 和 CoUninitialize必须成对调用。一般的应用程序要显示的初始化COM 并且在任务结束的时候,必须释放资源。使用CoCreateInstance创建一个新的COM对象,必须要有对象的CLSID,这个CLSID 是发布的话,那么必然在参考文档或者头文件中有说明。反之不是公开发布的话,你不能直接创建一个对象。CoCreateInstance 这个函数有五个参数.对于使用DirectX Com 对象你可以直接设置参数如下: rclsid. 这个参数是你想创建的对象的CLSID。pUnkOuter. 这个参数设置为NULL, 这个参数是COM在聚合对象的时候才会使用。(主要是完成接口的传递性) dwClsContext. 设置这个参数为 CLSCTX_INPROC_SERVER. 这种设置表明这个对象被实现在DLL中,并且在运行的时候作为你的应用程序的一部分。 riid. 这个参数设置为IID,(接口ID)。 这个函数将创建一个对象,并将你要的接口通过 ppv 参数返回出来. ppv.  设置这个参数为一个接口指针的地址。通过PPV 将返回 riid 所对应的接口。这个变量应该声明为请求的接口指针,然后将其转型为 (LPVOID *). 通常间接的方法创建对象比较简单。当你间接的创建对象的时候,你不能选择返回那个接口。例如IDirect3D9::CreateDevice这个方法可以创建一个设备对象,设备对象可以是显示适配器,它将返回一个指向IDirect3DDevice9接口指针,前四个参数提是要为你创建的对象提供的信息,第五个参数返回接口的指 针。  IDirect3DDevice9 *g_pd3dDevice = NULL; ... if( FAILED( g_pD3D->CreateDevice(D3DADAPTER_DEFAULT,                                                                                3DDEVTYPE_HAL,                                hWnd,                                 D3DCREATE_SOFTWARE_VERTEXPROCESSING,                                 &d3dpp,                                &g_pd3dDevice )))   return E_FAIL; Using COM Interfaces COM 对象创建以后,创建方法将返回一个接口指针,你可以使用接口指针去访问任何接口方法,语法同C++ 方法的访问语法。 Requesting Additional Interfaces很多情况下,你重创建方法上获得的接口指针,也许仅仅是你所需要的接口中的一个,事实上,一个对象通常只会导出除了IUnknown 以外的一个接口,还有很多对象都回导出多个接口,你可以使用指针指向这些接口中的任何部分。如果你想获得更多接口,不必创建一个新的对象,相反你可以请求对象的IUnknown::QueryInterface方法来获得另一个接口。使用 CoCreateInstance创建对象以后,就可以使用 IUnknown 接口并且调用 IUnknown::QueryInterface  去查询你需要的接口。接口扩展类似于C++ 的继承, 子接口将导出所有父接口的方法,并加上自己的方法,事实上,通常看到的继承被替换为扩展。你自己的应用程序不能继承或者扩展对象的接口,你可以使用子接口去调用这些方法。所有的接口都是 IUnknown 的子接口,你可以使用任何指向对象的指针调用QueryInterface去查询接口。 这样做的时候,你要提供IID,例如:下面的代码片段IDirectSound9::CreateSoundBuffer 去创建一个主缓冲区的对象,这个对象导出了几个接口, CreateSoundBuffer 方法可以返回一个 IDirectSoundBuffer9 接口. 下面的代码使用IDirectSoundBuffer9 接口调用 QueryInterface 请求IDirectSound3DListener9 接口。 IDirectSoundBuffer9* pDSBPrimary = NULL; IDirectSound3DListener9* pDSListener; .. if(FAILED(hr = g_pDS->CreateSoundBuffer( &dsbd,&pDSBPrimary, NULL )))  return hr; if(FAILED(hr =pDSBPrimary->QueryInterface(IID_IDirectSound3DListener9,                                          (LPVOID *)&pDSListener)))   return hr; Managing a COM Object's Lifetime 当一个对象被创建的时候,系统分配必要的内存给COM对象,当一个对象不再继续使用的时候,那么应该销毁内存空间。C++ 对象你可以使用new 和delete 来控制对象的生存期,COM 不能直接去创建和销毁对象,原因是这个对象可能被多个应用程序所使用,如果你一旦销毁对象,那么其他应用程序产生访问失败。COM 使用了引用计数来控制一个对象生命期。一个对象的引用计数表示的一个对象被请求了多少次,每次查询一个接口,引用计数就会自动加1,应用程序在Release一个接口的时候,引用计数自动减1,当所有的接口不再使用的时候,引用计数就为0,引用计数大于0 的时候,对象存在,等于零的时候对象销毁内存。Important引用计数是COM 编程中的重要部分,失败的情况可能产生内存泄漏,错误的使用可以到置对象的引用计数不为零,那么在不使用的时候,对象以然存在在内存中。Incrementing andDecrementing the Reference Count不管什么时候 ,你获得一个新的指针都要用IUnknown::AddRef.来增加自己的引用计数。你的应用程序不必常常去调用这个方法,如果你从IUnknown::QueryInterface方法获得接口的话,这个对象已经自动增加了引用计数。如果你以其他方式,如复制一个指针的时候,你必须显示的条用IUnknown::AddRef. 否则你在释放原始接口指针的时候,那个对象就会被销毁了。你必须释放所有的接口指针,不管是和对象的引用计数相关与否。当你不再使用接口指针的时候,调用IUnknown::Release去减少应用计数。 通常的做法是在初始化和结束将指针都置为NULL。 下面的代码讲述了如何使用引用计数: IDirectSoundBuffer9* pDSBPrimary = NULL; IDirectSound3DListener9* pDSListener = NULL; IDirectSound3DListener9* 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_IDirectSound3DListener9,                                        (LPVOID*)&pDSListener)))  return hr;  //Make a copy of the IDirectSound3DListener9 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; } Using Macros to Call DirectX COM Methods DirectX 的许多接口, 每个方法都有定义的宏. 这些宏简化程序的方法使用,你可以找到这些宏的定义在接口的头文件中。这些宏可以以C或C++ 的方式来使用,你如果定义了 _cplusplus. 就是C++ ,否则使用的是C macro. 例如IDirect3D9::GetAdapterIdentifier方法: ... #define IDirect3D9_GetAdapterIdentifier(p,a,b,c) (p)->lpVtbl->GetAdapterIdentifier(p,a,b,c) ... #else ... #define IDirect3D9_GetAdapterIdentifier(p,a,b,c)(p)->GetAdapterIdentifier(a,b,c) ...#endif 为了使用这个宏,你必须首先获得一个指针,然后将其作为宏的第一个参数,其它参数和方法的参数一致。 宏的返回值就是 HRESULT ,就是方法的返回值。例如:  hr = IDirect3D9_GetAdapterIdentifier(pD3D,                                     Adapter,                                               dwFlags,                                            pIdentifier);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章