青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

山寨:不是最好的,是最適合我們的!歡迎體驗(yàn)山寨 中文版MSDN

Blog @ Blog

當(dāng)華美的葉片落盡,生命的脈絡(luò)才歷歷可見。 -- 聶魯達(dá)

常用鏈接

統(tǒng)計(jì)

積分與排名

BBS

Blog

Web

最新評論

學(xué)習(xí)筆記

 

起源及復(fù)合文件

復(fù)合文件概念:

文件的 COM 結(jié)構(gòu)化存儲的實(shí)現(xiàn)。復(fù)合文件將單獨(dú)對象存儲在單一的、結(jié)構(gòu)化文件中,此文件由兩個(gè)主要元素組成:存儲對象流對象。二者結(jié)合使用,可象文件內(nèi)的文件系統(tǒng)一樣起作用。

特點(diǎn):

1)復(fù)合文件的內(nèi)部是使用指針構(gòu)造的一棵樹進(jìn)行管理的。使用的是單向指針,因此當(dāng)做定位操作的時(shí)候,向后定位比向前定位要快

2)復(fù)合文件中的流對象,是真正保存數(shù)據(jù)的空間。它的存儲單位512字節(jié)。

3)不同的進(jìn)程,或同一個(gè)進(jìn)程的不同線程可以同時(shí)訪問一個(gè)復(fù)合文件的不同部分而互不干擾

4)復(fù)合文件則提供了非常方便的增量訪問能力

5)當(dāng)頻繁地刪除文件,復(fù)制文件后,磁盤空間會變的很零碎,需要使用磁盤整理工具進(jìn)行重新整合。和磁盤管理非常相似,復(fù)合文件也會產(chǎn)生這個(gè)問題,在適當(dāng)?shù)臅r(shí)候也需要整理,但比較簡單,只要調(diào)用一個(gè)函數(shù)就可以完成了

復(fù)合文件函數(shù)
  復(fù)合文件的函數(shù)和磁盤目錄文件的操作非常類似。所有這些函數(shù),被分為3種類型:WIN API 全局函數(shù)存儲 IStorage 接口函數(shù) IStream 接口函數(shù)接口看成是完成一組相關(guān)操作功能的函數(shù)集合

小結(jié):

復(fù)合文件,結(jié)構(gòu)化存儲,是微軟組件思想的起源,在此基礎(chǔ)上繼續(xù)發(fā)展出了持續(xù)性、命名、ActiveX、對象嵌入、現(xiàn)場激活......一系列的新技術(shù)、新概念。因此理解和掌握復(fù)合文件是非常重要的,即使在你的程序中并沒有全面使用組件技術(shù),復(fù)合文件技術(shù)也是可以單獨(dú)被應(yīng)用的。

GUID 和接口

CLSID(注1)的方式間接描述這些對象數(shù)據(jù)的處理程序路徑CLSID 其實(shí)就是一個(gè)號碼,或者說是一個(gè)16字節(jié)的數(shù)CLSID 的結(jié)構(gòu)定義如下:

typedef struct _GUID  {
    DWORD Data1;    
// 
隨機(jī)數(shù)
    WORD Data2;    // 和時(shí)間相關(guān)
    WORD Data3;    // 和時(shí)間相關(guān)
    BYTE Data4[8]; // 和網(wǎng)卡MAC相關(guān)
} GUID;

typedef GUID CLSID;  
// 組件ID
typedef GUID IID;    // 接口ID
產(chǎn)生 CLSID

1.    如果使用開發(fā)環(huán)境編寫組件程序,則IDE會自動幫你產(chǎn)生 CLSID

2.    你可以手工寫 CLSID,但千萬不要和人家已經(jīng)生成的 CLSID 重復(fù)呀,所以嚴(yán)重地不推薦

3.    程序中,可以用函數(shù) CoCreateGuid() 產(chǎn)生 CLSID

4.    使用工具產(chǎn)生 GUID(注2);

微軟為了使用方便,也支持另一個(gè)字符串名稱方式,叫 ProgID由于 CLSID ProgID 其實(shí)是一個(gè)概念的兩個(gè)不同的表示形式,所以在程序中可以隨便使用任何一種。疑問?!字符串名稱容易重復(fù)啊!!

介紹一下 CLSID ProgID 之間的轉(zhuǎn)換方法和相關(guān)的函數(shù):

函數(shù)

功能說明

CLSIDFromProgID()

CLSIDFromProgIDEx()

ProgID得到CLSID。沒什么好說的,你自己都可以寫,查注冊表貝

ProgIDFromCLSID()

CLSID 得到 ProgID,調(diào)用者使用完成后要釋放 ProgID 的內(nèi)存(5)

CoCreateGuid()

隨機(jī)生成一個(gè) GUID

IsEqualGUID()IsEqualCLSID()IsEqualIID()

比較2個(gè)ID是否相等

StringFromCLSID()StringFromGUID2()StringFromIID()

CLSID,IID 得到注冊表中CLSID樣式的字符串,注意釋放內(nèi)存

關(guān)于接口

COM接口(組件)的集合,接口是方法屬性的集合。 要了解COM,就得先了解IUnknown接口,IUnknown接口的C++形式的定義如下:  
  interface   IUnknown  
  {  
          virtual   HRESULT   _stdcall   QueryInterface([in]REFIID   iid,[out]void   *   *   ppv)=0;  
          virtual   ULONG   _stdcall   AddRef(void)=0;  
          virtual   ULONG   _stdcall   Release(void)=0;  
  }  
 
她實(shí)現(xiàn)了接口查詢引用計(jì)數(shù),她是一個(gè)純抽象基類。 所有COM   定義的接口都必須從她繼承。  

