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);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章