com學習記錄(一)

2020-08-12 10:52:35

參考:https://baike.baidu.com/item/com%E7%BC%96%E7%A8%8B/833430?fr=aladdin

​ COM即元件物件模型(Component Object Model)

​ COM是一種跨應用和語言共用二進制程式碼的方法

​ COM通過定義二進制標準解決了這些問題,即COM明確指出二進制模組(DLLs和EXEs)必須被編譯成與指定的結構匹配。這個標準也確切規定了在記憶體中如何組織COM物件。COM定義的二進制標準還必須獨立於任何程式語言(如C++中的命名修飾)。一旦滿足了這些條件,就可以輕鬆地從任何程式語言中存取這些模組。由編譯器負責所產生的二進制程式碼與標準相容。這樣使後來的人就能更容易地使用這些二進制程式碼。

​ 在記憶體中,COM物件的這種標準形式在C++虛擬函式中偶爾用到,所以這就是爲什麼許多COM程式碼使用C++的原因。但是記住,編寫模組所用的語言是無關的,因爲結果二進制程式碼爲所有語言可用。

​ 此外,COM不是Win32特有的。從理論上講,它可以被移植到Unix或其它操作系統。

​ COM伺服器是包含了一個或多個coclass的二進制(DLL或EXE)。

​ GUID(諧音爲「fluid」,意思是全球唯一標示符——globally unique identifier)是個128位元的數位。它是一種獨立於COM程式語言的標示方法。每一個介面和coclass有一個GUID。因爲每一個GUID都是全球唯一的,所以避免了名字衝突(只要你用COM API建立它們)。有時你還會碰到另一個術語UUID(意思也是全球唯一標示符——universally unique identifier)。UUIDs和GUIDs在實際使用時的用途是一樣的。 類ID或者CLSID是命名coclass的GUID。介面ID或者IID是命名介面的GUID。

  • 建立一個新物件:

    C++中,用new操作符,或者在棧中建立物件。

    COM中,呼叫COM庫中的API。

  • 刪除物件*

    C++中,用delete操作符,或將棧物件踢出。

    COM中,所有的物件保持它們自己的參照計數。呼叫者必須通知物件什麼時候用完這個物件。當參照計數爲零時,COM物件將自己從記憶體中釋放。

  • 建立COM物件

    爲了建立COM物件並從這個物件獲得介面,必須呼叫COM庫的API函數,CoCreateInstance()。

其原型如下:

HRESULTCoCreateInstance
(
    REFCLSID 	rclsid,
    LPUNKNOWN 	pUnkOuter,
    DWORD 		dwClsContext,
    REFIID 		riid,
    LPVOID 		*ppv
);
// rclsid:coclass的CLSID,例如,可以傳遞CLSID_ShellLink建立一個COM物件來建立快捷方式。
	
// pUnkOuter:這個參數只用於COM物件的聚合,利用它向現有的coclass新增新方法。參數值爲null表示不使用聚合。
	
// dwClsContext:表示所使用COM伺服器的種類。 本文使用的是最簡單的COM伺服器,一個進程內(in-process)DLL, 所以傳遞的參數值爲CLSCTX_INPROC_SERVER。注意這裏不要隨意使用CLSCTX_ALL(在ATL中,它是個預設值, 因爲在沒有安裝DCOM的Windows95系統上會導致失敗。
	
// riid:請求介面的IID。例如,可以傳遞IID_IShellLink獲得IShellLink介面指針。
	
// ppv:介面指針的地址。

​ COM庫通過這個參數返回請求的介面。 當你呼叫CoCreateInstance()時,它負責在註冊表中查詢COM伺服器的位置,將伺服器載入到記憶體,並建立你所請求的coclass範例。以下是一個呼叫的例子,建立一個CLSID_ShellLink物件的範例並請求指向這個物件IShellLink介面指針。