IUnknown是所有接口的基礎(chǔ),他負(fù)責(zé)兩項(xiàng)工作:   
IUnknown::QueryInterface
負(fù)責(zé)得到該組件的其他接口的指針   
IUnknown::AddRef/Release
負(fù)責(zé)管理該組件的生存期,但有人使用該組件時(shí),保證該組件不會被意外刪除;再沒人使用該組件時(shí),保證該組件被自動刪除  

下面是容器和組件之間的一個(gè)模擬對話過程:

 

容器 協(xié)商部分

組件 應(yīng)答部分

1

根據(jù)CLSID啟動組件
CoCreateInstance()

生成對象,執(zhí)行構(gòu)造函數(shù),執(zhí)行初始化動作。

2

你有IUnknown接口嗎?

有,給你!

3

恩,太好了,那么你有IPersistStorage接口嗎?(9)
IUnknown::QueryInterface(IID_IPersistStorage...)

沒有!

4

真差勁,連這個(gè)都沒有。那你有IPersistStreamInit接口嗎?(10)
IUnknown::QueryInterface(IID_IPersistStreamInit...)

哈,這個(gè)有,給!

5

好,好,這還差不多。你現(xiàn)在給我初始化吧。
IPersistStreamInit::InitNew()

OK,初始化完成了。

6

完成了?好!現(xiàn)在你讀數(shù)據(jù)去吧。
IPersistStreamInit::Load()

讀完啦。我根據(jù)數(shù)據(jù),已經(jīng)在窗口中顯示出來了。

7

好,現(xiàn)在咱們各自處理用戶的鼠標(biāo)、鍵盤消息吧......

......

8

哎呀!用戶要保存退出程序了。你的數(shù)據(jù)被用戶修改了嗎?
IPersistStreamInit::IsDirty()

改了,用戶已經(jīng)修改啦。

9

那好,那么用戶修改后,你的數(shù)據(jù)需要多大的存儲空間呀?
IPersistStreamInit::GetSizeMax()

恩,我算算呀......好了,總共需要500KB

10

暈,你這么個(gè)小玩意居然占用這么大空間?!......好了,你可以存了。
IPersistStreamInit::Save()

謝謝,我已經(jīng)存好了。

11

恩。拜拜了您那。(11)
IPersistStreamInit::Release()
IUnknown::Release()

執(zhí)行析構(gòu)函數(shù),刪除對象。

12

我自己也該退出了......
PostQuitMessage()

 

數(shù)據(jù)類型

簡單調(diào)用組件

1、組件的啟動和釋放


圖一 組件調(diào)用機(jī)制

由上圖可以看出,當(dāng)調(diào)用組件的時(shí)候,其實(shí)是依靠代理(運(yùn)行在本地)和存根(運(yùn)行在遠(yuǎn)端)之間的通訊完成的。具體來說,當(dāng)客戶程序通過 CoCreateInstance() 函數(shù)啟動組件,則代理接管該調(diào)用,它和存根通訊,存根則它所在的本地(相對于客戶程序來說就是遠(yuǎn)程了)執(zhí)行 new 操作加載對象。遵守幾個(gè)原則:
  (1)啟動組件得到一個(gè)接口指針(Interface)后,不要調(diào)用AddRef()。因?yàn)橄到y(tǒng)知道你得到了一個(gè)指針,所以它已經(jīng)幫你調(diào)用了AddRef()函數(shù);
  (2)通過QueryInterface()得到另一個(gè)接口指針后,不要調(diào)用AddRef()。因?yàn)?/span>......和上面的道理一樣;
  (3)當(dāng)你把接口指針賦值給(保存到)另一個(gè)變量中的時(shí)候,請調(diào)用AddRef()
  (4)當(dāng)不需要再使用接口指針的時(shí)候,務(wù)必執(zhí)行Release()釋放;
  (5)當(dāng)使用智能指針的時(shí)候,可以省略指針的維護(hù)工作;

2、內(nèi)存分配和釋放
函數(shù)內(nèi)部根據(jù)實(shí)際需要動態(tài)申請內(nèi)存,而調(diào)用者負(fù)責(zé)釋。這雖然違背了上述原則,但 COM 從方便性和效率出發(fā),確實(shí)是這么設(shè)計(jì)的。

 

C語言

C++語言

Windows 平臺

COM

IMalloc 接口

BSTR

申請

malloc()

new

GlobalAlloc()

CoTaskMemAlloc()

Alloc()

SysAllocString()

重新申請

realloc()

 

GlobalReAlloc()

CoTaskRealloc()

Realloc()

SysReAllocString()

釋放

free()

delete

GlobalFree()

CoTaskMemFree()

Free()

SysFreeString()

