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