【轉載】COM 組件設計與應用(七)——編譯、註冊、調用

原文:http://vckbase.com/index.php/wv/1218.htmlphp

 

1、前言html

上兩回中,我們用 ATL 寫了第一個 COM 組件程序,這回中,主要介紹編譯、冊和調用方法。示例程序你已經下載了嗎?若是尚未下載,vc6.0 的用戶點,vc.net 的用戶點這裏。程序員

2、關於編譯函數

2-1 最小依賴spa

「最小依賴」,表示編譯器會把 ATL 中必須使用的一些函數靜態鏈接到目標程序中。這樣目標文件尺寸會稍大,但獨立性更強,安裝方便;反之系統執行的時候須要有 ATL.DLL 文件的支持。如何選擇設置爲「最小依賴」呢?答案是:刪除預約義宏「_ATL_DLL」,操做方法見圖1、圖二。.net

圖1、在vc6.0中,設置方法命令行

圖2、在 vc.net 2003中,設置方法設計

2-2 CRT庫代理

若是在 ATL 組件程序中調用了 CRT 的運行時刻庫函數,好比開平方 sqrt() ,那麼編譯的時候可能會報錯「error LNK2001: unresolved external symbol _main」。怎麼辦?刪除預約義宏「_ATL_MIN_CRT」!操做方法也見圖1、圖二。(vc.net 2003 中的這個項目屬性叫「在 ATL 中最小使用 CRT」)指針

2-3 MBCS/UNICODE

這個很少說了,在預約義宏中,分別使用 _MBCS 或 _UNICODE。

2-4 IDL 的編譯