HRESULThr;
IShellLink	*pISL;					  
hr = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (void**)&pISL);
// CLSID_ShellLink:			coclass的CLSID
// NULL:					不是用聚合
// CLSCTX_INPROC_SERVER:	伺服器型別
// IID_IShellLink: 			介面的IID
// (void**)&pISL:			指向介面的指針
if(SUCCEEDED(hr))
{
	//用pISL呼叫方法
}
else
{
	//不能建立COM物件,hr爲出錯程式碼
}

​ 首先宣告一個接受CoCreateInstance()返回值的HRESULT和IShellLink指針。呼叫CoCreateInstance()來建立新的COM物件。如果hr接受到一個表示成功的程式碼,則SUCCEEDED宏返回TRUE,否則返回FALSE。FAILED是一個與SUCCEEDED對應的宏用來檢查失敗程式碼。刪除COM物件

​ 前面說過,你不用釋放COM物件,只要告訴它們你已經用完物件。IUnknown是每一個COM物件必須實現的介面,它有一個方法,Release()。呼叫這個方法通知COM物件你不再需要物件。一旦呼叫了這個方法之後,就不能再次使用這個介面,因爲這個COM物件可能從此就從記憶體中消失了。

​ 如果你的應用程式使用許多不同的COM物件,因此在用完某個介面後呼叫Release()就顯得非常重要。如果你不釋放介面,這個COM物件(包含程式碼的DLLs)將保留在記憶體中,這會增加不必要的開銷。如果你的應用程式要長時間執行,就應該在應用程式處於空閒期間呼叫CoFreeUnusedLibraries() API。這個API將解除安裝任何沒有明顯參照的COM伺服器,因此這也降低了應用程式使用的記憶體開銷。

  • 釋放
//像上面一樣建立COM物件,然後,
if(SUCCEEDED(hr))
{
	//用pISL呼叫方法
	//通知COM物件不再使用它
	pISL->Release();
}

基本介面

​ 每一個COM介面都派生於IUnknown,每個COM物件都實現IUnknown

​ IUnknown 有三個方法:
​ AddRef() —— 通知COM物件增加它的參照計數。如果你進行了一次介面指針的拷貝,就必須呼叫一次這個方法,並且原始的值和拷貝的值兩者都要用到。在本文的例子中沒有用到AddRef()方法;
​ Release() —— 通知COM物件減少它的參照計數。參見前面的Release()範例程式碼段;
​ QueryInterface() —— 從COM物件請求一個介面指針。當coclass實現一個以上的介面時,就要用到這個方法;

​ 當你用CoCreateInstance()建立物件的時候,你得到一個返回的介面指針。如果這個COM物件實現一個以上的介面(不包括IUnknown),你就必須用QueryInterface()方法來獲得任何你需要的附加的介面指針。
​ QueryInterface()的原型:HRESULT IUnknown::QueryInterface ( REFIID iid, void** ppv );

  • iid:所請求的介面的IID。
  • ppv:介面指針的地址,QueryInterface()通過這個參數在成功時返回這個介面。
HRESULT hr;

IPersistFile *pIPF;

hr = pISL->QueryInterface ( IID_IPersistFile, (void**) &pIPF );

​ 然後使用SUCCEEDED宏檢查hr的值以確定QueryInterface()的呼叫情況,如果成功的話你就可以象使用其它介面指針那樣使用新的介面指針,pIPF。但必須記住呼叫pIPF->Release()通知COM物件已經用完這個介面。

​ WideCharToMultiByte()你可以用WideCharToMultiByte()將一個Unicode串轉換成一個ANSI串。此函數的原型如下:

