一、前言
上兩回中,咱們用 ATL 寫了第一個 COM 組件程式,這回中,主要介紹編譯、註冊和調用方法。樣本程式你已經下載了嗎?如果還沒有下載,vc6.0 的使用者點這裡,vc.net 的使用者點這裡。
二、關於編譯
2-1 最小依賴
“最小依賴”,表示編譯器會把 ATL
中必須使用的一些函數靜態串連到目標程式中。這樣目標檔案尺寸會稍大,但獨立性更強,安裝方便;反之系統執行的時候需要有 ATL.DLL
檔案的支援。如何選擇設定為“最小依賴”呢?答案是:刪除預定義宏“_ATL_DLL”,操作方法見圖一、圖二。
圖一、在vc6.0中,設定方法
圖二、在 vc.net 2003中,設定方法
2-2 CRT庫
如果在 ATL 組件程式中調用了 CRT 的運行時刻庫函數,比如開平方 sqrt() ,那麼編譯的時候可能會報錯“error
LNK2001: unresolved external symbol
_main”。怎麼辦?刪除預定義宏“_ATL_MIN_CRT”!操作方法也見圖一、圖二。(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 程式編譯的過程:
圖三、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)。
三、關於註冊
情況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 上是個“榜眼”呢......
四、關於組件調用
總的來說,調用組件程式大概有如下方法:
| #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<>,這兩個類都在
<atlbase.h> 中聲明。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,多看樣本程式吧。 寫累了:-(
五、小結
敬請關注《COM 組件設計與應用(八)》------如何增加 ATL 組件中的第二個介面
注1:編譯代理/存根,vc6.0 中稍微麻煩,我們在後面介紹“進程外組件”和“遠程組件”的時候再介紹。在 vc.net 2003 下則比較簡單,因為代理/存根作為單獨的一個工程項目會自動加到我們的解決方案中了。