天天看點

COM 元件設計與應用(七)——編譯、注冊、調用

本文摘自:http://www.vckbase.net/index.php/wv/1218

一、前言

上兩回中,咱們用 ATL 寫了第一個 COM 元件程式,這回中,主要介紹編譯、冊和調用方法。示例程式你已經下載下傳了嗎?如果還沒有下載下傳,vc6.0 的使用者點,vc.net 的使用者點這裡。

二、關于編譯

2-1 最小依賴

“最小依賴”,表示編譯器會把 ATL 中必須使用的一些函數靜态連接配接到目标程式中。這樣目标檔案尺寸會稍大,但獨立性更強,安裝友善;反之系統執行的時候需要有 ATL.DLL 檔案的支援。如何選擇設定為“最小依賴”呢?答案是:删除預定義宏“_ATL_DLL”,操作方法見圖一、圖二。

COM 元件設計與應用(七)——編譯、注冊、調用

圖一、在vc6.0中,設定方法

COM 元件設計與應用(七)——編譯、注冊、調用

圖二、在 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 程式編譯的過程:

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:當我們需要在程式中(比如安裝程式)需要執行注冊,那麼:

01.

typedef

HRESULT

(WINAPI * FREG)();

02.

TCHAR

szWorkPath[ MAX_PATH ];

03.

04.

::GetCurrentDirectory( 

sizeof

(szWorkPath), szWorkPath );    

// 儲存目前程序的工作目錄

05.

::SetCurrentDirectory( 元件目錄 );  

// 切換到元件的目錄

06.

07.

HMODULE

hDLL = ::LoadLibrary( 元件檔案名 );  

// 動态裝載元件

08.

if

(hDLL)

09.

{

10.

FREG lpfunc = (FREG)::GetProcAddress( hDLL, _T(

"DllRegisterServer"

) );  

// 取得注冊函數指針

11.

// 如果是反注冊,可以取得"DllUnregisterServer"函數指針

12.

if

( lpfunc )   lpfunc();   

// 執行注冊。這裡為了簡單,沒有判斷傳回值

13.

::FreeLibrary(hDLL);

14.

}

15.

16.

::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<>,這兩個類都在 中聲明。CComQIPtr<> 包含了 CComPtr<>的所有功能,是以我們可以完全用 CComQIPtr<> 來使用智能接口指針,唯一要說明的一點就是:CComQIPtr<> 由于使用了運算符的重載功能,它會自動幫我們調用QueryInterface()函數,是以 CComQIPtr<> 唯一的缺點就是不能定義 IUnknown * 指針。

1.

// 智能指針 smart pointer,按照匈牙利命名法,一般以 sp 開頭來表示變量類型

2.

CComPtr < IUnknown > spUnk;   

// 正确

3.

// 假設 IFun 是一個接口類型

4.

CComPtr < IFun > spFun;   

// 正确

5.

CComQIPtr < IFun > spFun; 

// 正确

6.

CComQIPtr < IFun, &IID_IFun > spFun;  

// 正确

7.

CComQIPtr < IUnknown > spUnk; 

// 錯誤!CComQIPtr不能定義IUnknown指針

給智能指針指派的方法:

01.

CComQIPtr < IFun > spFun; 

// 調用構造函數,還沒有指派,被包裝的内部接口指針為 NULL

02.

03.

CComQIPtr < IFun > spFun( pOtherInterface );  

// 調用構造函數,内部接口指針指派為

04.

// 通過 pOtherInterface 這個普通接口指針調用QueryInterface()得到的IFun接口指針

05.

06.

CComQIPtr < IFun > spFun( spOtherInterface ); 

// 調用構造函數,内部接口指針指派為

07.

// 通過 spOtherInterface 這個隻能接口指針調用QueryInterface()得到的IFun接口指針

08.

09.

CComQIPtr < IFun > spFun ( pUnknown );    

// 調用構造函數,由IUnknown的QueryInterface()得到IFun接口指針

10.

11.

CComQIPtr < IFun > spFun = pOtherInterface;   

// = 運算符重載,含義和上面一樣

12.

spFun = spOtherInterface;   

// 同上

13.

spFun = pUnknown;   

// 同上

14.

15.

pUnknown->QueryInterface( IID_IFun, &sp );   

// 也可以通過QueryInterface指派

16.

17.

// 智能指針指派後,可以用條件語句判斷是否合法有效

18.

if

( spFun ){}      

// 如果指針有效

19.

if

( NULL != spFun ){}  

// 如果指針有效

20.

21.

if

( !spFun ){}     

// 如果指針無效

22.

if

( NULL == spFun ){}  

// 如果指針無效

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

01.

spFun.CoCreateInstance(...);    

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

02.

spFun.QueryInterface(...);  

// 等價與 API 函數::QueryInterface()

03.

04.

spFun->Add(...); 

// 調用内部接口指針的接口函數

05.

06.

// 調用内部接口指針的QueryInterface()函數,其實效果和 spFun.QueryInterface(...) 一樣

07.

spFun->QueryInterface(...); 

08.

09.

spFun.Release();    

// 釋放内部的接口指針,同時内部指針指派為 NULL

10.

spFun->Release();    

// 錯!!!一定不要這麼使用。

11.

// 因為這個調用并不把内部指針清空,那麼析構的時候會被再次釋放(釋放了兩次)

咳......不說了,不說了,大家多看書,多看MSND,多看示例程式吧。 寫累了:-(

五、小結

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

注1:編譯代理/存根,vc6.0 中稍微麻煩,我們在後面介紹“程序外元件”和“遠端元件”的時候再介紹。在 vc.net 2003 下則比較簡單,因為代理/存根作為單獨的一個工程項目會自動加到我們的解決方案中了。

COM

繼續閱讀