以上這些函數(shù)必須要按類型配合使用(比如:new 申請的內(nèi)存,則必須用 delete 釋放)。在 COM 內(nèi)部,當(dāng)然你可以隨便使用任何類型的內(nèi)存分配釋放函數(shù),但組件如果需要與客戶進(jìn)行內(nèi)存的交互,則必須使用上表中的后三類函數(shù)族。
  (1BSTR 內(nèi)存在上回書中,已經(jīng)有比較豐富的介紹了,不再重復(fù);
  (2CoTaskXXX()函數(shù)族,其本質(zhì)上就是調(diào)用C語言的函數(shù)(malloc...);
  (3IMalloc 接口又是對 CoTaskXXX() 函數(shù)族的一個(gè)包裝。包裝后,同時(shí)增強(qiáng)了一些功能,比如:IMalloc::GetSize()可以取得尺寸,使用 IMallocSpy 可以監(jiān)視內(nèi)存的使用;

3參數(shù)傳遞方向
參數(shù)是動態(tài)分配的內(nèi)存指針,那么遵守如下的規(guī)定:

方向

申請人

釋放人

提示

[in]

調(diào)用者

調(diào)用者

組件接收指針后,不能重新分配內(nèi)存

[out]

組件

調(diào)用者

組件返回指針后,調(diào)用者愛咋咋地”(3)

[in,out]

調(diào)用者

調(diào)用者

組件可以重新分配內(nèi)存

 

ATL 寫第一個(gè)組件(關(guān)于流程多寫代碼就熟悉了,這里不重復(fù)!)

1建立 ATL 工程方法


圖一、建立 ATL DLL 工程

Dynamic Link Library(DLL) 表示建立一個(gè) DLL 的組件程序。
  Executable(EXE) 表示建立一個(gè) EXE 的組件程序。
  Service(EXE) 表示建立一個(gè)服務(wù)程序,系統(tǒng)啟動后就會加載并執(zhí)行的程序。
  Allow merging of proxy/stub code 選擇該項(xiàng)表示把代理/存根代碼合并到組件程序中,否則需要單獨(dú)編譯,單獨(dú)注冊代理存根程序。代理/存根,這個(gè)是什么概念?還記得我們在上回書中介紹的嗎?當(dāng)調(diào)用者調(diào)用進(jìn)程外或遠(yuǎn)程組件功能的時(shí)候,其實(shí)是代理/存根負(fù)責(zé)數(shù)據(jù)交換的。關(guān)于代理/存根的具體變成和操作,以后再說啦......
  Support MFC 除非有特殊的原因,我們寫 ATL 程序,最好不要選擇該項(xiàng)。

2、增加 ATL 對象類方法


圖二、選擇建立簡單COM對象

Category Object 普通組件。其中可以選擇的組件對象類型很多,但本質(zhì)上,就是讓向?qū)臀覀兡J(rèn)加上一些接口。比如我們選 "Simple Object",則向?qū)Ыo我們的組件加上 IUnknown 接口;我們選 "Internet Explorer Object",則向?qū)С思由?/span> IUnknown 接口外,再增加一個(gè)給 IE 所使用的 IObjectWithSite 接口。當(dāng)然了,我們完全可以手工增加任何接口。
  Category Controls ActiveX 控件。其中可以選擇的 ActiveX 類型也很多。我們在后續(xù)的專門介紹 ActiveX 編程中再討論。
  Category Miscellaneous 輔助雜類組件。
  Categroy Data Access 數(shù)據(jù)庫類組件(我最討厭數(shù)據(jù)庫編程了,所以我也不會)

圖四、接口屬性

Threading Model 選擇組件支持的線程模型。默認(rèn)Apartment,它代表什么那?簡單地說:當(dāng)在線程中調(diào)用組件函數(shù)的時(shí)候,這些調(diào)用會排隊(duì)進(jìn)行。因此,這種模式下,我們可以暫時(shí)不用考慮同步的問題。
   Interface 接口基本類型。Dual 表示支持雙接口(2),這個(gè)非常 非常重要,非常非常常用,但我們今天不講。Custom 表示自定義借口。切記!切記!我們的這第一個(gè) COM 程序中,一定要選擇它!!!!(如果你選錯(cuò)了,請刪除全部內(nèi)容,重新來過。)
   Aggregation 我們寫的組件,將來是否允許被別人聚合(3)使用。Only 表示必須被聚合才能使用,有點(diǎn)類似 C++ 中的純虛類,你要是總工程師,只負(fù)責(zé)設(shè)計(jì)但不親自寫代碼的話,才選擇它。
  Support ISupportErrorInfo 是否支持豐富信息的錯(cuò)誤處理接口。以后就講。
  Support Connection Points 是否支持連接點(diǎn)接口(事件、回調(diào))。以后就講。
  Free Threaded Marshaler 暫時(shí)我也不知道

3、添加接口函數(shù)方法

 

編譯、注冊、調(diào)用

關(guān)于編譯
  2-1 最小依賴
  最小依賴,表示編譯器會把 ATL 中必須使用的一些函數(shù)靜態(tài)連接到目標(biāo)程序中。這樣目標(biāo)文件尺寸會稍大,但獨(dú)立性更強(qiáng),安裝方便;反之系統(tǒng)執(zhí)行的時(shí)候需要有 ATL.DLL 文件的支持。如何選擇設(shè)置為最小依賴呢?答案是:刪除預(yù)定義宏“_ATL_DLL”,操作方法見圖一、圖二。


圖一、在vc6.0中,設(shè)置方法