COM 在設計初期,就定了一個目標:要能實現跨語言的調用。既然是跨語言的,那麼組件的接口描述就必須在任何語言環境中都要可以認識。怎麼辦?用 .h 文件描述?------ C語言程序員笑了,真方便!BASIC 程序員哭了:-( 所以,微軟使用了一個新的文件格式---IDL文件(接口定義描述語言)。IDL 是一個文本文件,它的語言語法比較簡單,很象C。具體 IDL 文件的講解,見下一回《COM 組件設計與應用(八)之添加新接口》。IDL 通過編譯,生成二進制的等價類型庫文件 TLB 提供給其它語言來使用。圖三示意了 ATL COM 程序編譯的過程:

圖3、ATL 組件程序編譯過程

說明1:編譯後,類型庫以 TLB 文件形式單獨存在,同時也保存在目標文件的資源中。所以,咱們未來在 #import 引入類型庫的時候,既能夠指定 TLB 文件,也能夠指定目標文件;

說明2:咱們做爲 C/C++ 的程序員,還算是比較幸福的。由於 IDL 編譯後,特地爲咱們提供了 C 語言形式的接口文件。

說明3:IDL 編譯後生成代理/存根源程序,有:dlldata.c、xxx_p.c、xxxps.def、xxxps.mak,咱們能夠用 NMAKE.EXE 再次編譯來產生真正的代理/存根DLL目標文件(注1)。

3、關於註冊

狀況1:當咱們使用 ATL 編寫組件程序,註冊不用咱們來負責。編譯成功後,IDE 會幫咱們自動註冊;

狀況2:當咱們使用 MFC 編寫組件程序,因爲編譯器不知道你寫的是不是 COM 組件,因此它不會幫咱們自動註冊。這個時候,咱們能夠執行菜單「Tools\Register Control」來註冊。

狀況3:當咱們寫一個具備 COM 功能的 EXE 程序時,註冊的方法就是運行一次這個程序;

狀況4:當咱們須要使用第三方提供的組件程序時,能夠命令行運行「regsvr32.exe 文件名」來註冊。順便說一句,反註冊的方法是「regsvr32.exe /u 文件名」;

狀況5:當咱們須要在程序中(好比安裝程序)須要執行註冊,那麼:

typedef HRESULT (WINAPI * FREG)();
TCHAR szWorkPath[ MAX_PATH ];

::GetCurrentDirectory( sizeof(szWorkPath), szWorkPath );	// 保存當前進程的工做目錄
::SetCurrentDirectory( 組件目錄 );	// 切換到組件的目錄

HMODULE hDLL = ::LoadLibrary( 組件文件名 );	// 動態裝載組件
if(hDLL)
{
	FREG lpfunc = (FREG)::GetProcAddress( hDLL, _T("DllRegisterServer") );	// 取得註冊函數指針
	// 若是是反註冊,能夠取得"DllUnregisterServer"函數指針
	if ( lpfunc )	lpfunc();	// 執行註冊。這裏爲了簡單,沒有判斷返回值
	::FreeLibrary(hDLL);
}

::SetCurrentDirectory(szWorkPath);	// 切換回原先的進程工做目錄

  

上面的示例,在多數狀況下能夠簡化掉切換工做目錄的代碼部分。可是,若是這個組件在裝載的時候,它須要同時加載一些必須依賴的DLL時,有可能因爲它自身程序的 BUG 致使沒法正肯定位。咳......仍是讓咱們本身寫的程序,來彌補它的錯誤吧......誰讓我們是好人呢 ,誰讓我們的水平比他高呢,誰讓我們在 vckbase 上是個「榜眼」呢......

4、關於組件調用

總的來講,調用組件程序大概有以下方法:

#include 方法 IDL編譯後,爲方便C/C++程序員的使用,會產生xxx.h和xxx_i.c文件。咱們真幸福,直接#include後就可使用了
#import 方法 比較通用的方法,vc 會幫咱們產生包裝類,讓咱們的調用更方便
加載類型庫包裝類 方法 若是組件提供了 IDispatch 接口,用這個方法調用組件是最簡單的啦。不過還沒講IDispatch,只能看之後的文章啦
加載ActiveX包裝類 方法 ActiveX 還沒介紹呢,之後再說啦

下載示例程序後,請逐項瀏覽使用方法:

示例

方法

簡要說明

1 #include 徹底用最基本的 API 方式調用組件,使你們熟悉調用原理
2 #include 大部分使用 API 方式,使用 CComBSTR 簡化對字符串的使用
3 #include 展現智能指針 CComPtr<> 的使用方法
4 #include 展現智能指針 CComPtr<> 和 CComQIPtr<> 混合的使用方法
5 #include 展現智能指針 CComQIPtr<> 的使用方法
6 #include 展現智能指針的釋放方法
7 #import vc 包裝的智能指針 IxxxPtr、_bstr_t、_variant_t 的使用方法和異常處理
8 #import import 後的命名空間的使用方法

示例程序中都寫有註釋,請讀者仔細閱讀並同時參考 MSDN 的函數說明。這裏,我給你們介紹一下「智能指針」:

對於操做原始的接口指針是比較麻煩的,須要咱們本身控制引用記數、API 調用、異常處理。因而 ATL 提供了2個智能指針的模板包裝類,CComPtr<> 和 CComQIPtr<>,這兩個類都在 中聲明。CComQIPtr<> 包含了 CComPtr<>的全部功能,所以咱們能夠徹底用 CComQIPtr<> 來使用智能接口指針,惟一要說明的一點就是:CComQIPtr<> 因爲使用了運算符的重載功能,它會自動幫咱們調用QueryInterface()函數,所以 CComQIPtr<> 惟一的缺點就是不能定義 IUnknown * 指針。

// 智能指針 smart pointer,按照匈牙利命名法,通常以 sp 開頭來表示變量類型
CComPtr < IUnknown > spUnk;	// 正確
// 假設 IFun 是一個接口類型
CComPtr < IFun > spFun;	// 正確
CComQIPtr < IFun > spFun;	// 正確
CComQIPtr < IFun, &IID_IFun > spFun;	// 正確
CComQIPtr < IUnknown > spUnk;	// 錯誤!CComQIPtr不能定義IUnknown指針

  

給智能指針賦值的方法:

CComQIPtr < IFun > spFun;	// 調用構造函數,尚未賦值,被包裝的內部接口指針爲 NULL

CComQIPtr < IFun > spFun( pOtherInterface );	// 調用構造函數,內部接口指針賦值爲
// 經過 pOtherInterface 這個普通接口指針調用QueryInterface()獲得的IFun接口指針

CComQIPtr < IFun > spFun( spOtherInterface ); // 調用構造函數,內部接口指針賦值爲
// 經過 spOtherInterface 這個只能接口指針調用QueryInterface()獲得的IFun接口指針

CComQIPtr < IFun > spFun ( pUnknown );	// 調用構造函數,由IUnknown的QueryInterface()獲得IFun接口指針

CComQIPtr < IFun > spFun = pOtherInterface;	// = 運算符重載,含義和上面同樣
spFun = spOtherInterface;	// 同上
spFun = pUnknown;	// 同上

pUnknown->QueryInterface( IID_IFun, &sp );	// 也能夠經過QueryInterface賦值

// 智能指針賦值後,能夠用條件語句判斷是否合法有效
if ( spFun ){}		// 若是指針有效
if ( NULL != spFun ){}	// 若是指針有效

if ( !spFun ){}		// 若是指針無效
if ( NULL == spFun ){}	// 若是指針無效

  

智能指針調用函數的方法:

spFun.CoCreateInstance(...);	// 等價與 API 函數::CoCreateInstance(...)
spFun.QueryInterface(...);	// 等價與 API 函數::QueryInterface()

spFun->Add(...);	// 調用內部接口指針的接口函數

// 調用內部接口指針的QueryInterface()函數,其實效果和 spFun.QueryInterface(...) 同樣
spFun->QueryInterface(...);	

spFun.Release();	// 釋放內部的接口指針,同時內部指針賦值爲 NULL
spFun->Release();	// 錯!!!必定不要這麼使用。
// 由於這個調用並不把內部指針清空,那麼析構的時候會被再次釋放(釋放了兩次)

  

咳......不說了,不說了,你們多看書,多看MSND,多看示例程序吧。 寫累了:-(

5、小結

敬請關注《COM 組件設計與應用(八)》------如何增長 ATL 組件中的第二個接口

注1:編譯代理/存根,vc6.0 中稍微麻煩,咱們在後面介紹「進程外組件」和「遠程組件」的時候再介紹。在 vc.net 2003 下則比較簡單,由於代理/存根做爲單獨的一個工程項目會自動加到咱們的解決方案中了。

相關文章
相關標籤/搜索