com技術內幕--讀書筆記(6)

第6章

在前五章,作者將細節問題隱藏起來。本章主要將討論COM的細節問題--HRESULT,GUID,註冊表,最後介紹了COM庫中一些函數。


HRESULT:

是QueryInterface的返回值。在設計組件的時候,可以用它來返回爭取及錯誤代碼。


HRESULT值實際上是一個可分成3個域的32位值,

HRESULT的低16位(0-15位)是函數返回值;16到30bit位返回的是設備值(操作系統代碼),可以使用HRESULT_FACILITY宏來查看HRESULT值的設備;31bit返回嚴重級別。

WIN32的API中的FormatMessage函數可以將HRESULT錯誤信息打印出來,判斷成功和失敗使用SUCCEEDED和FAILED宏。

用戶還可以自定義跟接口相關的HRESULT返回值,其中設備值部分必須是FACILITY_ITF,使用MAKE_HRESULT宏。還有幾條規則:

(1)不能使用0x0000到0x01FF範圍內的值。這是COM定義的FACILITY_ITY保留值。

(2)不要傳播FACILITY_ITY錯誤代碼。

(3)儘可能使用通用的COM成功和失敗代碼。

(4)避免定義自己的HRESULT,可以在函數中使用一個輸出參數。


HRESULT其實是一個LONG型的值。預定義的HRESULT值在WINERROR.H,打開該文件看了一下。第31bit是一個Severity - indicates success/fail 0 - Success
1 - Fail (COERROR),SUCCEEDED和FAILED宏是根據這個bit來判斷是成功還是失敗。


GUID:

GUID可以用來標識組件和接口的ID。


前幾章使用的IID是一個16字節(128bit)的GUID。GUID是Globally Unique Identifier的簡稱,在COM組件開發過程中主要是爲了保證IID即接口ID的唯一性。Microsoft提供了兩個生成GUID的程序,一個是UUIDGEN.EXE(命令行形式),一個是GUIDGEN.EXE(對話框形式)。

GUID的結構是

#ifndef GUID_DEFINED
#define GUID_DEFINED

typedef struct _GUID {          // size is 16
    DWORD Data1;
    WORD   Data2;
    WORD   Data3;
    BYTE  Data4[8];
} GUID;

#endif // !GUID_DEFINED

在OBJBASE.H文件中,定義了operator==操作符

__inline BOOL operator==(const GUID& guidOne, const GUID& guidOther)
{
    return !memcmp(&guidOne,&guidOther,sizeof(GUID));
}

GUID除了可以唯一的標識接口以外,還可以用來唯一標識組件,稱爲類標識符CLSID。在第六章將會用到COM庫的CoCreateInstance函數就是使用CLSID來將要創建的組件。

IID的引用const IID &可以使用REFIID,CLSID是REFCLSID,GUID是REFGUID(都定義在OBJBASE.H文件中)。


Windows註冊表

對於組件的使用者來說,怎樣找到組件所在的文件,並且能夠正確的調用它們,就要用到註冊表,註冊表中保存了組件所在的文件及其他信息。


CLSID在註冊表中作爲索引在註冊表中發佈包含它的DLL文件的名稱,作爲CoCreateInstance函數的傳入參數,CoCreateInstance使用CLSID作爲關鍵字在註冊表中查找所需的文件名稱。


註冊表中是由許多元素組成的層次結構,每一個元素被稱作一個關鍵字。每一個關鍵字可以包含一系列子關鍵字、一系列命名的值、一個未命名的值。

在此,我們關心CLSID和DLL文件的名稱。

COM使用了註冊表的一個分支HKEY_CLASSES_ROOT,在這個關鍵字下有一個CLSID的子關鍵字,在CLSID關鍵字下列有系統中安裝的所有組件的CLSID,每一個CLSID關鍵字的缺省值是組件有意義的名稱,子關鍵字InproServer32的缺省值是組件所在的DLL文件名稱。如下圖所示



HKEY_CLASSES_ROOT