圖二、在 vc.net 2003中,設(shè)置方法

  2-2 CRT
  如果在 ATL 組件程序中調(diào)用了 CRT 的運(yùn)行時(shí)刻庫函數(shù),比如開平方 sqrt() ,那么編譯的時(shí)候可能會報(bào)錯(cuò)“error LNK2001: unresolved external symbol _main”。怎么辦?刪除預(yù)定義宏“_ATL_MIN_CRT”!操作方法也見圖一、圖二。(vc.net 2003 中的這個(gè)項(xiàng)目屬性叫 ATL 中最小使用 CRT”
  2-3 MBCS/UNICODE
  這個(gè)不多說了,在預(yù)定義宏中,分別使用 _MBCS _UNICODE
  2-4 IDL 的編譯
  COM 在設(shè)計(jì)初期,就定了一個(gè)目標(biāo):要能實(shí)現(xiàn)跨語言的調(diào)用。既然是跨語言的,那么組件的接口描述就必須在任何語言環(huán)境中都要能夠認(rèn)識。怎么辦?用 .h 文件描述?------ C語言程序員笑了,真方便!BASIC 程序員哭了:-( 因此,微軟使用了一個(gè)新的文件格式---IDL文件(接口定義描述語言)IDL 是一個(gè)文本文件,它的語言語法比較簡單,很象C。具體 IDL 文件的講解,見下一回《COM 組件設(shè)計(jì)與應(yīng)用(八)之添加新接口》。IDL 經(jīng)過編譯,生成二進(jìn)制的等價(jià)類型庫文件 TLB 提供給其它語言來使用。圖三示意了 ATL COM 程序編譯的過程:


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

  說明1:編譯后,類型庫以 TLB 文件形式單獨(dú)存在,同時(shí)也保存在目標(biāo)文件的資源中。因此,我們將來在 #import 引入類型庫的時(shí)候,既可以指定 TLB 文件,也可以指定目標(biāo)文件;
  說明2:我們作為 C/C++ 的程序員,還算是比較幸福的。因?yàn)?/span> IDL 編譯后,特意為我們提供了 C 語言形式的接口文件。
  說明3IDL 編譯后生成代理/存根源程序,有:dlldata.cxxx_p.cxxxps.defxxxps.mak,我們可以用 NMAKE.EXE 再次編譯來產(chǎn)生真正的代理/存根DLL目標(biāo)文件(1)

關(guān)于注冊
  情況1當(dāng)我們使用 ATL 編寫組件程序,注冊不用我們來負(fù)責(zé)。編譯成功后,IDE 會幫我們自動注冊;
  情況2當(dāng)我們使用 MFC 編寫組件程序,由于編譯器不知道你寫的是否是 COM 組件,所以它不會幫我們自動注冊。這個(gè)時(shí)候,我們可以執(zhí)行菜單“Tools\Register Control”來注冊。
  情況3當(dāng)我們寫一個(gè)具有 COM 功能的 EXE 程序時(shí),注冊的方法就是運(yùn)行一次這個(gè)程序;
  情況4當(dāng)我們需要使用第三方提供的組件程序時(shí),可以命令行運(yùn)行“regsvr32.exe 文件名來注冊。順便說一句,反注冊的方法是“regsvr32.exe /u 文件名
  情況5當(dāng)我們需要在程序中(比如安裝程序)需要執(zhí)行注冊,那么:

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

::GetCurrentDirectory( 
sizeof(szWorkPath), szWorkPath );    // 
保存當(dāng)前進(jìn)程的工作目錄
::SetCurrentDirectory( 組件目錄 );    // 切換到組件的目錄

HMODULE hDLL = ::LoadLibrary( 
組件文件名 );    // 動態(tài)裝載組件
if(hDLL)
{
    FREG lpfunc = (FREG)::GetProcAddress( hDLL, _T("DllRegisterServer") );    
// 取得注冊函數(shù)指針
    // 
如果是反注冊,可以取得"DllUnregisterServer"函數(shù)指針
    if ( lpfunc )    lpfunc();    // 執(zhí)行注冊。這里為了簡單,沒有判斷返回值
    ::FreeLibrary(hDLL);
}

::SetCurrentDirectory(szWorkPath);    
// 切換回原先的進(jìn)程工作目錄

上面的示例,在多數(shù)情況下可以簡化掉切換工作目錄的代碼部分。但是,如果這個(gè)組件在裝載的時(shí)候,它需要同時(shí)加載一些必須依賴的DLL時(shí),有可能由于它自身程序的 BUG 導(dǎo)致無法正確定位。咳......還是讓我們自己寫的程序,來彌補(bǔ)它的錯(cuò)誤吧......誰讓咱們是好人呢 ,誰讓咱們的水平比他高呢,誰讓咱們在 vckbase 上是個(gè)榜眼......

關(guān)于組件調(diào)用

總的來說,調(diào)用組件程序大概有如下方法:

#include 方法

IDL編譯后,為方便C/C++程序員的使用,會產(chǎn)生xxx.hxxx_i.c文件。我們真幸福,直接#include后就可以使用了

#import 方法

比較通用的方法,vc 會幫我們產(chǎn)生包裝類,讓我們的調(diào)用更方便

加載類型庫包裝類 方法

如果組件提供了 IDispatch 接口,用這個(gè)方法調(diào)用組件是最簡單的啦。不過還沒講IDispatch,只能看以后的文章啦

加載ActiveX包裝類 方法

ActiveX 還沒介紹呢,以后再說啦

 

實(shí)現(xiàn)多接口

按照函數(shù)的功能進(jìn)行分類,把不同功能分類的函數(shù)用多個(gè)接口表現(xiàn)出來。這樣可以有如下的一些好處:
    1
、一個(gè)接口中的函數(shù)個(gè)數(shù)有限、功能集中,使用者容易學(xué)習(xí)、記憶調(diào)用。一個(gè)接口到底提供多少個(gè)函數(shù)合適那?答案是:如果你是黑猩猩,那么一個(gè)接口最多3個(gè)函數(shù),如果你是人,那么一個(gè)接口最好不要超過7個(gè)函數(shù)。(1)
    2
、容易維護(hù)。至少你肉眼搜索的時(shí)候也方便一些呀。
    3
、容易升級。當(dāng)我們給組件增加函數(shù)的時(shí)候,不要修改已經(jīng)發(fā)表的接口,而是提供一個(gè)新的接口來完成功能擴(kuò)展

實(shí)現(xiàn)

1 import "oaidl.idl";
2 import "ocidl.idl";
3    [
4        
object,
5        uuid(072EA6CA-7D08-4E7E-B2B7-B2FB0B875595),
    
6        helpstring("IMathe Interface"),
7        pointer_default(unique)
8    ]
9    
interface IMathe : IUnknown
10    
{
11        [helpstring("method Add")] HRESULT Add([
inlong n1, [inlong n2, [out,retval] long *pnVal);
12    };

13 [
14    uuid(CD7672F7-C0B4-4090-A2F8-234C0062F42C),
15    version(1.0),
16    helpstring("Simple3 1.0 Type Library")
17 ]
18 library SIMPLE3Lib
19 
{
20    importlib("stdole32.tlb");
21    importlib("stdole2.tlb");

22    [
23        uuid(C6F241E2-43F6-4449-A024-B7340553221E),
24        helpstring("Mathe Class")
25    ]
26    coclass Mathe
27    
{
28        [
defaultinterface IMathe;
29    };
30 };

1-2

引入 IUnknown ATL已經(jīng)定義的其它接口描述文件。import 類似與 C 語言中的 #include

3-12

一個(gè)接口的完整描述

4

object 表示本塊描述的是一個(gè)接口。IDL文件是借用了PRC遠(yuǎn)程數(shù)據(jù)交換格式的說明方法

5

uuid(......) 接口的 IID,這個(gè)值是 ATL 自動生成的,可以手工修改或用 guidgen.exe 產(chǎn)生(3)

6

在某些軟件或工具中,能看到這個(gè)提示

7

定義接口函數(shù)中參數(shù)所使用指針的默認(rèn)屬性(4)

9

接口叫 IMathe 派生自 IUnknown,于是 IMathe 接口的頭三個(gè)函數(shù)一定就是QueryInterface,AddRefRelease

10-12

接口函數(shù)列表

13-30

類型庫的完整描述(類型庫的概念以后再說),下面所說明的行,是需要先了解的

18

#import 時(shí)候的默認(rèn)命名空間

23

組件的 CLSIDCoCreateInstance()的第一個(gè)參數(shù)就是它

27-29

接口列表

28

[default]表示誰提供了IUnknown接口

手工修改IDL文件,黑體字部分是手工輸入的。完成后保存。

import "oaidl.idl";
import "ocidl.idl";
    [
        
object,
        uuid(072EA6CA-7D08-4E7E-B2B7-B2FB0B875595),
    
        helpstring("IMathe Interface"),
        pointer_default(unique)
    ]
    
interface IMathe : IUnknown
    
{
        [helpstring("method Add")] HRESULT Add([
inlong n1, [inlong n2, [out,retval] long *pnVal);
    };


    [    
// 
所謂手工輸入,其實(shí)也是有技巧的:把上面的接口描述(IMathe)復(fù)制、粘貼下來,然后再改更方便哈
        object,
        uuid(072EA6CB-7D08-4E7E-B2B7-B2FB0B875595), 
// 手工或用工具產(chǎn)生的 IID
    
        helpstring("IStr Interface"),
        pointer_default(unique)
    ]
    
interface IStr : IUnknown
    
{
        
// 目前還沒有任何接口函數(shù)
    };

 [
    uuid(CD7672F7-C0B4-4090-A2F8-234C0062F42C),
    version(1.0),
    helpstring("Simple3 1.0 Type Library")
]
library SIMPLE3Lib
{
    importlib("stdole32.tlb");
    importlib("stdole2.tlb");

    [
        uuid(C6F241E2-43F6-4449-A024-B7340553221E),
        helpstring("Mathe Class")
    ]
    coclass Mathe
    
{
        [
defaultinterface IMathe;
        
interface IStr;    // 
別忘了呦,這里還有一個(gè)那
    };
};

    3-4、打開頭文件(Mathe.h),手工增加類的派生關(guān)系和接口入口表 ,然后保存。

class ATL_NO_VTABLE CMathe : 
    
public CComObjectRootEx <CComSingleThreadModel>,
    
public CComCoClass <CMathe, &CLSID_Mathe>,
    
public IMathe,    // 
別忘了,這里加一個(gè)逗號
    public IStr    // 增加一個(gè)基類
{
public:
    CMathe()
    
{
    }

DECLARE_REGISTRY_RESOURCEID(IDR_MATHE)

DECLARE_PROTECT_FINAL_CONSTRUCT()

BEGIN_COM_MAP(CMathe)    
// 接口入口表。這里填寫的接口,才能被QueryInterface()找到
    COM_INTERFACE_ENTRY(IMathe)
    COM_INTERFACE_ENTRY(IStr)
END_COM_MAP()

    3-5、好了,一切就緒。接下來,就可以在 IStr 接口中增加函數(shù)了。

 

IDispatch 接口

自動化組件,其實(shí)就是實(shí)現(xiàn)了 IDispatch 接口的組件。IDispatch 接口有4個(gè)函數(shù),解釋語言的執(zhí)行器就通過這僅有的4個(gè)函數(shù)來執(zhí)行組件所提供的功能。IDispatch 接口用 IDL 形式說明如下:(1)

[

    object,

    uuid(00020400-0000-0000-C000-000000000046),     // IDispatch 接口的 IID = IID_IDispatch

    pointer_default(unique)

]

 

interface IDispatch : IUnknown

{

    typedef [unique] IDispatch * LPDISPATCH;        // 轉(zhuǎn)定義 IDispatch * LPDISPATCH

 

    HRESULT GetTypeInfoCount([out] UINT * pctinfo); // 有關(guān)類型庫的這兩個(gè)函數(shù),咱們以后再說

    HRESULT GetTypeInfo([in] UINT iTInfo,[in] LCID lcid,[out] ITypeInfo ** ppTInfo);

 

    HRESULT GetIDsOfNames(        // 根據(jù)函數(shù)名字,取得函數(shù)序號(DISPID)

                [in] REFIID riid,

                [in, size_is(cNames)] LPOLESTR * rgszNames,

                [in] UINT cNames,

                [in] LCID lcid,

                [out, size_is(cNames)] DISPID * rgDispId

            );

 

    [local]               // 本地版函數(shù)

    HRESULT Invoke(       // 根據(jù)函數(shù)序號,解釋執(zhí)行函數(shù)功能

                [in] DISPID dispIdMember,

                [in] REFIID riid,

                [in] LCID lcid,

                [in] WORD wFlags,

                [in, out] DISPPARAMS * pDispParams,

                [out] VARIANT * pVarResult,

                [out] EXCEPINFO * pExcepInfo,

                [out] UINT * puArgErr

            );

 

    [call_as(Invoke)]     // 遠(yuǎn)程版函數(shù)

    HRESULT RemoteInvoke(

                [in] DISPID dispIdMember,

                [in] REFIID riid,

                [in] LCID lcid,

                [in] DWORD dwFlags,

                [in] DISPPARAMS * pDispParams,

                [out] VARIANT * pVarResult,

                [out] EXCEPINFO * pExcepInfo,

                [out] UINT * pArgErr,

                [in] UINT cVarRef,

                [in, size_is(cVarRef)] UINT * rgVarRefIdx,

                [in, out, size_is(cVarRef)] VARIANTARG * rgVarRef

            );

}

MFC 實(shí)現(xiàn)自動化組件

3-1:建立一個(gè)工作區(qū)(Workspace)
3-2
:建立一個(gè) MFC DLL 工程(Project),工程名稱為“Simple5”

3-3:一定要選擇 automation,切記!切記!

3-4:建立新類

3-5:在新建類中支持automation

Class information - Name 你隨便寫個(gè)類名子啦
Class information - Base class
一定要從 CComTarget 派生呀,只有它才提供了 IDispatch 的支持
Automation - None
表示不支持自動化,你要選擇了它,那就白干啦
Automation - Automation
支持自動化,但不能被直接實(shí)例化。后面在講解多個(gè) IDispatch 的時(shí)候就用到它了,現(xiàn)在先不要著急。
Automation - Createable by type ID
一定要選擇這個(gè)項(xiàng)目,這樣我們在后面的調(diào)用中,VB就能夠CreateObject(),VC就能夠CreateDispatch()對組件對象實(shí)例化了。注意一點(diǎn),這個(gè) ID 其實(shí)就是組件的 ProgID 啦。
    3-6
:啟動 ClassWizard,選擇 Automation 卡片,準(zhǔn)備建立函數(shù)


    3-7
:添加函數(shù)。我們要寫一個(gè)整數(shù)加法函數(shù)Add()

 

IDispatch 及雙接口的調(diào)用 雙接口表示在一個(gè)接口中,同時(shí)支持自定義接口 IDispatch

IDispatch 接口和雙接口
    使用者要想調(diào)用普通的 COM 組件功能,必須要加載這個(gè)組件的類型庫(Type library)文件 tlb(比如在 VC 中使用 #import)。然而,在腳本程序中,由于腳本是被解釋執(zhí)行的,所以無法使用加載類型庫的方式進(jìn)行預(yù)編譯。那么腳本解釋器如何使用 COM 組件那?這就是自動化(IDispatch)組件大顯身手的地方了。IDispatch 接口需要實(shí)現(xiàn)4個(gè)函數(shù),調(diào)用者只通過這4個(gè)函數(shù),就能實(shí)現(xiàn)調(diào)用自動化組件中所有的函數(shù)。這4個(gè)函數(shù)功能如下:

HRESULT GetTypeInfoCount(
    [out] UINT * pctinfo)

組件中提供幾個(gè)類型庫?當(dāng)然一般都是一個(gè)啦。
但如果你在一個(gè)組件中實(shí)現(xiàn)了多個(gè) IDispatch 接口,那就不一定啦(注1

HRESULT GetTypeInfo(
    [in] UINT iTInfo,
    [in] LCID lcid,
    [out] ITypeInfo ** ppTInfo)

調(diào)用者通過該函數(shù)取得他想要的類型庫
幸好,在 99% 的情況下,我們都不用關(guān)心這兩個(gè)函數(shù)的實(shí)現(xiàn),因?yàn)?/span> MFC/ATL 都幫我們完成了默認(rèn)的一個(gè)實(shí)現(xiàn),如果是自己完成函數(shù)代碼,甚至可以直接返回 E_NOTIMPL 表示沒有實(shí)現(xiàn)。(注2

HRESULT GetIDsOfNames(
    [in] REFIID riid,
    [in,size_is(cNames)] LPOLESTR * rgszNames,
    [in] UINT cNames,
    [in] LCID lcid,
    [out,size_is(cNames)] DISPID * rgDispId)

根據(jù)函數(shù)名稱取得函數(shù)序號,為調(diào)用 Invoke() 做準(zhǔn)備。
所謂函數(shù)序號,大家去觀察雙接口 IDL 文件和 MFC ODL 文件,每一個(gè)函數(shù)和屬性都會有 [id(序號)....] 這樣的描述。

HRESULT Invoke(
    [in] DISPID dispIdMember,
    [in] REFIID riid,
    [in] LCID lcid,
    [in] WORD wFlags,
    [in,out] DISPPARAMS * pDispParams,
    [out] VARIANT * pVarResult,
    [out] EXCEPINFO * pExcepInfo,
    [out] UINT * puArgErr)

根據(jù)序號,執(zhí)行函數(shù)
使用 MFC/ATL 寫的組件程序,我們也不必關(guān)心這個(gè)函數(shù)的實(shí)現(xiàn)。如果是自己寫代碼,則該函數(shù)類似如下實(shí)現(xiàn):
switch(dispIdMember)
{
    case 1: .....; break;
    case 2: .....; break;
    ....
}
其實(shí),就是根據(jù)序號進(jìn)行分支調(diào)用啦。(3)
 

Invoke() 函數(shù)的實(shí)現(xiàn)就可以看出,使用 IDispatch 接口的程序,其執(zhí)行效率是比較低的。ATL 從效率出發(fā),實(shí)現(xiàn)了一種叫“雙接口(dual)的接口模式。下面我們來看看,到底什么是雙接口:


圖一、雙接口(dual) 結(jié)構(gòu)示意圖

    從上圖中可以看出,所謂雙接口,其實(shí)是在一個(gè) VTAB 的虛函數(shù)表容納三個(gè)接口(因?yàn)槿魏谓涌诙际菑?/span> IUnknown 派生的,所以就不強(qiáng)調(diào) IUnknown 了,叫做雙接口)。我們?nèi)绻麖娜我庖粋€(gè)接口中調(diào)用 QueryInterface()得到另外的接口指針的話,其實(shí),得到的指針地址都是同一個(gè)。雙接口有什么好處那?答:好呀,多好呀,特別好呀......

使用方式

因?yàn)?/span>

所以

腳本語言使用組件

解釋器只認(rèn)識 IDispatch 接口

可以調(diào)用,但執(zhí)行效率最低

編譯型語言使用組件

它認(rèn)識 IDispatch 接口

可以調(diào)用,執(zhí)行效率比較低

編譯型語言使用組件

它裝載類型庫后,就認(rèn)識了 Ixxx 接口

可以直接調(diào)用 Ixxx 函數(shù),效率最高啦

結(jié)論

雙接口,既滿足腳本語言的使用方便性,又滿足編譯型語言的使用高效性。
于是,我們寫的所有的 COM 組件接口,都用雙接口實(shí)現(xiàn)嗎?
錯(cuò)!否!NO
如果不是明確非要支持腳本的調(diào)用,則最好不要使用雙接口,因?yàn)椋?/span>

如果所有函數(shù)都放在一個(gè)雙接口中,那么層次、結(jié)構(gòu)、分類不清

如果使用多個(gè)雙接口,則會產(chǎn)生其它問題(注4

雙接口、IDispatch接口只支持自動化的參數(shù)類型,使用受到限制,某些情況下很不方便嘍

還有很多弊病呦,不過現(xiàn)在我想不起來嘍......

使用方法

示例程序

自動化組件的使用方式

簡要說明

示例0

在腳本中調(diào)用

第九回/第十回中,已經(jīng)做了介紹

示例1

使用 API 方式調(diào)用

揭示 IDispatch 的調(diào)用原理,但傻子才去這么使用那,會累死了

示例2

使用 CComDispatchDriver 的智能指針包裝類

比直接使用 API 方式要簡單多啦,這個(gè)不錯(cuò)!

示例3

使用 MFC 裝載類型庫的包裝方式

簡單!好用!常用!但它本質(zhì)上是使用 IDispatch 接口,所以執(zhí)行效率稍差

示例4

使用 #import 方式加載類型庫方式

#import 方式使用組件,咱們在第七回中講過啦。常用!對雙接口組件,直接調(diào)用自定義接口函數(shù),不再經(jīng)過 IDispatch,因此執(zhí)行效率最高啦

 

錯(cuò)誤與異常處理

事件和通知

流程:
  客戶端啟動組件(Simple11.IEvent1.1)并得到接口指針 IEvent1 *
  調(diào)用接口方法 IEvent1::Advise() 把客戶端內(nèi)部的一個(gè)接收器(sink)接口指針(ICallBack *)傳遞到組件服務(wù)器中;
  調(diào)用 IEvent1::Add() 去計(jì)算兩個(gè)整數(shù)的和;
  但是計(jì)算結(jié)果并不通過該函數(shù)返回,而是通過 ICallBack::Fire_Result() 返回給客戶端;
  當(dāng)客戶端不再需要接受事件的時(shí)候,調(diào)用 IEvent1::Unadvise() 斷開和組件的聯(lián)系。

連接點(diǎn)

持續(xù)性

屬性包

posted on 2008-09-24 11:33 isabc 閱讀(896) 評論(0)  編輯 收藏 引用


只有注冊用戶登錄后才能發(fā)表評論。
網(wǎng)站導(dǎo)航: 博客園   IT新聞   BlogJava   博問   Chat2DB   管理


廣告信息(免費(fèi)廣告聯(lián)系)

中文版MSDN:
歡迎體驗(yàn)

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            欧美日本精品| 国产伊人精品| 国产精品99久久久久久久久| 欧美高清在线| 欧美大成色www永久网站婷| 欧美aⅴ一区二区三区视频| 欧美aⅴ99久久黑人专区| 亚洲国产日韩欧美在线99| 麻豆免费精品视频| 亚洲国产精品va在看黑人| 亚洲另类春色国产| 欧美亚洲综合久久| 欧美大尺度在线观看| 欧美亚日韩国产aⅴ精品中极品| 欧美日韩亚洲在线| 国产亚洲人成a一在线v站| 亚洲第一黄网| 亚洲欧美日韩在线| 免费成人你懂的| 正在播放亚洲一区| 久久视频国产精品免费视频在线| 久久精品亚洲一区| 欧美一区影院| 性做久久久久久久免费看| 亚洲欧美日韩一区在线观看| 欧美午夜不卡| 欧美性做爰猛烈叫床潮| 在线不卡a资源高清| 亚洲国产精品国自产拍av秋霞 | 老巨人导航500精品| 亚洲网站在线| 久久精品夜色噜噜亚洲aⅴ| 久久久久久999| 欧美国产日韩免费| 国产精品久久网| 亚洲国产另类精品专区| 亚洲自拍都市欧美小说| 久久这里有精品视频| 亚洲黄色天堂| 久久亚洲影院| 国产一区免费视频| 亚洲高清免费视频| 久久精品官网| 欧美一区二区精品| 欧美激情一区二区三区 | 欧美视频二区| 欧美一区二区黄| 亚洲精品国产精品乱码不99| 99香蕉国产精品偷在线观看| 免费高清在线一区| 国产午夜精品久久久久久免费视 | 亚洲欧美视频一区| 欧美视频中文字幕在线| 亚洲一区国产一区| 欧美影院在线| 欧美在线3区| 欧美激情女人20p| 欧美一区日本一区韩国一区| 国产一区二区剧情av在线| 亚洲女同在线| a91a精品视频在线观看| 欧美日韩国产在线一区| 亚洲一区图片| av成人动漫| 国产女人水真多18毛片18精品视频| 亚洲视频一区二区在线观看 | 亚洲精品久久久久久久久| 久久精品最新地址| 亚洲精品一二区| 亚洲图片欧美午夜| 国模私拍视频一区| 亚洲精品国久久99热| 国产农村妇女精品| 欧美国产大片| 国产亚洲激情在线| 米奇777超碰欧美日韩亚洲| 六月婷婷久久| 欧美激情一区二区三区在线视频| 欧美激情 亚洲a∨综合| 在线一区二区日韩| 亚洲人成网站精品片在线观看| 国产精品久久久久久久久免费桃花 | 久久一区二区三区av| 国产伦精品一区二区三区四区免费 | 久久精品视频导航| 亚洲国产视频直播| 日韩视频欧美视频| 国产精品腿扒开做爽爽爽挤奶网站| 午夜精品三级视频福利| 久久精品国产一区二区三| 亚洲精品免费一二三区| 亚洲视频在线视频| 亚洲第一黄色| 亚洲综合第一| 亚洲人永久免费| 午夜国产精品视频| 亚洲精品中文字幕女同| 午夜精品999| 亚洲精品一区二区网址| 午夜精品视频| 在线精品视频免费观看 | 亚洲黄一区二区三区| 亚洲国产精品免费| 欧美成人免费播放| 在线观看视频日韩| 亚洲午夜在线观看| 91久久精品国产91久久性色tv| 一区二区三区国产在线观看| 激情成人综合| 亚洲一区成人| 一本不卡影院| 免费不卡在线观看av| 亚洲日本aⅴ片在线观看香蕉| 麻豆av一区二区三区| 欧美亚洲在线视频| 一区二区欧美激情| 噜噜噜噜噜久久久久久91| 欧美一区二区三区另类| 欧美日韩国产色综合一二三四 | 国产亚洲欧美日韩一区二区| 亚洲精品中文字幕有码专区| 亚洲成色www久久网站| 香蕉成人伊视频在线观看| 亚洲视频免费看| 欧美福利视频在线观看| 欧美国产日韩视频| 亚洲成色www8888| 久久综合狠狠综合久久激情| 久久婷婷综合激情| 国产欧美丝祙| 午夜精品在线看| 欧美一级黄色录像| 国产精品素人视频| 亚洲欧美网站| 久久不见久久见免费视频1| 国产精品午夜在线观看| 亚洲欧美日韩成人高清在线一区| 亚洲免费中文| 国产美女一区二区| 午夜精品福利电影| 欧美一区二区三区免费看| 亚洲综合日韩| 美女诱惑一区| 狠狠色2019综合网| 午夜精品一区二区三区四区| 欧美国产日韩二区| 久久久久久久久蜜桃| 国产日韩欧美综合一区| 亚洲视频你懂的| 91久久精品一区二区三区| 午夜免费久久久久| 国产一区二区黄色| 羞羞漫画18久久大片| 亚洲网站啪啪| 国自产拍偷拍福利精品免费一| 亚洲欧美国产三级| 亚洲午夜免费视频| 国产午夜精品在线| 久久黄色小说| 久久久亚洲精品一区二区三区| 黄色成人片子| 亚洲国产精品精华液2区45| 男女精品网站| 亚洲综合视频网| 久久精品五月| 一区二区三区**美女毛片| 亚洲午夜久久久久久尤物| 在线不卡免费欧美| 亚洲理伦电影| 在线播放中文一区| 亚洲网站在线看| 亚洲国产精品成人一区二区 | 亚洲高清三级视频| 欧美激情亚洲视频| 久久精品91久久香蕉加勒比| 老牛嫩草一区二区三区日本| 亚洲天堂免费观看| 老色批av在线精品| 欧美在线观看视频一区二区三区| 久久国产高清| 欧美一区二区三区四区高清 | 亚洲精品视频在线观看免费| 国产亚洲欧美日韩精品| 亚洲少妇一区| 亚洲美女免费精品视频在线观看| 欧美一进一出视频| 亚洲欧美日韩综合| 欧美日韩视频不卡| 亚洲免费精彩视频| 在线视频你懂得一区| 欧美激情一二区| 欧美高清视频一二三区| 影音欧美亚洲| 久久久免费观看视频| 久久一综合视频| 亚洲三级电影全部在线观看高清| 久久精品国产精品| 亚洲第一在线| 一本色道久久综合亚洲精品不| 欧美成人午夜视频|