​ intWideCharToMultiByte(UINT CodePage,

​ DWORD dwFlags,

​ LPCWSTR lpWideCharStr,

​ int cchWideChar,

​ LPSTR lpMultiByteStr,

​ int cbMultiByte,

​ LPCSTR lpDefaultChar,

​ LPBOOL lpUsedDefaultChar

);

  • CodePage:Unicode字元轉換成的內碼表。你可以傳遞CP_ACP來使用當前的ANSI內碼表。內碼表是256個字元集。字元0——127與ANSI編碼一樣。字元128——255與ANSI字元不同,它可以包含圖形字元或者讀音符號。每一種語言或地區都有其自己的內碼表,所以使用正確的內碼表對於正確地顯示重音字元很重要。

  • dwFlags:dwFlags 確定Windows如何處理「複合」 Unicode字元,它是一種後面帶讀音符號的字元。如è就是一個複合字元。如果這些字元在CodePage參數指定的內碼表中,不會出什麼事。
    否則,Windows必須對之進行轉換。傳遞WC_COMPOSITECHECK使得這個API檢查非對映覆合字元。
    傳遞WC_SEPCHARS使得Windows將字元分爲兩段,即字元加讀音,如e`。
    傳遞WC_DISCARDNS使得Windows丟棄讀音符號。
    傳遞WC_DEFAULTCHAR使得Windows用lpDefaultChar參數中說明的預設字元替代複合字元。
    預設行爲是WC_SEPCHARS。

  • lpWideCharStr: 要轉換的Unicode串。

  • cchWideChar l pWideCharStr: 在Unicode 字元中的長度。通常傳遞-1,表示這個串是以0x00結尾。

  • lpMultiByteStr: 接受轉換的串的字元緩衝

  • cbMultiByte: lpMultiByteStr的位元組大小。

  • lpDefaultChar: 可選——當dwFlags包含WC_COMPOSITECHECK | WC_DEFAULTCHAR並且某個Unicode字元不能被對映到同等的ANSI串時所傳遞的一個單字元ANSI串,包含被插入的「預設」字元。可以傳遞NULL,讓API使用系統預設字元(一種寫法是一個問號)。

  • lpUsedDefaultChar: 可選——指向BOOL型別的一個指針,設定它來表示是否預設字元曾被插入ANSI串。可以傳遞NULL來忽略這個參數。

//假設已經有了一個Unicode串wszSomeString...
charszANSIString[MAX_PATH];
WideCharToMultiByte(CP_ACP,//ANSI內碼表
WC_COMPOSITECHECK,//檢查重音字元
wszSomeString,//原Unicode串
-1,//-1意思是串以0x00結尾
szANSIString,//目的char字串
sizeof(szANSIString),//緩衝大小
NULL,//肥預設字串
NULL);//忽略這個參數

​ wcstombs()這個CRT函數是WideCharToMultiByte()的簡化版,但它總結了WideCharToMultiByte()的呼叫,所以最終結果是一樣的。

size_t wcstombs(char *mbstr, constwchar_t *wcstr, size_t count);
// mbstr:接受結果ANSI串的字元(char)緩衝。
// wcstr:要轉換的Unicode串。
// count:mbstr參數所指的緩衝大小。

單介面COM物件

初始化COM庫。 (Initialize);

建立一個與活動桌面互動的COM物件,並取得IActiveDesktop介面;

呼叫COM物件的GetWallpaper()方法;

如果GetWallpaper()成功,則輸出/顯示牆紙檔名;

釋放介面(Release());

收回COM庫(Uninitialize);

WCHARwszWallpaper[MAX_PATH];
CStringstrPath;
HRESULThr;
IActiveDesktop*pIAD;
//1.初始化COM庫(讓Windows載入DLLs)。通常是在程式的InitInstance()中呼叫
//CoInitialize(NULL)或其它啓動程式碼。MFC程式使用AfxOleInit()。
CoInitialize(NULL);
//2.使用外殼提供的活動桌面元件物件類建立COM物件。
//第四個參數通知COM需要什麼介面(這裏是IActiveDesktop).
hr = CoCreateInstance(CLSID_ActiveDesktop, NULL, CLSCTX_INPROC_SERVER, IID_IActiveDesktop, (void**)&pIAD);
if(SUCCEEDED(hr))
{
    //3.如果COM物件被建立成功,則呼叫這個物件的GetWallpaper()方法。
    hr=pIAD->GetWallpaper(wszWallpaper,MAX_PATH,0);
    if(SUCCEEDED(hr))
    {
        //4.如果GetWallpaper()成功,則輸出它返回的檔案名字。
        //注意這裏使用wcout來顯示Unicode串wszWallpaper.wcout是
        //Unicode專用,功能與cout.相同。
        wcout<<L"Wallpaperpathis:\n"<<wszWallpaper<<endl<<endl;
    }
    else
    {
        cout<<_T("GetWallpaper()failed.")<<endl<<endl;
    }
	//5.釋放介面。
	pIAD->Release();
}
else
{
	cout<<_T("CoCreateInstance()failed.")<<endl<<endl;
}
//6.收回COM庫。MFC程式不用這一步,它自動完成。
CoUninitialize();

使用一個提供單介面的COM物件QueryInterface()函數

初始化 COM 庫;

建立一個用於建立快捷方式的COM 物件並取得IShellLink 介面;

呼叫IShellLink 介面的SetPath()方法;

呼叫物件的QueryInterface()函數並取得IPersistFile介面;

呼叫IPersistFile 介面的Save()方法;

釋放介面;

收回COM庫;

CString sWallpaper = wszWallpaper;
//將牆紙路徑轉換爲ANSII
ShellLink *pISL;
IPersistFile *pIPF;
// 1. 初始化COM庫(讓Windows 載入DLLs). 通常在InitInstance()中呼叫
// CoInitialize ( NULL )或其它啓動程式碼。MFC 程式使用AfxOleInit() 。
CoInitialize ( NULL );
//2. 使用外殼提供的Shell Link元件物件類建立COM物件。.
// 第四個參數通知COM 需要什麼介面(這裏是IShellLink)。
hr = CoCreateInstance ( CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (void**) &pISL );
if ( SUCCEEDED(hr) )
{
    // 3. 設定快捷方式目標(牆紙檔案)的路徑。
    hr = pISL->SetPath ( sWallpaper );
    if ( SUCCEEDED(hr) )
	{
		// 4. 獲取這個物件的第二個介面(IPersistFile)。
        hr = pISL->QueryInterface ( IID_IPersistFile, (void**) &pIPF );
        if ( SUCCEEDED(hr) )
        {
        // 5. 呼叫Save() 方法儲存某個檔案得快捷方式。第一個參數是
        // Unicode 串。
        hr = pIPF->Save ( L"C:\\wallpaper.lnk", FALSE );
        // 6a. 釋放IPersistFile 介面。
        pIPF->Release();
        }
	}
    // 6. 釋放IShellLink 介面。
    pISL->Release();
}
// 輸出錯誤資訊部分這裏省略。
// 7. 收回COM 庫。MFC 程式不用這一步,它自動完成。
CoUninitialize();

處理HRESULT

​ HRESULT是個32位元符號整數,其非負值表示成功,負值表示失敗。
​ HRESULT有三個域:程度位(表示成功或失敗),功能碼和狀態碼。
​ 功能碼錶 碼表示HRESULT來自什麼元件或程式。是個16位元的值,僅此而已,沒有其它內在含義
winerror.h標頭檔案中查詢錯誤程式碼,會看到許多按照[功能][程度][描述]命名規範列出的HRESULT值,由元件返回的通用的HRESULT(類似E_OUTOFMEMORY)在名字中沒有功能碼。

  • 如:REGDB_E_READREGDB:
    功能碼 = REGDB, 指「註冊表數據庫(registry database)」;
    程度 = E 意思是錯誤(error);
    描述 = READREGDB 是對錯誤的描述(意思是不能讀註冊表數據庫)。 S_OK: 沒有功能碼——通用(generic)
    HRESULT;
    程度=S;表示成功(success);
    OK 是狀態描述表示一切都正常。