註冊表HKEY_CLASSES_ROOT關鍵字的開頭,列出的是各種應用程序註冊的文件擴展名。在擴展名之後,有許多其他的名字,大多數是ProgID,ProgID是給某個CLSID指定的容易記憶的名稱,Visual Basic使用ProgID而不是CLSID來標識組件。還有AppID,此關鍵字下子關鍵字的作用是將某個APPID映射爲某個遠程服務器的名稱,將在第10章討論。組件類別,講CATID(組件類別ID)映射爲某個特定的組件類別。Interface,用於講IID映射爲某個接口的相關信息,用於在跨進程使用接口的情況,將在第10章討論。TypeLib,保存接口成員函數使用的參數信息,將在第11章討論。

ProgID

ProgID是爲CLSID起的容易記憶的名字,只是沒辦法保證ProgID的唯一性。ProgID的命名約定<Program>.<Component>.<Version>,<Version>字段可以沒有,ProgID的主要作用是獲取相應的CLSID,ProgID關鍵字的缺省值是組件用戶易記的名稱,它下面有一個CLSID的子關鍵字,缺省值是組件的CLSID。<Version>字段沒有的ProgID也被列在HKEY_CLASSES_ROOT下,除了CLSID子關鍵字,還有一個CurVer的子關鍵字,缺省值是組件當前版本的ProgID。如下圖所示



註冊表的其他細節

(1)CLSIDFromProgID和ProgIDFromCLSID,可以進行CLSID和ProgID之間的轉換。

(2)組件註冊----重要

寫完了組件,怎樣在註冊表中對它進行註冊呢,以方便使用者來調用它。

在包含組件的DLL中需要輸出STDAPI DllRegisterServer()和STDAPI DllUnregisterServer()函數,其中STDAPI在OBJBASE.H中定義爲extern "C" HRESULT __stdcall,使用regsvr32.exe來註冊某個組件的過程,其實就是調用DllRegisterServer函數進行註冊,在DllRegisterServer和DllUnregisterServer中實際上使用WIN32 API的註冊表編輯函數RegCreateKeyEx,RegSetValueEx等來完成組件的註冊或取消(使用Reg函數需要鏈接ADVAPID32.LIB導入庫)。

(3)組件類別

組件類別實際上是一個接口集合,也是用GUID進行識別,只是被稱作CATID。如果某個組件能夠實現該組件類別的所有接口,它就可以是該組件類別的一個成員。組件的開發人員不需要自己完成將組件加入組件類別的註冊工作,Windows系統的Component Category Manager會完成這個工作。

(4)OleView

Win32 SDK提供的OleView提供了查看組件的另外一種方式。


COM庫函數

COM組件的使用者和創建者,都需要使用一些相同的操作,例如對使用者來說,用CLSID來調用組件的函數等等。這些都在COM庫中實現,統一用戶的調用方法。


COM庫函數在OLE32.DLL中實現

(1)初始化

除了CoGetMalloc外,在使用COM庫函數之前,必須調用CoInitialize函數來初始化,當不使用時,調用CoUninitialize函數。這兩個函數的原型是HRESULT CoInitialize(void *reserved)和void CoUninitialize(),reserved參數必須爲NULL。對於每個進程,COM庫函數只需初始化一次。OLE是建立在COM庫基礎上的,當需要對類型庫、剪貼板、拖放、ActiveX文檔、自動化及ActiveX控件的支持時,需要調用OleInitialize及OleUninitialize函數。

(2)內存管理

任務內存分配器,考慮到了多線程同步的問題,可以再多線程應用程序中使用。

CoGetMalloc返回IMalloc接口,IMalloc接口的Alloc,Free函數來分配,釋放內存。更方便的方式是使用void* CoTaskMemAlloc(ULONG cb //size in bytes)和void CoTaskMemFree(void* pv)。

(3)字符串轉化成GUID

StringFromCLSID,StringFromIID,StringFromGUID2,CLSIDFromString,IIDFromString,可以完成字符串與GUID之間的轉換。詳細的用法,查看MSDN。









發佈了34 篇原創文章 · 獲贊 9 · 訪問量 19萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章