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

C++ Coder

HCP高性能計(jì)算架構(gòu),實(shí)現(xiàn),編譯器指令優(yōu)化,算法優(yōu)化, LLVM CLANG OpenCL CUDA OpenACC C++AMP OpenMP MPI

C++博客 首頁 新隨筆 聯(lián)系 聚合 管理
  98 Posts :: 0 Stories :: 0 Comments :: 0 Trackbacks

#

     摘要: 目錄(?)[-] 一、前言二、組件的啟動(dòng)和釋放三、內(nèi)存分配和釋放四、參數(shù)傳遞方向五、示例程序六、小結(jié) 本文摘自:http://www.vckbase.net/index.php/wv/1211 一、前言      同志們、朋友們、各位領(lǐng)導(dǎo),大家好。      VCKBASE 不得了,   &nb...  閱讀全文
posted @ 2012-10-17 23:13 jackdong 閱讀(577) | 評(píng)論 (0)編輯 收藏

本文摘自:http://www.vckbase.com/index.php/wv/1206

 

一、前言

上回書介紹了GUID、CLSID、IID和接口的概念。本回的重點(diǎn)是介紹 COM 中的數(shù)據(jù)類型。咋還不介紹組件程序的設(shè)計(jì)步驟呀?咳......別著急,別著急!孔子曰:“飯要一口一口地吃”;老子語:“心急吃不了熱豆腐”,孫子云:“走一步看一步吧” ...... 先掌握必要的知識(shí),將來寫起程序來才會(huì)得心應(yīng)手也:-)

走入正題之前,請(qǐng)大家牢牢記住一條原則:COM 組件是運(yùn)行在分布式環(huán)境中的。比如,你寫了一個(gè)組件程序(DLL或EXE),那么使用者可能是在本機(jī)的某個(gè)進(jìn)程內(nèi)加載組件(INPROC_SERVER);也可能是從另一個(gè)進(jìn)程中調(diào)用組件的進(jìn)程(LOCAL_SERVER);也可能是在這臺(tái)計(jì)算機(jī)上調(diào)用地球那邊計(jì)算機(jī)上的組件(REMOTE_SERVER)。所以在理解和設(shè)計(jì)的時(shí)候,要時(shí)時(shí)刻刻想起這句話。快!拿出小本本,記下來!

二、HRESULT 函數(shù)返回值

每個(gè)人在做程序設(shè)計(jì)的時(shí)候,都有他們各自的哲學(xué)思想。拿函數(shù)返回值來說,就有好多種形式。

函數(shù) 返回值 返回值信息
double sin(double)

浮點(diǎn)數(shù)值

計(jì)算正玄值
BOOL DeleteFile(LPCTSTR)

布爾值

文件刪除是否成功。如失敗,需要GetLastError()才能取得失敗原因
void * malloc(size_t)

內(nèi)存指針

內(nèi)存申請(qǐng),如果失敗,返回空指針 NULL
LONG RegDeleteKey(HKEY,LPCTSTR)

整數(shù)

刪除注冊(cè)表項(xiàng)。0表示成功,非0失敗,同時(shí)這個(gè)值就反映了失敗的原因
UINT DragQueryFile(HDROP,UINT,LPTSTR,UINT)

整數(shù)

取得拖放文件信息。以不同的參數(shù)調(diào)用,則返回不同的含義:
一會(huì)兒表示文件個(gè)數(shù),一會(huì)兒表示文件名長度,一會(huì)兒表示字符長度
...... ......

...

...... ......

 

 

如此紛繁復(fù)雜的返回值,如此含義多變的返回值,使得大家在學(xué)習(xí)和使用的過程中,增加了額外的困難。好了,COM 的設(shè)計(jì)規(guī)范終于對(duì)他們進(jìn)行了統(tǒng)一。組件API及接口指針中,除了IUnknown::AddRef()和IUnknown::Release()兩個(gè)函數(shù)外,其它所有的函數(shù),都以 HRESULT 作為返回值。大家想象一個(gè)組件的接口函數(shù)比如叫Add(),完成2個(gè)整數(shù)的加法運(yùn)算,在C語言中,我們可以如下定義:

  1. long Add( long n1, long n2 )  
  2. {  
  3. return n1 + n2;  
  4. }  

還記得剛才我們說的原則嗎?COM 組件是運(yùn)行在分布式環(huán)境中的。也就是說,這個(gè)函數(shù)可能運(yùn)行在“地球另一邊”的計(jì)算機(jī)上,既然運(yùn)行在那么遙遠(yuǎn)的地方,就有可能出現(xiàn)服務(wù)器關(guān)機(jī)、網(wǎng)絡(luò)掉線、運(yùn)行超時(shí)、對(duì)方不在服務(wù)區(qū)......等異常。于是,這個(gè)加法函數(shù),除了需要返回運(yùn)算結(jié)果以外,還應(yīng)該返回一個(gè)值------函數(shù)是否被正常執(zhí)行了。

  1. HRESULT Add( long n1, long n2, long *pSum )  
  2. {  
  3. 3*pSum = n1 + n2;  
  4.  return S_OK;  
  5. }  

如果函數(shù)正常執(zhí)行,則返回 S_OK,同時(shí)真正的函數(shù)運(yùn)行結(jié)果則通過參數(shù)指針返回。如果遇到了異常情況,則COM系統(tǒng)經(jīng)過判斷,會(huì)返回相應(yīng)的錯(cuò)誤值。常見的返回值有:

HRESULT 含義
S_OK 0x00000000 成功
S_FALSE 0x00000001 函數(shù)成功執(zhí)行完成,但返回時(shí)出現(xiàn)錯(cuò)誤
E_INVALIDARG 0x80070057 參數(shù)有錯(cuò)誤
E_OUTOFMEMORY 0x8007000E 內(nèi)存申請(qǐng)錯(cuò)誤
E_UNEXPECTED 0x8000FFFF 未知的異常
E_NOTIMPL 0x80004001 未實(shí)現(xiàn)功能
E_FAIL 0x80004005 沒有詳細(xì)說明的錯(cuò)誤。一般需要取得 Rich Error 錯(cuò)誤信息(注1)
E_POINTER 0x80004003 無效的指針
E_HANDLE 0x80070006 無效的句柄
E_ABORT 0x80004004 終止操作
E_ACCESSDENIED 0x80070005 訪問被拒絕
E_NOINTERFACE 0x80004002 不支持接口

圖一、HRESULT 的結(jié)構(gòu)

HRESULT 其實(shí)是一個(gè)雙字節(jié)的值,其最高位(bit)如果是0表示成功,1表示錯(cuò)誤。具體參見 MSDN 之"Structure of COM Error Codes"說明。我們?cè)诔绦蛑腥绻枰袛喾祷刂担瑒t可以使用比較運(yùn)算符號(hào);switch開關(guān)語句;也可以使用VC提供的宏:

  1. HRESULT hr = 調(diào)用組件函數(shù);  
  2. if( SUCCEEDED( hr ) ){...} // 如果成功  
  3. ......  
  4. if( FAILED( hr ) ){...} // 如果失敗  
  5. ......  

三、UNICODE

計(jì)算機(jī)發(fā)明后,為了在計(jì)算機(jī)中表示字符,人們制定了一種編碼,叫ASCII碼。ASCII碼由一個(gè)字節(jié)中的7位(bit)表示,范圍是0x00 - 0x7F 共128個(gè)字符。他們以為這128個(gè)數(shù)字就足夠表示abcd....ABCD....1234 這些字符了。

咳......說英語的人就是“笨”!后來他們突然發(fā)現(xiàn),如果需要按照表格方式打印這些字符的時(shí)候,缺少了“制表符”。于是又?jǐn)U展了ASCII的定義,使用一個(gè)字節(jié)的全部8位(bit)來表示字符了,這就叫擴(kuò)展ASCII碼。范圍是0x00 - 0xFF 共256個(gè)字符。

咳......說中文的人就是聰明!中國人利用連續(xù)2個(gè)擴(kuò)展ASCII碼的擴(kuò)展區(qū)域(0xA0以后)來表示一個(gè)漢字,該方法的標(biāo)準(zhǔn)叫GB-2312。后來,日文、韓文、阿拉伯文、臺(tái)灣繁體(BIG-5)......都使用類似的方法擴(kuò)展了本地字符集的定義,現(xiàn)在統(tǒng)一稱為 MBCS 字符集(多字節(jié)字符集)。這個(gè)方法是有缺陷的,因?yàn)楦鱾€(gè)國家地區(qū)定義的字符集有交集,因此使用GB-2312的軟件,就不能在BIG-5的環(huán)境下運(yùn)行(顯示亂碼),反之亦然。

咳......說英語的人終于變“聰明”一些了。為了把全世界人民所有的所有的文字符號(hào)都統(tǒng)一進(jìn)行編碼,于是制定了UNICODE標(biāo)準(zhǔn)字符集。UNICODE 使用2個(gè)字節(jié)表示一個(gè)字符(unsigned shor int、WCHAR、_wchar_t、OLECHAR)。這下終于好啦,全世界任何一個(gè)地區(qū)的軟件,可以不用修改地就能在另一個(gè)地區(qū)運(yùn)行了。雖然我用 IE 瀏覽日本網(wǎng)站,顯示出我不認(rèn)識(shí)的日文文字,但至少不會(huì)是亂碼了。UNICODE 的范圍是 0x0000 - 0xFFFF 共6萬多個(gè)字符,其中光漢字就占用了4萬多個(gè)。嘿嘿,中國人賺大發(fā)了:0)

在程序中使用各種字符集的方法:

  1. const char * p = "Hello"// 使用 ASCII 字符集  
  2. const char * p = "你好"// 使用 MBCS 字符集,由于 MBCS 完全兼容 ASCII,多數(shù)情況下,我們并不嚴(yán)格區(qū)分他們  
  3. LPCSTR p = "Hello,你好"// 意義同上  
  4.   
  5. const WCHAR * p = L"Hello,你好"// 使用 UNICODE 字符集  
  6. LPCOLESTR p = L"Hello,你好"// 意義同上  
  7.   
  8. // 如果預(yù)定義了_UNICODE,則表示使用UNICODE字符集;如果定義了_MBCS,則表示使用 MBCS  
  9. const TCHAR * p = _T("Hello,你好");   
  10. LPCTSTR p = _T("Hello,你好"); // 意義同上  

在上面的例子中,T是非常有意思的一個(gè)符號(hào)(TCHAR、LPCTSTR、LPTSTR、_T()、_TEXT()...),它表示使用一種中間類型,既不明確表示使用 MBCS,也不明確表示使用 UNICODE。那到底使用哪種字符集那?嘿嘿......編譯的時(shí)候決定吧。設(shè)置條件編譯的方式是:VC6中,"Project\Settings...\C/C++卡片 Preprocessor definitions" 中添加或修改 _MBCS、_UNICODE;VC.NET中,"項(xiàng)目\屬性\配置屬性\常規(guī)\字符集"然后用組合窗進(jìn)行選擇。使用 T 類型,是非常好的習(xí)慣,嚴(yán)重推薦!

四、BSTR

COM 中除了使用一些簡單標(biāo)準(zhǔn)的數(shù)據(jù)類型外(注2),字符串類型需要特別重點(diǎn)地說明一下。還記得原則嗎?COM 組件是運(yùn)行在分布式環(huán)境中的。通俗地說,你不能直接把一個(gè)內(nèi)存指針直接作為參數(shù)傳遞給COM函數(shù)。你想想,系統(tǒng)需要把這塊內(nèi)存的內(nèi)容傳遞到“地球另一 邊”的計(jì)算機(jī)上,因此,我至少需要知道你這塊內(nèi)存的尺寸吧?不然讓我如何傳遞呀?傳遞多少字節(jié)呀?!而字符串又是非常常用的一種類型,因此 COM 設(shè)計(jì)者引入了 BASIC 中字符串類型的表示方式---BSTR。BSTR 其實(shí)是一個(gè)指針類型,它的內(nèi)存結(jié)構(gòu)是:(輸入程序片段 BSTR p = ::SysAllocString(L"Hello,你好");斷點(diǎn)執(zhí)行,然后觀察p的內(nèi)存)

圖二、BSTR 內(nèi)存結(jié)構(gòu)

BSTR 是一個(gè)指向 UNICODE 字符串的指針,且 BSTR 向前的4個(gè)字節(jié)中,使用DWORD保存著這個(gè)字符串的字節(jié)長度( 沒有含字符串的結(jié)束符)。因此系統(tǒng)就能夠正確處理并傳送這個(gè)字符串到“地球另一 邊”了。特別需要注意的是,由于BSTR的指針就是指向 UNICODE 串,因此 BSTR 和 LPOLESTR 可以在一定程度上混用,但一定要注意:

有函數(shù) fun(LPCOLESTR lp),則你調(diào)用 BSTR p=...; fun(p); 正確

有函數(shù) fun(const BSTR bstr),則你調(diào)用 LPCOLESTR p=...; fun(p); 錯(cuò)誤!!!

有關(guān) BSTR 的處理函數(shù):

API 函數(shù) 說明
SysAllocString() 申請(qǐng)一個(gè) BSTR 指針,并初始化為一個(gè)字符串
SysFreeString() 釋放 BSTR 內(nèi)存
SysAllocStringLen() 申請(qǐng)一個(gè)指定字符長度的 BSTR 指針,并初始化為一個(gè)字符串
SysAllocStringByteLen() 申請(qǐng)一個(gè)指定字節(jié)長度的 BSTR 指針,并初始化為一個(gè)字符串
SysReAllocStringLen() 重新申請(qǐng) BSTR 指針

CString 函數(shù)

說明

AllocSysString() 從 CString 得到 BSTR
SetSysString() 重新申請(qǐng) BSTR 指針,并復(fù)制到 CString 中

CComBSTR 函數(shù)

ATL 的 BSTR 包裝類。在 atlbase.h 中定義

Append()、AppendBSTR()、AppendBytes()、ArrayToBSTR()、BSTRToArray()、AssignBSTR()、Attach()、Detach()、Copy()、CopyTo()、Empty()、Length()、ByteLength()、ReadFromStream()、WriteToStream()、LoadString()、ToLower()、ToUpper()
運(yùn)算符重載:!,!=,==,<,>,&,+=,+,=,BSTR
太多了,但從函數(shù)名稱不能看出其基本功能。詳細(xì)資料,查看MSDN 吧。另外,左側(cè)函數(shù),有很多是 ATL 7.0 提供的,VC6.0 下所帶的 ATL 3.0 不支持。
由于我們將來主要用 ATL 開發(fā)組件程序,因此使用 ATL 的 CComBSTR 為主。VC也提供了其它的包裝類 _bstr_t。

五、各種字符串類型之間的轉(zhuǎn)換

1、函數(shù) WideCharToMultiByte(),轉(zhuǎn)換 UNICODE 到 MBCS。使用范例:

  1.     LPCOLESTR lpw = L"Hello,你好";  
  2.     size_t wLen = wcslen( lpw ) + 1;  // 寬字符字符長度,+1表示包含字符串結(jié)束符  
  3.       
  4.     int aLen=WideCharToMultiByte(  // 第一次調(diào)用,計(jì)算所需 MBCS 字符串字節(jié)長度  
  5. CP_ACP,  
  6. 0,  
  7. lpw,  // 寬字符串指針  
  8. wLen, // 字符長度  
  9. NULL,  
  10. 0,  // 參數(shù)0表示計(jì)算轉(zhuǎn)換后的字符空間  
  11. NULL,  
  12. NULL);  
  13.   
  14.     LPSTR lpa = new char [aLen];  
  15.   
  16.     WideCharToMultiByte(  
  17. CP_ACP,  
  18. 0,  
  19. lpw,  
  20. wLen,  
  21. lpa,  // 轉(zhuǎn)換后的字符串指針  
  22. aLen, // 給出空間大小  
  23. NULL,  
  24. NULL);  
  25.   
  26.     // 此時(shí),lpa 中保存著轉(zhuǎn)換后的 MBCS 字符串  
  27.     ... ... ... ...  
  28.     delete [] lpa;  

2、函數(shù) MultiByteToWideChar(),轉(zhuǎn)換 MBCS 到 UNICODE。使用范例:

  1.     LPCSTR lpa = "Hello,你好";  
  2.     size_t aLen = strlen( lpa ) + 1;  
  3.       
  4.     int wLen = MultiByteToWideChar(  
  5. CP_ACP,  
  6. 0,  
  7. lpa,  
  8. aLen,  
  9. NULL,  
  10. 0);  
  11.       
  12.     LPOLESTR lpw = new WCHAR [wLen];  
  13.     MultiByteToWideChar(  
  14. CP_ACP,  
  15. 0,  
  16. lpa,  
  17. aLen,  
  18. lpw,  
  19. wLen);  
  20.     ... ... ... ...  
  21.     delete [] lpw;  

3、使用 ATL 提供的轉(zhuǎn)換宏。

A2BSTR OLE2A T2A W2A
A2COLE OLE2BSTR T2BSTR W2BSTR
A2CT OLE2CA T2CA W2CA
A2CW OLE2CT T2COLE W2COLE
A2OLE OLE2CW T2CW W2CT
A2T OLE2T T2OLE W2OLE
A2W OLE2W T2W W2T

上表中的宏函數(shù),其實(shí)非常容易記憶:

2 好搞笑的縮寫,to 的發(fā)音和 2 一樣,所以借用來表示“轉(zhuǎn)換為、轉(zhuǎn)換到”的含義。
A ANSI 字符串,也就是 MBCS。
W、OLE 寬字符串,也就是 UNICODE。
T 中間類型T。如果定義了 _UNICODE,則T表示W(wǎng);如果定義了 _MBCS,則T表示A
C const 的縮寫

使用范例:

  1. #include < atlconv.h >  
  2.   
  3. void fun()  
  4. {  
  5.     USES_CONVERSION;  // 只需要調(diào)用一次,就可以在函數(shù)中進(jìn)行多次轉(zhuǎn)換  
  6.       
  7.     LPCTSTR lp = OLE2CT( L"Hello,你好") );  
  8.     ... ... ... ...  
  9.     // 不用顯式釋放 lp 的內(nèi)存,因?yàn)?/span>  
  10.     // 由于 ATL 轉(zhuǎn)換宏使用棧作為臨時(shí)空間,函數(shù)結(jié)束后會(huì)自動(dòng)釋放棧空間。  
  11. }  

使用 ATL 轉(zhuǎn)換宏,由于不用釋放臨時(shí)空間,所以使用起來非常方便。但是考慮到棧空間的尺寸(VC 默認(rèn)2M),使用時(shí)要注意幾點(diǎn):

1、只適合于進(jìn)行短字符串的轉(zhuǎn)換;

2、不要試圖在一個(gè)次數(shù)比較多的循環(huán)體內(nèi)進(jìn)行轉(zhuǎn)換;

3、不要試圖對(duì)字符型文件內(nèi)容進(jìn)行轉(zhuǎn)換,因?yàn)槲募叽缫话闱闆r下是比較大的;

4、對(duì)情況 2 和 3,要使用 MultiByteToWideChar() 和 WideCharToMultiByte();

六、VARIANT

C++、BASIC、Java、Pascal、Script......計(jì)算機(jī)語言多種多樣,而它們各自又都有自己的數(shù)據(jù)類型,COM 產(chǎn)生目的,其中之一就是要跨語言(注3)。而 VARIANT 數(shù)據(jù)類型就具有跨語言的特性,同時(shí)它可以表示(存儲(chǔ))任意類型的數(shù)據(jù)。從C語言的角度來講,VARIANT 其實(shí)是一個(gè)結(jié)構(gòu),結(jié)構(gòu)中用一個(gè)域(vt)表示------該變量到底表示的是什么類型數(shù)據(jù),同時(shí)真正的數(shù)據(jù)則存貯在 union 空間中。結(jié)構(gòu)的定義太長了(雖然長,但其實(shí)很簡單)大家去看 MSDN 的描述吧,這里給出如何使用的簡單示例:

學(xué)生:我想用 VARIANT 表示一個(gè)4字節(jié)長的整數(shù),如何做?

老師:VARIANT v; v.vt=VT_I4; v.lVal=100;

學(xué)生:我想用 VARIANT 表示布爾值“真”,如何做?

老師:VARIANT v; v.vt=VT_BOOL; v.boolVal=VARIANT_TRUE;

學(xué)生:這么麻煩?我能不能 v.boolVal=true; 這樣寫?

老師:不可以!因?yàn)椤?

類型 字節(jié)長度 假值 真值
bool 1(char) 0(false) 1(true)
BOOL 4(int) 0(FALSE) 1(TRUE)
VT_BOOL 2(short int) 0(VARIANT_FALSE) -1(VARIANT_TRUE)

所以如果你 v.boolVal=true 這樣賦值,那么將來 if(VARIANT_TRUE==v.boolVal) 的時(shí)候會(huì)出問題(-1 != 1)。但是你注意觀察,任何布爾類型的“假”都是0,因此作為一個(gè)好習(xí)慣,在做布爾判斷的時(shí)候,不要和“真值”相比較,而要與“假值”做比較。

學(xué)生:謝謝老師,你太牛了。我對(duì)老師的敬仰如滔滔江水,連綿不絕......

學(xué)生:我想用 VARIANT 保存字符串,如何做?

老師:VARIANT v; v.vt=VT_BSTR; v.bstrVal=SysAllocString(L"Hello,你好");

學(xué)生:哦......我明白了。可是這么操作真夠麻煩的,有沒有簡單一些的方法?

老師:有呀,你可以使用現(xiàn)成的包裝類 CComVariant、COleVariant、_variant_t。比如上面三個(gè)問題就可以這樣書寫:CComVariant v1(100),v2(true),v3("Hello,你好"); 簡單了吧?!(注4)

學(xué)生:老師,我再問最后一個(gè)問題,我如何用 VARIANT 保存一個(gè)數(shù)組?

老師:這個(gè)問題很復(fù)雜,我現(xiàn)在不能告訴你,我現(xiàn)在告訴你怕你印象不深......(注5)

學(xué)生:~!@#$%^&*()......暈!

七、小結(jié)

以上所介紹的內(nèi)容,是基本功,必須熟練掌握。先到這里吧,休息一會(huì)兒......更多精彩內(nèi)容,敬請(qǐng)關(guān)注《COM 組件設(shè)計(jì)與應(yīng)用(四)》


注1:在后續(xù)的 ISupportErrorInfo 接口中介紹。

注2:常見的數(shù)據(jù)類型,請(qǐng)參考 IDL 文件的說明。(別著急,還沒寫那......嘿嘿)

注3:跨語言就是各種語言中都能使用COM組件。但啥時(shí)候能跨平臺(tái)呢?

注4:CComVariant/COlevariant/_variant_t 請(qǐng)參看 MSDN。

注5:關(guān)于安全數(shù)組 SafeArray 的使用,在后續(xù)的文章中討論。

posted @ 2012-10-17 22:52 jackdong 閱讀(445) | 評(píng)論 (0)編輯 收藏

本文摘自:http://blog.vckbase.com/teacheryang/archive/2005/06/27/8884.html

一、前言
  書接上回,話說在 doc(Word) 復(fù)合文件中,已經(jīng)解決了保存 xls(Excel) 數(shù)據(jù)的問題了。那么,接下來又要解決另一個(gè)問題:當(dāng) WORD 程序讀取復(fù)合文件,遇到了 xls 數(shù)據(jù)的時(shí)候,它該如何啟動(dòng) Excel 呢?啟動(dòng)后,又如何讓 Excel 自己去讀入、解析、顯示 xls 數(shù)據(jù)呢?

二、CLSID 概念
  有一個(gè)非常簡單的解決方案,那就是在對(duì)象數(shù)據(jù)的前面,保存有處理這個(gè)數(shù)據(jù)的程序名。(見下圖左上)


圖一、CLSID 的概念
  這的確是一個(gè)簡單的方法,但同時(shí)問題也很嚴(yán)重。在“張三”的計(jì)算機(jī)上,Excel 的路徑是:"c:\office\Excel.exe",如果把這個(gè) doc 文件復(fù)制到“李四”的計(jì)算機(jī)上使用,而“李四”的 Excel 的路徑是:
"d:\Program files\Microsoft Office\Office\Excel.exe",完蛋了:-(
  于是,微軟想出了一個(gè)解決方案,那就是不使用直接的路徑表示方法,而使用一個(gè)叫 CLSID(注1)的方式間接描述這些對(duì)象數(shù)據(jù)的處理程序路徑。CLSID 其實(shí)就是一個(gè)號(hào)碼,或者說是一個(gè)16字節(jié)的數(shù)。觀察注冊(cè)表(上圖),在HKCR\CLSID\{......}主鍵下,LocalServer32(DLL組件使用InprocServer32) 中保存著程序路徑名稱。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
#define REFCLSID const CLSID &

// 常見的聲明和賦值方法
CLSID CLSID_Excel = {0x00024500,0x0000,0x0000,{0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46}};
struct __declspec(uuid("00024500-0000-0000-C000-000000000046")) CLSID_Excel;
class DECLSPEC_UUID("00024500-0000-0000-C000-000000000046") CLSID_Excel;

// 注冊(cè)表中的表示方法
{00024500-0000-0000-C000-000000000046}

  用一個(gè)號(hào)碼間接表示程序名,的確是個(gè) Good idea,實(shí)現(xiàn)了組件位置的透明性,并方便地?cái)U(kuò)展出 DCOM(遠(yuǎn)程組件)。但,但,但,但.....CLSID 有16個(gè)字節(jié)共128位二進(jìn)制數(shù),干嗎用這么長的數(shù)字呀?遙想當(dāng)年......我還在上幼兒園的時(shí)候,人們?cè)O(shè)計(jì)了 socket,用 TCP/IP 協(xié)議進(jìn)行網(wǎng)絡(luò)通訊。每個(gè)參與通訊的計(jì)算機(jī)都有一個(gè)4字節(jié)的 IP 表示編號(hào)地址,范圍是 0,0,0,0 ~ 255,255,255,255 共42億個(gè)地址。可是沒想到啊,沒想到,自從 Internet 選擇了TCP/IP 協(xié)議后,42億個(gè)地址就不夠全世界的勞動(dòng)人民分配啦。除了勞動(dòng)人民,還有冰箱、彩電、電飯鍋、手機(jī)、手提電腦......這些都需要連網(wǎng)呀。在辦公室通過網(wǎng)絡(luò)開啟電飯鍋給我燜飯,下班回家后就能吃現(xiàn)成的啦,多幸福呀?!(注:在我們家老婆是領(lǐng)導(dǎo),所以是我做飯。咳......)
  由于前車之鑒,微軟這次設(shè)計(jì) CLSID/IID 就使用了GUID概念的16個(gè)字節(jié),這下好啦,全世界60億人口,每個(gè)人每秒鐘分配10億個(gè)號(hào)碼,那么需要分配1800億年。反正等到地球沒有了都不會(huì)使用完的:-)

三、產(chǎn)生 CLSID
  
1、
如果使用開發(fā)環(huán)境編寫組件程序,則IDE會(huì)自動(dòng)幫你產(chǎn)生 CLSID;
  2、
你可以手工寫 CLSID,但千萬不要和人家已經(jīng)生成的 CLSID 重復(fù)呀,所以嚴(yán)重地不推薦;(可是微軟的CLSID都是手工寫的,這叫“只許州官放火,不許百姓點(diǎn)燈”) ;
  3、
程序中,可以用函數(shù) CoCreateGuid() 產(chǎn)生 CLSID;
  4、
使用工具產(chǎn)生 GUID(注2);

  vc6.0版本運(yùn)行:"vc目錄\Common\Tools\GuidGen.exe"程序(你可以參照上回文章中介紹的方法,把這個(gè)工具程序加到開發(fā)環(huán)境中,方便調(diào)用)。vc.net版本,在菜單“工具\(yùn)創(chuàng)建GUID”中,就可以執(zhí)行了。

四、ProgID 概念
  每一個(gè)COM組件都需要指定一個(gè) CLSID,并且不能重名。它之所以使用16個(gè)字節(jié),就是要從概率上保證重復(fù)是“不可能”的。但是,(世界上就怕“但是”二字)微軟為了使用方便,也支持另一個(gè)字符串名稱方式,叫 ProgID(注3)。見上圖注冊(cè)表的ProgID 子鍵內(nèi)容(注4)。由于 CLSID 和 ProgID 其實(shí)是一個(gè)概念的兩個(gè)不同的表示形式,所以我們?cè)诔绦蛑锌梢噪S便使用任何一種。(有些人就是討厭,說話不算數(shù)。明明 GUID 的目的就是禁止重復(fù),但居然又允許使用 ProgID?!ProgID 是一個(gè)字符串的名字,重復(fù)的可能性就太大了呀。趕明兒我也寫個(gè)程序,我打算這個(gè)程序的 ProgID 叫“Excel.Application”,嘿嘿)下面介紹一下 CLSID 和 ProgID 之間的轉(zhuǎn)換方法和相關(guān)的函數(shù):

函數(shù) 功能說明
CLSIDFromProgID()、CLSIDFromProgIDEx() 由 ProgID 得到 CLSID。沒什么好說的,你自己都可以寫,查注冊(cè)表貝
ProgIDFromCLSID() 由 CLSID 得到 ProgID,調(diào)用者使用完成后要釋放 ProgID 的內(nèi)存(注5)
CoCreateGuid() 隨機(jī)生成一個(gè) GUID
IsEqualGUID()、IsEqualCLSID()、IsEqualIID() 比較2個(gè)ID是否相等
StringFromCLSID()、StringFromGUID2()、StringFromIID() 由 CLSID,IID 得到注冊(cè)表中CLSID樣式的字符串,注意釋放內(nèi)存

 

五、接口(Interface)的來歷
  到此,我們已經(jīng)知道了 CLSID 或 ProgID 唯一地表示一個(gè)組件服務(wù)程序,那么根據(jù)這些ID,就可以加載運(yùn)行組件,并為客戶端程序提供服務(wù)了。(啟動(dòng)組件程序的方法,會(huì)陸續(xù)介紹)。接下來先討論如何調(diào)用組件提供的函數(shù)?-----接口。
  作為客戶端程序員,它希望或者說他要求:我的程序只寫一次,然后不做任何修改就可以調(diào)用任意一個(gè)組件。舉例來說:

  1. 你可以在 Word 中嵌入 Excel,也可以嵌入 Picture,也可以嵌入任何第三方發(fā)表的 ActiveX 文檔......也就是說,連 Word 自己都不知道使用它的人將會(huì)在 doc 里面插入什么東東;

  2. 你可以在 HTML 文件中插入一個(gè) ActiveX,也可以插入一個(gè)程序腳本Script,......你自己寫的插件也可以插入到 IE 環(huán)境中。為了完成你的功能, 你絕對(duì)也不會(huì)去讓微軟修改IE吧?!

 

  這個(gè)要求實(shí)在有點(diǎn)難度,Office 開發(fā)停滯了。說來話巧,一天老O(Office 項(xiàng)目的總工程師)和小B(VB 項(xiàng)目的總工程師)一起喝酒,老O向小B傾訴了他的煩惱:
老O:怎么能讓我寫的程序C,可以調(diào)用其它人寫的程序S中的函數(shù)?(C表示客戶程序,S表示提供服務(wù)的程序)
小B:你是不是喝糊涂了?讓S作成 DLL,你去 LoadLibrary()、GetProcAddress()、...FreeLibrary()?!
老O:廢話!要是這么簡單就好了。問題是,連我都不知道這個(gè)S程序是干什么的?能干什么?我怎么調(diào)用呀?
小B:哦......這個(gè)比較高級(jí),但我現(xiàn)在不能告訴你,因?yàn)槲遗履阌∠蟛簧睢?/span>
老O:~!·#¥%……—*......
小B:是這樣的,在VB中,我們制定了一個(gè)標(biāo)準(zhǔn),這個(gè)標(biāo)準(zhǔn)允許任何一個(gè)VB開發(fā)者,把他自己寫的某個(gè)功能的小程序放在VB的工具欄上,這樣就好象他擴(kuò)展了 VB 的功能一樣。
老O:哦?就是那個(gè)叫什么 VBX 的濫玩意兒?
小B:我呸......別看 VBX 這個(gè)東西不起眼兒,的確我也沒看上它。但你猜怎么著?現(xiàn)在有成千上萬的 VB 程序愛好者把他們寫的各式各樣功能的 VBX 小程序,放到網(wǎng)上,讓大家共享那。
老O:哦~~~,那你們的這個(gè) VBX 標(biāo)準(zhǔn)是什么?
小B:嘿嘿......其實(shí)特簡單,就是在 VBX 中必須實(shí)現(xiàn)7個(gè)函數(shù),這7個(gè)函數(shù)名稱和功能必須是:初始化、釋放、顯示、消息處理......,而至于它內(nèi)部想干什么,我也管不著。我只是在需要的時(shí)候調(diào)用我需要的這7個(gè)函數(shù)。
老O:哦~~~,這樣呀......對(duì)了,我現(xiàn)有個(gè)急事,我先走了。88,你付帳吧......
小B:喂!喂喂...... 走這么急干什么,錢包都掉了:-)
  老O雖然丟了錢包,仍然興奮地沖回辦公室,他開始了思考......

1、我的程序C,要能調(diào)用任何人寫的程序B。那么B必須要按照我事先的要求,提供我需要的函數(shù)F1(),F2(),F3(),K1(),K2()。
2、BASIC 是解釋執(zhí)行,因此它的函數(shù)不用考慮書寫順序,只要給出函數(shù)名,解釋器就能找到。但我使用的是 C++呀......
3、C++編譯后的代碼中沒有函數(shù)名,只有函數(shù)地址,因此我必須改進(jìn)為用VTAB(虛函數(shù)表)表示函數(shù)入口:


圖二、VTAB 的結(jié)構(gòu)

4、還不夠好,需要改進(jìn)一下,因?yàn)樗械暮瘮?shù)地址都放在一個(gè)表中會(huì)不靈活、不好修改、不易擴(kuò)展。恩,有了!按照函數(shù)功能的類型進(jìn)行分類:


圖三、多個(gè) VTAB 的結(jié)構(gòu)

5、問題又來了,現(xiàn)在有2個(gè) VTAB 虛函數(shù)表,那么怎么能夠從一個(gè)表找到另一個(gè)表那?恩又有辦法了,我要求你必須要實(shí)現(xiàn)一個(gè)函數(shù),并且這個(gè)函數(shù)地址必須放在所有表的開頭(表中的第一個(gè)函數(shù)指針),這個(gè)函數(shù)就叫 QueryInterface()吧,完成從一個(gè)表查找到另一個(gè)表的功能:(除了QueryInterface()函數(shù),順便也完成另外兩個(gè)函數(shù),叫 AddRef() 和 Release()。這兩個(gè)函數(shù)的功能以后再說)


圖四、COM 接口結(jié)構(gòu)

6、為了以后描述方便,不再使用上圖(圖四)的方法了,而使用圖五這樣簡潔的樣式:


圖五、COM 接口結(jié)構(gòu)的簡潔圖示


六、接口(Interface)概念
1、函數(shù)是通過 VTAB 虛函數(shù)表提供其地址, 從另一個(gè)角度來看,不管用什么語言開發(fā),編譯器產(chǎn)生的代碼都能生成這個(gè)表。這樣就實(shí)現(xiàn)了組件的“二進(jìn)制特性”輕松實(shí)現(xiàn)了組件的跨語言要求。

2、假設(shè)有一個(gè)指針型變量保存著 VTAB 的首地址,則這個(gè)變量就叫“接口指針”(注6), 變量命名的時(shí)候,習(xí)慣上加上"I"開頭。另外為了區(qū)分不同的接口,每個(gè)接口 也都要有一個(gè)名字,該名字就和 CLSID 一樣,使用 GUID 方式,叫 IID。
3、接口一經(jīng)發(fā)表,就不能再修改了。不然就會(huì)出現(xiàn)向前兼容的問題。這個(gè)性質(zhì)叫“接口不變性”。
4、組件中必須有3個(gè)函數(shù),QueryInterface、AddRef、Release,它們3個(gè)函數(shù)也組成一個(gè)接口,叫"IUnknown"。(注7)
5、任何接口,其實(shí)都包含了 IUnknown 接口。隨著你接觸到更多的接口就會(huì)了更體會(huì)解到接口的另一個(gè)性質(zhì)“繼承性”。
6、在任何接口上,調(diào)用表中的第一個(gè)函數(shù),其實(shí)就是調(diào)用 QueryInterface()函數(shù),就得到你想要的另外一個(gè)接口指針。這個(gè)性質(zhì)叫“接口的傳遞性”
7、C/C++語言中需要事先對(duì)函數(shù)聲明,那么就 會(huì)要求組件也必須提供C語言的頭文件。不行!為了能使COM具有跨語言的能力,決定不再為任何語言提供對(duì)應(yīng)的函數(shù)接口聲明,而是獨(dú)立地提供一個(gè)叫類型庫(TLB)的聲明。每個(gè)語言的IDE環(huán)境自己去根據(jù)TLB生成自己語言需要的包裝。這個(gè)性質(zhì)叫“接口聲明的獨(dú)立性”(注8)

七、客戶程序與組件之間的協(xié)商調(diào)用
  
回到我們的上一個(gè)話題,Word中嵌入一個(gè)組件,那么Word是如何協(xié)商使用這個(gè)組件的那?下面是容器和組件之間的一個(gè)模擬對(duì)話過程:

  容器 協(xié)商部分 組件 應(yīng)答部分
1 根據(jù)CLSID啟動(dòng)組件 。
CoCreateInstance()
生成對(duì)象,執(zhí)行構(gòu)造函數(shù),執(zhí)行初始化動(dòng)作。
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ù)需要多大的存儲(chǔ)空間呀?
IPersistStreamInit::GetSizeMax()
恩,我算算呀......好了,總共需要500KB。
10 暈,你這么個(gè)小玩意居然占用這么大空間?!......好了,你可以存了。
IPersistStreamInit::Save()
謝謝,我已經(jīng)存好了。
11 恩。拜拜了您那。(注11)
IPersistStreamInit::Release();IUnknown::Release()
執(zhí)行析構(gòu)函數(shù),刪除對(duì)象。
12 我自己也該退出了......
PostQuitMessage()
 

  容器(或者說客戶端)就是這樣和組件進(jìn)行對(duì)話,協(xié)商調(diào)用的。如果組件甲實(shí)現(xiàn)了 IA 接口,那么容器就會(huì)使用它,如果組件乙沒有提供 IA 接口,但是它提供了 IB 接口,那么容器就會(huì)調(diào)用 IB 接口的函數(shù)......如此,容器程序根本就不需要知道組件到底是干什么的,組件到底是用什么語言開發(fā)的,組件的磁盤位置到底在哪里,它都可以正常運(yùn)行。太奇妙了!太精彩了!怎一個(gè)“爽”字了得!

八、小結(jié)
  第二回中,介紹了兩個(gè)非常重要的概念:CLSID 和 Interface。由于全篇都是概念描述而沒有示例程序相配合,可能讀者的理解還不太深入、不徹底。別著急,我們馬上就要進(jìn)入到組件程序設(shè)計(jì)階段了,到那個(gè)時(shí)候,你根據(jù)具體的程序代碼,再回過頭來再次閱讀本回文章,沒讀懂?哦......再讀!慢慢地您老人家就懂了:-)

留作業(yè)啦......
1、IDispatch 接口的 IID 是多少?(哎~~~ 笨笨,在源程序中,用鼠標(biāo)右鍵執(zhí)行Go to definition 呀)
2、IPicture 接口有幾個(gè)函數(shù)?功能是什么?(別玩了!你多大了?想不想在程序中顯示 JPG 圖像呀,看 MSDN 去)
  想知道為什么COM函數(shù)總是返回 HRESULT 嗎?想知道如何使用 BSTR、VARIANT 嗎?想知道 COM 中應(yīng)該如何使用內(nèi)存嗎?想知道如何使用 UNICODE 嗎?......恩~~~,我現(xiàn)在不能告訴你,我現(xiàn)在告訴你,怕你印象不深!且聽下回分解......

 


 

注1:CLSID = Class ID 上回書已經(jīng)介紹了把CLSID寫入復(fù)合文件的函數(shù):WriteClassStg()、IStorage::SetClass()。
注2:GUID 全局唯一標(biāo)示符,CLSID/IID 其實(shí)是借用了GUID的概念。
注3:ProgID = Program ID,等價(jià)于 CLSID, 是用字符串表示的。
注4:注冊(cè)表子鍵 ProgID 和 VersionIndependentProgID 分別表示真正的 ProgID 和版本無關(guān)的 ProgID。比如在我計(jì)算機(jī)上安裝的 Excel,它的 ProgID = "Excel.Application.9",而 VersionIndependentProgID = "Excel.Application"。
注5:COM 組件的內(nèi)存管理,見后續(xù)的文章。
注6:Interface = 接口,以前微軟不叫它接口,而叫協(xié)議Protocol。其實(shí)我 到認(rèn)為這個(gè)詞更貼切一些。
注7:IUnknown 這個(gè)名字起的好,居然叫“我不知道”:-),它的 IID 叫 IID_IUnknown,如果用注冊(cè)表樣式表示,那么它的值是{00000000-0000-0000-C000-000000000046}。
注8:TLB是由一個(gè)描述接口的文件 IDL 經(jīng)過編譯產(chǎn)生的。IDL 的說明,見后續(xù)的文章吧。
注9:IPersistStorage 是用復(fù)合文件的存儲(chǔ)(Storage)功能來保存/讀取數(shù)據(jù)用的一個(gè)接口。
注10:IPersistStreamInit 是用復(fù)合文件的流(Stream)功能來保存/讀取數(shù)據(jù)用的一個(gè)接口。
注11:拜拜了您那 = 英語北京話,再見。

posted @ 2012-10-17 22:33 jackdong 閱讀(508) | 評(píng)論 (0)編輯 收藏

本文摘自:http://blog.vckbase.com/teacheryang/archive/2005/06/27/8883.html

一、前言
  公元一九九五年某個(gè)夜黑風(fēng)高的晚上,我的一位老師跟我說:“小楊呀,以后寫程序就和搭積木一樣啦。你趕快學(xué)習(xí)一些OLE的技術(shù)吧......”,當(dāng)時(shí)我心里就尋思 :“開什么玩笑?搭積木方式寫程序?再過100年吧......”,但作為一名聽話的好學(xué)生,我開始在書店里“踅摸”(注1)有關(guān)OLE的書籍(注2)。功夫不負(fù)有心人,終于買到了我的第一本COM書《OLE2 高級(jí)編程技術(shù)》,這本800多頁的大布頭花費(fèi)了我1/5的月工資呀......于是開始日夜耕讀.....
功夫不負(fù)有心人,我堅(jiān)持讀完了全部著作,感想是:這本書,在說什么吶?
功夫不負(fù)有心人,我又讀完了一遍大布頭,感想是:咳~~~,沒懂!
功夫不負(fù)有心人,我再,我再,我再讀 ... 感想是:哦~~~,讀懂了一點(diǎn)點(diǎn)啦,哈哈哈。
...... ......
功夫不負(fù)有心人,我終于,我終于懂了。
800頁的書對(duì)現(xiàn)在的我來說,其實(shí)也就10幾頁有用。到這時(shí)候才體會(huì)出什么叫“書越讀越薄”的道理了。到后來,能買到的書也多了,上網(wǎng)也更方便更便宜了......
  為了讓VCKBASE上的朋友,不再經(jīng)歷我曾經(jīng)的痛苦、不再重蹈我“無頭蒼蠅”般探索的艱辛、為了VCKBASE的蓬勃發(fā)展、為了中國軟件事業(yè)的騰飛(糟糕,吹的太也高了)......我打算節(jié)約一些在 BBS 上賺分的時(shí)間,寫個(gè)系列論文,就叫“COM組件設(shè)計(jì)與應(yīng)用”吧。今天是第一部分——起源。

二、文件的存儲(chǔ)
  傳說350年前,牛頓被蘋果砸到了頭,于是發(fā)現(xiàn)了萬有引力。但到了二十一世紀(jì)的現(xiàn)在,任何一個(gè)技術(shù)的發(fā)明和發(fā)展,已經(jīng)不再依靠圣人靈光的一閃。技術(shù)的進(jìn)步轉(zhuǎn)而是被社會(huì)的需求、商業(yè)的利益、競爭的壓力、行業(yè)的滲透等推動(dòng)的。微軟在Windows平臺(tái)上的組件技術(shù)也不例外,它的發(fā)明,有其必然因素。什么是這個(gè)因素那?答案是——文件的存儲(chǔ)。
  打開記事本程序,輸入了一篇文章后,保存。——這樣的文件叫“非結(jié)構(gòu)化文件”;
  打開電子表格程序,輸入一個(gè)班的學(xué)生姓名和考試成績,保存。——這樣的文件叫“標(biāo)準(zhǔn)結(jié)構(gòu)化文件”;
  在我們寫的程序中,需要把特定的數(shù)據(jù)按照一定的結(jié)構(gòu)和順序?qū)懙轿募斜4妗?#8212;—這樣的文件叫“自定義結(jié)構(gòu)化文件”;(比如 *.bmp 文件)
  以上三種類型的文件,大家都見的多了。那么文件存儲(chǔ)就依靠上述的方式能滿足所有的應(yīng)用需求嗎?恩~~~,至少從計(jì)算機(jī)發(fā)明后的50多年來,一直是夠用的了。嘿嘿,下面看看商業(yè)利益的推動(dòng)作用,對(duì)文件 的存儲(chǔ)形式產(chǎn)生了什么變化吧。30歲以上的朋友,我估計(jì)以前都使用過以下幾個(gè)著名的軟件:WordStar(獨(dú)霸DOS下的英文編輯軟件),WPS(裘伯君寫的中文編輯軟件,據(jù)說當(dāng)年的市場占有率高達(dá)90%,各種計(jì)算機(jī)培訓(xùn)班的必修課程),LOTUS-123(蓮花公司出品的電子表格軟件)......
微軟在成功地推出 Windows 3.1 后,開始垂涎桌面辦公自動(dòng)化軟件領(lǐng)域。微軟的 OFFICE 開發(fā)部門,各小組分別獨(dú)立地開發(fā)了 WORD 和 EXCEL 等軟件,并采用“自定義結(jié)構(gòu)”方式,對(duì)文件進(jìn)行存儲(chǔ)。在激烈的市場競爭下,為了打敗競爭對(duì)手,微軟自然地產(chǎn)生了一個(gè)念頭------如果我能在 WORD 程序中嵌入 EXCEL,那么用戶在購買了我 WORD 軟件的情況下,不就沒有必要再買 LOTUS-123 了嗎?!“惡毒”(中國微軟的同志們看到了這個(gè)詞,不要激動(dòng),我是加了引號(hào)的呀)的計(jì)劃產(chǎn)生后,他們開始了實(shí)施工作,這就是 COM 的前身 OLE 的起源(注3)。但立刻就遇到了一個(gè)嚴(yán)重的技術(shù)問題:需要把 WORD 產(chǎn)生的 DOC 文件和 EXCEL 產(chǎn)生的 XLS 文件保存在一起。

方案

優(yōu)點(diǎn)

缺點(diǎn)

建立一個(gè)子目錄,把 DOC、XLS 存儲(chǔ)在這同一個(gè)子目錄中。 數(shù)據(jù)隔離性好,WORD 不用了解 EXCEL 的存儲(chǔ)結(jié)構(gòu);容易擴(kuò)展。 結(jié)構(gòu)太松散,容易造成數(shù)據(jù)的損壞或丟失。
不易攜帶。
修改文件存儲(chǔ)結(jié)構(gòu),在DOC結(jié)構(gòu)基礎(chǔ)上擴(kuò)展出包容 XLS 的結(jié)構(gòu)。 結(jié)構(gòu)緊密,容易攜帶和統(tǒng)一管理。 WORD 的開發(fā)人員需要通曉 EXCEL 的存儲(chǔ)格式;缺少擴(kuò)展性,總不能新加一個(gè)類型就擴(kuò)展一下結(jié)構(gòu)吧?!

  以上兩個(gè)方案,都有嚴(yán)重的缺陷,怎么解決那?如果能有一個(gè)新方案,能夠合并前兩個(gè)方案的優(yōu)點(diǎn),消滅缺點(diǎn),該多好呀......微軟是作磁盤操作系統(tǒng)起家的,于是很自然地他們提出了一個(gè)非常完美的設(shè)計(jì)方案,那就是把磁盤文件的管理方式移植到文件中了------復(fù)合文件,俗稱“文件中的文件系統(tǒng)”。連微軟當(dāng)年都沒有想到,就這么一個(gè)簡單的想法,居然最后就演變出了 COM 組件程序設(shè)計(jì)的方法。可以說,復(fù)合文件是 COM 的基石。下圖是磁盤文件組織方式與復(fù)合文件組織方式的類比圖:


圖一、左側(cè)表示一個(gè)磁盤下的文件組織方式,右側(cè)表示一個(gè)復(fù)合文件內(nèi)部的數(shù)據(jù)組織方式。

三、復(fù)合文件的特點(diǎn)
  1、
復(fù)合文件的內(nèi)部是使用指針構(gòu)造的一棵樹進(jìn)行管理的。編寫程序的時(shí)候要注意,由于使用的是單向指針,因此當(dāng)做定位操作的時(shí)候,向后定位比向前定位要快;
  2、
復(fù)合文件中的“流對(duì)象”,是真正保存數(shù)據(jù)的空間。它的存儲(chǔ)單位為512字節(jié)。也就是說,即使你在流中只保存了一個(gè)字節(jié)的數(shù)據(jù),它也要占據(jù)512字節(jié)的文件空間。啊~~~,這也太浪費(fèi)了呀?不浪費(fèi)!因?yàn)槲募4嬖诖疟P上,即使一個(gè)字節(jié)也還要占用一個(gè)“簇”的空間那;
  3、
不同的進(jìn)程,或同一個(gè)進(jìn)程的不同線程可以同時(shí)訪問一個(gè)復(fù)合文件的不同部分而互不干擾;
  4、
大家都有這樣的體會(huì),當(dāng)需要往一個(gè)文件中插入一個(gè)字節(jié)的話,需要對(duì)整個(gè)文件進(jìn)行操作,非常煩瑣并且效率低下。而復(fù)合文件則提供了非常方便的“增量訪問”能力;
  5、當(dāng)頻繁地刪除文件,復(fù)制文件后,磁盤空間會(huì)變的很零碎,需要使用磁盤整理工具進(jìn)行重新整合。和磁盤管理非常相似,復(fù)合文件也會(huì)產(chǎn)生這個(gè)問題,在適當(dāng)?shù)臅r(shí)候也需要整理,但比較簡單,只要調(diào)用一個(gè)函數(shù)就可以完成了。

四、瀏覽復(fù)合文件
  VC6.0 附帶了一個(gè)工具軟件“復(fù)合文件瀏覽器”,文件名是“vc目錄\Common\Tools\DFView.exe”。為了方便使用該程序,可以把它加到工具(tools)菜單中。方法是:Tools\Customize...\Tools卡片中增加新的項(xiàng)目。運(yùn)行 DFView.exe,就可以打開一個(gè)復(fù)合文件進(jìn)行觀察了(注4)。但奇怪的是,在 Microsoft Visual Studio .NET 2003 中,我反而找不到這個(gè)工具程序了,汗!不過這恰好提供給大家一個(gè)練習(xí)的機(jī)會(huì),在你閱讀完本篇文章并掌握了編程方法后,自己寫一個(gè)“復(fù)合文件瀏覽編輯器”程序,又練手了,還有實(shí)用的價(jià)值。

五、復(fù)合文件函數(shù)
  復(fù)合文件的函數(shù)和磁盤目錄文件的操作非常類似。所有這些函數(shù),被分為3種類型:WIN API 全局函數(shù),存儲(chǔ) IStorage 接口函數(shù),流 IStream 接口函數(shù)。什么是接口?什么是接口函數(shù)?以后的文章中再陸續(xù)介紹,這里大家只要把“接口”看成是完成一組相關(guān)操作功能的函數(shù)集合就可以了。

WIN API 函數(shù)

功能說明

StgCreateDocfile() 建立一個(gè)復(fù)合文件,得到根存儲(chǔ)對(duì)象
StgOpenStorage() 打開一個(gè)復(fù)合文件,得到根存儲(chǔ)對(duì)象
StgIsStorageFile() 判斷一個(gè)文件是否是復(fù)合文件

 

IStorage 函數(shù)

功能說明

CreateStorage() 在當(dāng)前存儲(chǔ)中建立新存儲(chǔ),得到子存儲(chǔ)對(duì)象
CreateStream() 在當(dāng)前存儲(chǔ)中建立新流,得到流對(duì)象
OpenStorage() 打開子存儲(chǔ),得到子存儲(chǔ)對(duì)象
OpenStream() 打開流,得到流對(duì)象
CopyTo() 復(fù)制存儲(chǔ)下的所有對(duì)象到目標(biāo)存儲(chǔ)中,該函數(shù)可以實(shí)現(xiàn)“整理文件,釋放碎片空間”的功能
MoveElementTo() 移動(dòng)對(duì)象到目標(biāo)存儲(chǔ)中
DestoryElement() 刪除對(duì)象
RenameElement() 重命名對(duì)象
EnumElements() 枚舉當(dāng)前存儲(chǔ)中所有的對(duì)象
SetElementTimes() 修改對(duì)象的時(shí)間
SetClass() 在當(dāng)前存儲(chǔ)中建立一個(gè)特殊的流對(duì)象,用來保存CLSID(注5)
Stat() 取得當(dāng)前存儲(chǔ)中的系統(tǒng)信息
Release() 關(guān)閉存儲(chǔ)對(duì)象
 

IStream 函數(shù)

功能說明

Read() 從流中讀取數(shù)據(jù)
Write() 向流中寫入數(shù)據(jù)
Seek() 定位讀寫位置
SetSize() 設(shè)置流尺寸。如果預(yù)先知道大小,那么先調(diào)用這個(gè)函數(shù),可以提高性能
CopyTo() 復(fù)制流數(shù)據(jù)到另一個(gè)流對(duì)象中
Stat() 取得當(dāng)前流中的系統(tǒng)信息
Clone() 克隆一個(gè)流對(duì)象,方便程序中的不同模塊操作同一個(gè)流對(duì)象
Release() 關(guān)閉流對(duì)象
 
WIN API 補(bǔ)充函數(shù) 功能說明
WriteClassStg() 寫CLSID到存儲(chǔ)中,同IStorage::SetClass()
ReadClassStg() 讀出WriteClassStg()寫入的CLSID,相當(dāng)于簡化調(diào)用IStorage::Stat()
WriteClassStm() 寫CLSID到流的開始位置
ReadClassStm() 讀出WriteClassStm()寫入的CLSID
WriteFmtUserTypeStg() 寫入用戶指定的剪貼板格式和名稱到存儲(chǔ)中
ReadFmtUserTypeStg() 讀出WriteFmtUserTypeStg()寫入的信息。方便應(yīng)用程序快速判斷是否是它需要的格式數(shù)據(jù)。
CreateStreamOnHGlobal() 內(nèi)存句柄 HGLOBAL 轉(zhuǎn)換為流對(duì)象
GetHGlobalFromStream() 取得CreateStreamOnHGlobal()調(diào)用中使用的內(nèi)存句柄

  為了讓大家快速地瀏覽和掌握基本方法,上面所列表的函數(shù)并不是全部,我省略了“事務(wù)”函數(shù)和未實(shí)現(xiàn)函數(shù)部分。更全面的介紹,請(qǐng)閱讀 MSDN。
下面程序片段,演示了一些基本函數(shù)功能和調(diào)用方法。
示例一:建立一個(gè)復(fù)合文件,并在其下建立一個(gè)子存儲(chǔ),在該子存儲(chǔ)中再建立一個(gè)流,寫入數(shù)據(jù)。

  1. void SampleCreateDoc()  
  2. {  
  3.   ::CoInitialize(NULL);   // COM 初始化  
  4.                // 如果是MFC程序,可以使用AfxOleInit()替代  
  5.   HRESULT hr;   // 函數(shù)執(zhí)行返回值  
  6.   IStorage *pStg = NULL; // 根存儲(chǔ)接口指針  
  7.   IStorage *pSub = NULL; // 子存儲(chǔ)接口指針  
  8.   IStream *pStm = NULL; // 流接口指針  
  9.   
  10.   hr = ::StgCreateDocfile(  // 建立復(fù)合文件  
  11.         L"c:\\a.stg"// 文件名稱  
  12.         STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE, // 打開方式  
  13.         0, // 保留參數(shù)  
  14.         &pStg); // 取得根存儲(chǔ)接口指針  
  15.   ASSERT( SUCCEEDED(hr) ); // 為了突出重點(diǎn),簡化程序結(jié)構(gòu),所以使用了斷言。  
  16.                  // 在實(shí)際的程序中則要使用條件判斷和異常處理  
  17.   
  18.   hr = pStg->CreateStorage( // 建立子存儲(chǔ)  
  19.         L"SubStg"// 子存儲(chǔ)名稱  
  20.         STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE,  
  21.         0,0,  
  22.         &pSub); // 取得子存儲(chǔ)接口指針  
  23.   ASSERT( SUCCEEDED(hr) );  
  24.   
  25.   hr = pSub->CreateStream(  // 建立流  
  26.         L"Stm"// 流名稱  
  27.         STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE,  
  28.         0,0,  
  29.         &pStm); // 取得流接口指針  
  30.   ASSERT( SUCCEEDED(hr) );  
  31.   
  32.   hr = pStm->Write( // 向流中寫入數(shù)據(jù)  
  33.         "Hello"// 數(shù)據(jù)地址  
  34.         5, // 字節(jié)長度(注意,沒有寫入字符串結(jié)尾的\0)  
  35.         NULL); // 不需要得到實(shí)際寫入的字節(jié)長度  
  36.   ASSERT( SUCCEEDED(hr) );  
  37.   
  38.   if( pStm ) pStm->Release(); // 釋放流指針  
  39.   if( pSub ) pSub->Release(); // 釋放子存儲(chǔ)指針  
  40.   if( pStg ) pStg->Release(); // 釋放根存儲(chǔ)指針  
  41.   
  42.   ::CoUninitialize() // COM 釋放  
  43.            // 如果使用 AfxOleInit(),則不調(diào)用該函數(shù)  
  44. }  

 


圖二、運(yùn)行示例程序一后,使用 DFView.exe 打開觀察復(fù)合文件的效果圖

示例二:打開一個(gè)復(fù)合文件,枚舉其根存儲(chǔ)下的所有對(duì)象。

  1. #include // ANSI、MBCS、UNICODE 轉(zhuǎn)換  
  2. void SampleEnum()   
  3. {   
  4.   // 假設(shè)你已經(jīng)做過 COM 初始化了  
  5.   LPCTSTR lpFileName = _T( "c:\\a.stg" );  
  6.   HRESULT hr;  
  7.   IStorage *pStg = NULL;  
  8.   
  9.   USES_CONVERSION; // (注6)  
  10.   LPCOLESTR lpwFileName = T2COLE( lpFileName ); // 轉(zhuǎn)換T類型為寬字符  
  11.   hr = ::StgIsStorageFile( lpwFileName ); // 是復(fù)合文件嗎?  
  12.   if( FAILED(hr) )  
  13.     return;  
  14.   hr = ::StgOpenStorage( // 打開復(fù)合文件  
  15.         lpwFileName, // 文件名稱  
  16.         NULL,  
  17.         STGM_READ | STGM_SHARE_DENY_WRITE,  
  18.         0,  
  19.         0,  
  20.         &pStg); // 得到根存儲(chǔ)接口指針  
  21.   IEnumSTATSTG *pEnum=NULL; // 枚舉器  
  22.   hr = pStg->EnumElements( 0, NULL, 0, &pEnum );  
  23.   ASSERT( SUCCEEDED(hr) );  
  24.   STATSTG statstg;  
  25.   while( NOERROR == pEnum->Next( 1, &statstg, NULL) )  
  26.   {  
  27.     // statstg.type 保存著對(duì)象類型 STGTY_STREAM 或 STGTY_STORAGE  
  28.     // statstg.pwcsName 保存著對(duì)象名稱  
  29.     // ...... 還有時(shí)間,長度等很多信息。請(qǐng)查看 MSDN  
  30.     ::CoTaskMemFree( statstg.pwcsName ); // 釋放名稱所使用的內(nèi)存(注6)  
  31.   }  
  32.   
  33.   if( pEnum ) pEnum->Release();  
  34.   if( pStg ) pStg->Release();  
  35. }  

 

六、小結(jié)
  復(fù)合文件,結(jié)構(gòu)化存儲(chǔ),是微軟組件思想的起源,在此基礎(chǔ)上繼續(xù)發(fā)展出了持續(xù)性、命名、ActiveX、對(duì)象嵌入、現(xiàn)場激活......一系列的新技術(shù)、新概念。因此理解和掌握 復(fù)合文件是非常重要的,即使在你的程序中并沒有全面使用組件技術(shù),復(fù)合文件技術(shù)也是可以單獨(dú)被應(yīng)用的。祝大家學(xué)習(xí)快樂,為社會(huì)主義軟件事業(yè)而奮斗:-)

留作業(yè)啦......
作業(yè)1:寫個(gè)小應(yīng)用程序,從 MSWORD 的 doc 文件中,提取出附加信息(作者、公司......)。
作業(yè)2:寫個(gè)全功能的“復(fù)合文件瀏覽編輯器”。

注1:踅摸(xuemo),動(dòng)詞,北方方言,尋找搜索的意思。
注2:問:為什么不上網(wǎng)查資料學(xué)習(xí)?
答:開什么國際玩笑!在那遙遠(yuǎn)的1995年代,我的500塊工資,不吃不喝正好夠上100小時(shí)的Internet網(wǎng)。
注3:OLE,對(duì)象的連接與嵌入。
注4:可以用 DFView.exe 打開 MSWORD 的 DOC 文件進(jìn)行復(fù)合文件的瀏覽。但是該程序并沒有實(shí)現(xiàn)國際化,不能打開中文文件名的復(fù)合文件,因此需要改名后才能瀏覽。
注5:CLSID,在后續(xù)的文章中介紹。
注6:關(guān)于 COM 中內(nèi)存使用的問題,在后續(xù)的文章中介紹。

posted @ 2012-10-17 22:23 jackdong 閱讀(419) | 評(píng)論 (0)編輯 收藏

      (為了方便大家下,我打包了放在一下地址:

1-6:http://download.csdn.net/detail/wangqiulin123456/4601530

7-12:http://download.csdn.net/detail/wangqiulin123456/4601508

13-16:http://download.csdn.net/detail/wangqiulin123456/4601493

          來自微軟的權(quán)威技術(shù)專家將向您解釋W(xué)indows操作系統(tǒng)的內(nèi)部工作原理,從系統(tǒng)架構(gòu)的大局觀出發(fā),逐步展示進(jìn)程、線程、安全機(jī)制、內(nèi)存管理和存儲(chǔ)管理等子系統(tǒng)的工作方式。通過對(duì)底層原理的揭示,使您更進(jìn)一步的理解Windows上各類程序的工作方式和如何進(jìn)行錯(cuò)誤診斷及性能優(yōu)化。 本次課程的內(nèi)容編排得到了國內(nèi)知名技術(shù)作家,《Windows Internals》一書的中文譯者,潘愛民先生的大力支持,同時(shí)TechNet也邀請(qǐng)到了眾多微軟一線技術(shù)專家進(jìn)行講解。這是一個(gè)為IT專業(yè)人員量身定做的Windows內(nèi)部知識(shí)課程,在介紹原理的同時(shí),也緊密地圍繞實(shí)際案例和常見的故障進(jìn)行分析點(diǎn)評(píng)。這是一個(gè)系統(tǒng)的學(xué)習(xí)Windows底層工作機(jī)制的好機(jī)會(huì),課程內(nèi)容深入淺出,精彩紛呈,絕對(duì)不容錯(cuò)過。

深入研究Windows內(nèi)部原理系列之一:Windows的昨天、今天和明天
 

講師信息:潘愛民
2007年01月25日 14:00-15:30
Level: 300

著名技術(shù)作家、微軟亞洲研究院研究員潘愛民老師將在這次課程中跟聽眾分享Windows的發(fā)展歷程和技術(shù)精萃,描繪操作系統(tǒng)的體系架構(gòu)、Vista的內(nèi)核變更以及今后版本W(wǎng)indows的發(fā)展趨勢(shì)。

深入研究Windows內(nèi)部原理系列之二:Windows體系結(jié)構(gòu)-從操作系統(tǒng)的角度
 

講師信息:張銀奎
2007年01月26日 14:00-15:30
Level: 400

操作系統(tǒng)是計(jì)算機(jī)系統(tǒng)的靈魂和管理中心,也是軟件系統(tǒng)中最復(fù)雜的部分。本講座將以生動(dòng)的講解和豐富的演示帶您領(lǐng)略Windows操作系統(tǒng)的核心架構(gòu)和主要組件,包括HAL、內(nèi)核、執(zhí)行體、系統(tǒng)進(jìn)程(IDLE、SMSS.EXE、WinLogon.EXE)和Windows子系統(tǒng)(CSRSS.EXE、WIN32K.SYS以及子系統(tǒng)DLL)等。并討論中斷管理、對(duì)象管理、和異常分發(fā)等系統(tǒng)機(jī)制和實(shí)現(xiàn)這些機(jī)制的基本數(shù)據(jù)結(jié)構(gòu)。

深入研究Windows內(nèi)部原理系列之三:Windows體系結(jié)構(gòu)-從應(yīng)用程序的角度
 

講師信息:曾震宇
2007年01月29日 14:00-15:30
Level: 400

從服務(wù)器軟件到Office辦公應(yīng)用,從聯(lián)網(wǎng)游戲到即時(shí)消息,不管這些應(yīng)用的復(fù)雜程度如何,他們都是一個(gè)個(gè)在操作系統(tǒng)控制和管理之下的可執(zhí)行程序。本次課程邀請(qǐng)微軟全球技術(shù)中心專家級(jí)工程師,為各位講解一個(gè)程序是如何經(jīng)歷從啟動(dòng)、分配資源、運(yùn)行、結(jié)束這一連串的過程,并且介紹其中的重要概念和排錯(cuò)診斷技巧。

深入研究Windows內(nèi)部原理系列之四:Windows操作系統(tǒng)中的重要基本概念
 

講師信息:高宇
2007年01月30日 14:00-15:30
Level: 400

進(jìn)程、線程、資源分配、內(nèi)存管理、Win32 API、服務(wù)、安全,這些是工作中常常提及但是又無法深入理解的神秘概念。在這次課程中,講師將介紹Windows中最常見與最重要的一些基本概念. 使大家能夠順利地參與到本系列之后的討論中去。

深入研究Windows內(nèi)部原理系列之五:Windows Sysinternals工具集介紹
 

講師信息:彭愛華
2007年01月31日 14:00-15:30
Level: 400

Sysinternals Suite(Windows Sysinternals工具集)包含一系列免費(fèi)的系統(tǒng)工具,其中有大名鼎鼎的Process Explorer、FileMon、RegMon等(在Windows Vista下,F(xiàn)ileMon和RegMon則被Process Monitor所代替),如果把系統(tǒng)管理員比喻成戰(zhàn)士的話,那么Sysinternals Suite就是我們手中的良兵利器。熟悉和掌握這些工具,并且對(duì)Windows的體系有一定的了解,將大幅度的提高日常的診斷和排錯(cuò)能力。本課程將以任務(wù)驅(qū)動(dòng)的模式,介紹幾個(gè)經(jīng)典的應(yīng)用案例,來介紹Sysinternals Suite的強(qiáng)大功能。

深入研究Windows內(nèi)部原理系列之六:Vista新特性底層揭秘
 

講師信息:彭愛華
2007年02月01日 14:00-15:30
Level: 400

Windows Vista絕非僅僅是具有諸如3D切換、毛玻璃等炫目的界面效果,花錢購買了Windows Vista,而僅僅為了使用其界面效果,難免有點(diǎn)“買櫝還珠”的感覺。實(shí)際上Windows Vista值得稱道的是它具有很多全新的安全特性,例如用戶帳戶控制、IE保護(hù)模式、服務(wù)隔離和Windows資源保護(hù)等等。有了這些全新的安全特性,我們就可以在相當(dāng)?shù)某潭壬蠑[脫惡意軟件的滋擾。Windows之父Jim Allchin曾經(jīng)說過不要滿足于只知道How-to、小技巧之類的知識(shí),而是應(yīng)該深入底層了解其內(nèi)部原理。只有了解了這些安全特性的內(nèi)在原理,才能真正了解Windows Vista是怎樣精心替我們解決安全問題的,才能真正利用好這些安全特性。本課程將以UAC、IE保護(hù)模式為例,介紹這些安全特性的內(nèi)在原理。

深入研究Windows內(nèi)部原理系列之七:開機(jī)引導(dǎo)過程
 

講師信息:張銀奎
2007年02月02日 14:00-15:30
Level: 400

Windows的啟動(dòng)是一個(gè)復(fù)雜的過程,從加載器(NTLDR或WinLoad)開始工作到Windows子系統(tǒng)準(zhǔn)備就緒,中間經(jīng)歷了若干個(gè)復(fù)雜的步驟,包括內(nèi)核和執(zhí)行體的初始化,創(chuàng)建系統(tǒng)進(jìn)程和線程,對(duì)象管理器初始化基本對(duì)象,I/O管理器枚舉設(shè)備并安裝驅(qū)動(dòng)程序,啟動(dòng)SMSS和WinLogon進(jìn)程,運(yùn)行Windows子系統(tǒng)進(jìn)程。本講座將解析以上各個(gè)步驟的來龍去脈,并探討驅(qū)動(dòng)的加載順序、用戶登錄(Gina,SAM數(shù)據(jù)庫,域身份驗(yàn)證)、系統(tǒng)服務(wù)程序、Shell等等啟動(dòng)過程密切相關(guān)的問題。

深入研究Windows內(nèi)部原理系列之八:內(nèi)存管理揭秘
 

講師信息:徐曉卓
2007年02月05日 14:00-15:30
Level: 400

工欲善其事,必先利其器。如果能夠深入了解Windows內(nèi)存管理機(jī)制,那么無論在系統(tǒng)配置還是在故障排錯(cuò)方面,都能讓我們直達(dá)根源,起到事半功倍的效果。本課程將全面介紹Windows內(nèi)部內(nèi)存管理機(jī)制,包括尋址原理、進(jìn)程內(nèi)存空間分布、核心態(tài)用戶態(tài)內(nèi)存管理原理以及虛擬內(nèi)存管理原理等。同時(shí)將討論應(yīng)用程序中內(nèi)存的使用問題,內(nèi)存泄露的發(fā)生以及排除方法。

深入研究Windows內(nèi)部原理系列之九:Windows的安全機(jī)制和實(shí)現(xiàn)
 

講師信息:張瞰
2007年02月06日 14:00-15:30
Level: 400

Windows如何從操作系統(tǒng)層面保障所有程序的安全?訪問控制列表,令牌、系統(tǒng)帳號(hào)、SAM數(shù)據(jù)庫、GINA、交互式登陸、COM+,這些概念如何組成一個(gè)完整的Windows安全平臺(tái)?這次課程將解答您這方面的疑問。

深入研究Windows內(nèi)部原理系列之十:驅(qū)動(dòng)和硬件的管理
 

講師信息:張偉偉
2007年02月07日 14:00-15:30
Level: 400

驅(qū)動(dòng)程序如何被Windows識(shí)別、加載和管理?隨著Windows的發(fā)展,驅(qū)動(dòng)程序的類型和作用經(jīng)歷了怎么樣的變化?inf文件在驅(qū)動(dòng)安裝過程中起到了怎樣的作用?Vista的驅(qū)動(dòng)程序有哪些新變化?如果這方面的問題一直困擾著您,那這次課程是絕對(duì)不容錯(cuò)過的。

深入研究Windows內(nèi)部原理系列之十一:存儲(chǔ)和文件系統(tǒng)
 

講師信息:高宇
2007年02月08日 14:00-15:30
Level: 400

課程將在宏觀上簡要介紹Windows的存儲(chǔ)體系, 觀察磁盤上的扇區(qū)怎樣變成用戶眼中的文件. 然后深入觀察磁盤上的數(shù)據(jù)結(jié)構(gòu). 在分析枯燥的16進(jìn)制數(shù)據(jù)的同時(shí), 也會(huì)和大家討論一些有趣和常見的錯(cuò)誤現(xiàn)象。

深入研究Windows內(nèi)部原理系列之十二:網(wǎng)絡(luò)協(xié)議的構(gòu)成和實(shí)現(xiàn)
 

講師信息:高宇
2007年02月09日 14:00-15:30
Level: 400

課程內(nèi)容包括Windows中的網(wǎng)絡(luò)組件, 網(wǎng)絡(luò)協(xié)議,重要網(wǎng)絡(luò)服務(wù)的實(shí)現(xiàn)與特點(diǎn)。 Windows中的TCP/IP以及其上的服務(wù)將是本節(jié)的主要部分。

深入研究Windows內(nèi)部原理系列之十三:如何診斷和調(diào)試藍(lán)屏錯(cuò)誤
 

講師信息:張銀奎
2007年02月12日 14:00-15:30
Level: 400

當(dāng)Windows操作系統(tǒng)檢測(cè)到來源于系統(tǒng)硬件或內(nèi)核代碼的嚴(yán)重錯(cuò)誤時(shí),為了避免繼續(xù)運(yùn)行可能導(dǎo)致的更嚴(yán)重后果,Windows會(huì)通過藍(lán)屏報(bào)告錯(cuò)誤并讓整個(gè)系統(tǒng)以可控的方式停止運(yùn)行(BSOD)。Windows提供了多種方法來診斷和調(diào)試藍(lán)屏錯(cuò)誤,包括故障轉(zhuǎn)儲(chǔ)文件(DUMP)、內(nèi)核調(diào)試以及通過驅(qū)動(dòng)程序注冊(cè)并接收錯(cuò)誤信息。本講座將解釋藍(lán)屏產(chǎn)生的原因和過程,引發(fā)藍(lán)屏錯(cuò)誤典型的根源,并向您介紹使用WinDbg分析DUMP文件的高級(jí)技巧。

深入研究Windows內(nèi)部原理系列之十四:用戶模式的程序排錯(cuò)(上)
 

講師信息:喻勇
2007年03月09日 14:00-15:30
Level: 400

“該程序執(zhí)行了非法操作,即將被關(guān)閉”,這是我們耳熟能詳?shù)某鲥e(cuò)報(bào)告。程序?yàn)槭裁磿?huì)崩潰?如何發(fā)現(xiàn)崩潰的原因并進(jìn)行解決?在全面了解了Windows的體系結(jié)構(gòu)和程序運(yùn)行方式后,我們將進(jìn)一步介紹訪問越界、緩沖溢出、內(nèi)存泄露等故障的原理,并理論聯(lián)系實(shí)際,帶領(lǐng)大家使用調(diào)試工具來解決一些常見的問題。

深入研究Windows內(nèi)部原理系列之十五:用戶模式的程序排錯(cuò)(下)
 

講師信息:喻勇
2007年03月13日 14:00-15:30
Level: 400

“該程序執(zhí)行了非法操作,即將被關(guān)閉”,這是我們耳熟能詳?shù)某鲥e(cuò)報(bào)告。程序?yàn)槭裁磿?huì)崩潰?如何發(fā)現(xiàn)崩潰的原因并進(jìn)行解決?在全面了解了Windows的體系結(jié)構(gòu)和程序運(yùn)行方式后,我們將進(jìn)一步介紹訪問越界、緩沖溢出、內(nèi)存泄露等故障的原理,并理論聯(lián)系實(shí)際,帶領(lǐng)大家使用調(diào)試工具來解決一些常見的問題。

深入研究Windows內(nèi)部原理系列之十六:使您成為Windows專家的一些學(xué)習(xí)習(xí)慣
 

講師信息:喻勇
2007年03月15日 14:00-15:30
Level: 200

在系統(tǒng)的學(xué)習(xí)了前面的Windows內(nèi)部原理之后,大家一定對(duì)這么多的技術(shù)細(xì)節(jié)和深入分析大呼過癮,也一定想盡快地掌握這些知識(shí)。如何學(xué)好Windows?如何成為一個(gè)技術(shù)過硬的IT專業(yè)人士?作為這個(gè)技術(shù)大餐的最后一講,講師將跟大家分享一些學(xué)習(xí)的心得,如何找對(duì)突破方向和知識(shí)重點(diǎn),循序漸進(jìn)的進(jìn)行系統(tǒng)的技術(shù)學(xué)習(xí)。同時(shí)也會(huì)指出常見的一些學(xué)習(xí)弊病和改進(jìn)方法。最后,老師將推薦一些重要的書籍和學(xué)習(xí)資料供聽眾參考。

posted @ 2012-10-17 22:21 jackdong 閱讀(1040) | 評(píng)論 (0)編輯 收藏

     摘要: http://blog.csdn.net/wangqiulin123456/article/details/8072545目錄(?)[-] 走馬觀花看COM服務(wù)器服務(wù)器生命其管理實(shí)現(xiàn)接口,從IUnknown開始構(gòu)造器和析構(gòu)器AddRef() 和 Release()QueryInterface()深入CoCreateInstance()COM服務(wù)器注冊(cè)創(chuàng)建COM對(duì)象——類...  閱讀全文
posted @ 2012-10-17 22:15 jackdong 閱讀(535) | 評(píng)論 (0)編輯 收藏

     摘要: http://blog.csdn.net/wangqiulin123456/article/details/8026270目錄(?)[-] COM到底是什么基本元素的定義 創(chuàng)建一個(gè)新對(duì)象刪除對(duì)象 創(chuàng)建COM對(duì)象刪除COM對(duì)象接下來將詳細(xì)討論IUnknown接口 基本接口――IUnknown仔細(xì)做好串處理 WideCharToMultiByte()wcstombs...  閱讀全文
posted @ 2012-10-17 22:13 jackdong 閱讀(403) | 評(píng)論 (0)編輯 收藏

http://www.cnblogs.com/lxconan/archive/2012/09/09/2677957.html

最近從架構(gòu)的角度做了一個(gè) Windows 8 下 Metro Style 應(yīng)用程序開發(fā)介紹的講座。以下是講稿。

如有問題歡迎指正。

下載地址:

1          概述

這篇的標(biāo)題叫做Windows RT Introduction而非Windows 8 Introduction是想強(qiáng)調(diào)此次介紹是從開發(fā)人員的角度而不是普通用戶的角度出發(fā)的。同時(shí),我們關(guān)注的是Metro Style應(yīng)用而不是傳統(tǒng)的Win32應(yīng)用程序的開發(fā)。

實(shí)際上,使用C#或者HTML + Javascript書寫一個(gè)Hello world應(yīng)用的代碼例子已經(jīng)在網(wǎng)上泛濫了。但是僅有一個(gè)Hello world并不能夠說你掌握了Win RT的開發(fā)。從Pro的角度來說我們應(yīng)該弄清楚整件事情的細(xì)節(jié)。那么首先就應(yīng)當(dāng)是他的架構(gòu)。這樣寫起程序來才能心定。

2          Windows 8 Metro與Desktop模式

2.1         兩種模式

Windows 8的應(yīng)用程序顯示模式目前有兩種,定義在METRO_MONITOR_MODE中:即傳統(tǒng)的桌面模式(MMM_Desktop)以及Metro模式(MMM_Metro)。如果你是Windows Phone用戶的話可能就會(huì)對(duì)Metro比較熟悉。事實(shí)上,微軟在2009年啟動(dòng)Windows 8的研發(fā)工作時(shí)目標(biāo)是創(chuàng)造一個(gè)完全不同以往的操作系統(tǒng),完全不以之前的操作系統(tǒng)為藍(lán)本。而后發(fā)現(xiàn)Desktop應(yīng)用是不可或缺的部分而將兩個(gè)部分進(jìn)行合并。一開始用可能會(huì)有些別扭,但是我估計(jì)開發(fā)人員半天之內(nèi)就能夠熟練使用這個(gè)系統(tǒng)了。

2.2         Metro和Desktop的一些不同

既然有兩種模式那么我們自然就會(huì)關(guān)注他們的不同點(diǎn)。這個(gè)問題應(yīng)該從架構(gòu)圖上做一下說明但是我們可以先有一些直觀的認(rèn)識(shí)。

2.2.1          Message Loop

消息處理的編程是傳統(tǒng)Desktop應(yīng)用程序的重要部分。你需要書寫維護(hù)Message Loop的代碼。例如:在WinMain調(diào)用(或者其子例程中)你需要書寫類似

 

while (::GetMessage(&message, NULL, 0, 0)) {

    ::TranslateMessage(&message);

    ::DispatchMessage(&message);

}

 

而在Window創(chuàng)建之前候你一定指定了

 

WNDCLASS wndClass;

// ...

wndClass.lpfnWndProc = WndProc;

 

這樣你就可以在WndProc函數(shù)中決定特定message的流向了。對(duì)于繪圖來說,你一定是接受了WM_PAINT消息,然后執(zhí)行了區(qū)域重繪。

但在Metro App中這些都已經(jīng)隱藏了,而且消息的細(xì)節(jié)也可能發(fā)生了變化。Metro App中你看不到消息循環(huán)。一切關(guān)于界面消息的分發(fā)都隱藏在了CoreDispatcher中。因此如果你用Spy++去試探Metro App的消息循環(huán)那么你什么都抓不到。

2.2.2          Display

在傳統(tǒng)的Desktop應(yīng)用程序中,繪圖可能通過GDI,GDI+,DirectDraw,DirectX進(jìn)行。同樣通過捕獲WM_PAINT消息或者當(dāng)系統(tǒng)處于IDLE的時(shí)候進(jìn)行繪圖(對(duì)于游戲編程來說)。

而Metro App不會(huì)再支持GDI和GDI+,在Metro App中繪圖只能通過DirectX來進(jìn)行。確切的說是Direct3D和新公布的Direct2D、Direct Write API。因此Metro應(yīng)用的所有繪圖都是希望是硬件加速的。這種繪圖更高效,解放CPU,而且一般不需要處理復(fù)雜的Dirty Region Repaint。

2.2.3          Life Cycle

Metro App并沒有關(guān)閉窗口這種按鈕。其生命周期是由系統(tǒng)托管的。系統(tǒng)會(huì)決定僅僅是掛起應(yīng)用執(zhí)行還是需要完全銷毀應(yīng)用進(jìn)程。這和一般意義上的Desktop應(yīng)用程序不一樣。(當(dāng)然,你也可以使用Alt+F4顯示的結(jié)束Metro App的執(zhí)行)。

2.2.4          Share & Communication

傳統(tǒng)的桌面應(yīng)用程序有多種手段進(jìn)行公共組建的公用或IPC。但是在Metro App中,隔離是一個(gè)很重要的概念,應(yīng)用的可執(zhí)行部分,運(yùn)行庫,Isolated Storage都是獨(dú)立的,不能夠共用。同樣,不能夠使用傳統(tǒng)的IPC機(jī)制。應(yīng)用程序的互動(dòng)僅僅可以通過內(nèi)置的Contracts進(jìn)行,關(guān)于這一部分內(nèi)容可以查看MSDN:

http://msdn.microsoft.com/en-us/library/windows/apps/hh464906.aspx

2.2.5          Portability

傳統(tǒng)的Desktop應(yīng)用程序的支持大多為x86/64架構(gòu)的處理器。由于Metro環(huán)境可以完整運(yùn)行在ARM處理器上是一個(gè)重要的特性,因此Metro App可以運(yùn)行在ARM處理器上,即同時(shí)部署在PC和移動(dòng)設(shè)備上。

2.2.6          OS Access

當(dāng)然為了Portability的要求,必然要求應(yīng)用不能夠越過Win RT的抽象,因此Metro是不能像Desktop App那樣訪問所有的Windows API的。

3          從Windows 8 API的架構(gòu)圖看Windows RT

我們對(duì)Windows RT的介紹都將圍繞著這個(gè)圖展開。

在這個(gè)圖中,最底層的是NT的內(nèi)核;在其上是Windows子系統(tǒng)。實(shí)際上NT至少有三個(gè)子系統(tǒng),Windows子系統(tǒng),POSIX子系統(tǒng)(Unix)和OS/2子系統(tǒng)。POSIX子系統(tǒng)和OS/2子系統(tǒng)實(shí)際還是在使用Windows子系統(tǒng)。 在Windows子系統(tǒng)上劃分了不同的運(yùn)行時(shí)(橙色)和程序庫(淺藍(lán)色),最上面的綠色是我們使用的各種開發(fā)語言。

這個(gè)架構(gòu)圖實(shí)際上說明了一切。并且消除了很多誤解:

(1)       第一個(gè)誤解是INFOQ指出的Windows RT和Win32是完全分開的。這源于微軟發(fā)布的一幅飽受批評(píng)的架構(gòu)圖,在那張架構(gòu)圖中,Windows RT和Windows子系統(tǒng)竟然是并排排列的。這是很荒謬的,Windows RT實(shí)際上基于Windows子系統(tǒng)。首先Windows RT完全基于COM;其次Windows RT利用了一部分現(xiàn)有的Win32 API;其余的部分Windows RT則直接訪問NT內(nèi)核。

(2)       第二個(gè)誤解是C++/CX。C++/CX是微軟推薦的開發(fā)Windows RT的方式。他主要隱藏了COM的復(fù)雜性。關(guān)于這個(gè)問題我們后續(xù)會(huì)有說明。這個(gè)誤解是C++/CX實(shí)際就是C++ CLI。實(shí)際上這是兩個(gè)完全不同的東西,C++ CLI是運(yùn)行在托管環(huán)境下的,而C++/CX完全是Native的。

3.1         Windows RT僅用于Metro應(yīng)用

從架構(gòu)圖中可以看出,Win RT僅僅用于Metro應(yīng)用。并秉承了我們剛才介紹的,簡單部署,沒有共享的組件,沒有IPC,等等。

3.2         Windows RT構(gòu)建與COM之上

這也是為什么說Windows RT是構(gòu)建與Win32之上,因?yàn)镃OM是Win32重要的組成部分。這意味著:

(1)       你可以用之前所有的消費(fèi)COM的方式來使用Windows RT,你可以用C,你可以用ATL或者新的WRL;

(2)       WRL完全符合傳統(tǒng)的C++語法,這意味著你可以使用不同的編譯器(例如Intel C++編譯器)來構(gòu)建Metro應(yīng)用。但是微軟顯然希望大家都來使用C++/CX,WRL的文檔跟沒有差不多,現(xiàn)在也看不到一個(gè)完整的例子出現(xiàn)。

3.3         Windows RT限制了系統(tǒng)API的調(diào)用

Win RT是基于COM的,但是COM僅僅是一個(gè)二進(jìn)制協(xié)議而已。在COM Interface實(shí)現(xiàn)中從技術(shù)上講還是在調(diào)用Win32 API。但由于前面介紹的Win RT的設(shè)計(jì)要求,系統(tǒng)API的調(diào)用需要受到嚴(yán)格的限制。僅僅支持有限的API調(diào)用,因此在你希望使用一個(gè)Win32 API時(shí),一定要查詢MSDN上的Applied To一節(jié),看看是否是Metro Style App | desktop App。

同樣的道理,.NET的某些方法也在進(jìn)行著系統(tǒng)調(diào)用,因此在使用.NET開發(fā)Metro Style應(yīng)用程序的時(shí)候也并不是所有的程序集都能夠支持。當(dāng)然,如果使用P-Invoke的方式調(diào)用Win32 API那么危險(xiǎn)性就會(huì)更大。

總之,在Metro應(yīng)用中調(diào)用不支持的Win32 API會(huì)有如下的后果:

(1)       發(fā)生一個(gè)Runtime Exception;

(2)       應(yīng)用程序失去響應(yīng),尤其是在使用和消息循環(huán)相關(guān)的代碼時(shí)。例如對(duì)Metro App進(jìn)程使用WaitForSingleObject(hProcess)。

(3)       調(diào)用成功,但是你的Metro App應(yīng)用會(huì)被Windows Store駁回。

按照上述分析,那么即使你存在相當(dāng)可觀的COM代碼庫,也需要巨大的努力才能夠保證他們?cè)贛etro App上正確運(yùn)行(消除非法的系統(tǒng)調(diào)用)。對(duì)于新的應(yīng)用來說,為了避免書寫大量的COM開發(fā)代碼,最好使用C++/CX進(jìn)行開發(fā)了。

3.4         C++/CX

為什么會(huì)有C++/CX呢?這可以聯(lián)想n年前我們?yōu)榱吮苊釩++開發(fā)COM的冗長的代碼,轉(zhuǎn)而使用C開發(fā)關(guān)鍵程序,而使用Visual Basic創(chuàng)建COM組件。現(xiàn)在時(shí)間到了2012年,VB6已經(jīng)不在考慮范圍之內(nèi)了,于是C++/CX取代了他的位置。

C++/CX是Native的,但是它的語法為什么能夠和C++ CLI保持近乎一致呢?這是因?yàn)閃in RT本身雖然是Native的,但它以.NET兼容的方式暴露了元數(shù)據(jù)。但是我們?cè)诰幊讨幸獣r(shí)刻想到,我們?cè)诓僮鲗?shí)打?qū)嵉腘ative對(duì)象。根本沒有什么垃圾收集器在幫助我們。

那么為什么不單純使用.NET開發(fā)Metro App呢?這是因?yàn)閷?duì)于移動(dòng)設(shè)備來說,CPU的速度和電池是兩大局限,因此在近一年,Go Native的大潮終于襲來。目前:

(1)       iOS使用Objective-C進(jìn)行程序開發(fā),而且在移動(dòng)設(shè)備上也是沒有垃圾收集器的,需要手動(dòng)釋放使用的內(nèi)存;

(2)       Android一開始使用Java進(jìn)行開發(fā),但是在糟糕的性能和社區(qū)的強(qiáng)大壓力下,終于開放了C/C++開發(fā)接口;

(3)       WP7/8也出現(xiàn)了類似Android的情況。

目前客戶端應(yīng)用向更薄(核心應(yīng)用向服務(wù)器移動(dòng)),更快(運(yùn)行速度快,耗電小),交互更豐富(沒有動(dòng)畫你都對(duì)不起觀眾)的方向發(fā)展。因此開放Native接口是大勢(shì)所趨,C/C++順理成章的在Windows 8強(qiáng)勢(shì)回歸了。

但是,用.NET開發(fā)Metro應(yīng)用也是一個(gè)不錯(cuò)的選擇,尤其你的應(yīng)用沒有密集的運(yùn)算(游戲)的情況下。你可以參考幻燈片中的Cheat Sheet。

posted @ 2012-10-16 23:56 jackdong 閱讀(577) | 評(píng)論 (0)編輯 收藏

僅列出標(biāo)題
共10頁: First 2 3 4 5 6 7 8 9 10 
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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>
            玖玖综合伊人| 免费欧美在线视频| 欧美日韩第一页| 亚洲欧美日韩成人高清在线一区| 亚洲一区二区精品在线| 国产精品视频在线观看| 久久久久久高潮国产精品视| 老司机免费视频一区二区三区 | 亚洲激情欧美| 欧美色区777第一页| 午夜精品久久久99热福利| 久久久久看片| 亚洲一区日韩在线| 久久久在线视频| 亚洲欧美日韩区| 蘑菇福利视频一区播放| 亚洲欧美日韩高清| 男女精品网站| 久久免费99精品久久久久久| 欧美日韩国产va另类| 久久精品论坛| 国产精品jvid在线观看蜜臀| 欧美第十八页| 国产主播一区二区三区四区| 亚洲人成绝费网站色www| 狠狠色伊人亚洲综合成人| 亚洲精品国精品久久99热| 国产精品美女久久久久久免费| 欧美成人在线影院| 国产精品一区毛片| 亚洲人精品午夜在线观看| 激情久久久久久| 亚洲欧美大片| 亚洲一区影音先锋| 欧美激情一区二区三区在线| 久久婷婷国产综合尤物精品| 国产精品日韩在线播放| 一区二区三区高清| 亚洲精品视频一区二区三区| 久久精品国产99精品国产亚洲性色 | 99精品视频免费观看| 久久久99久久精品女同性| 欧美手机在线| 99国产精品| 一本大道av伊人久久综合| 美女露胸一区二区三区| 你懂的国产精品永久在线| 国内成人在线| 欧美亚洲一区二区三区| 欧美一区二区三区另类| 国产精品亚洲综合天堂夜夜| 一区二区三区色| 亚洲一二三四久久| 欧美视频日韩视频| 亚洲视频在线观看三级| 午夜精品亚洲| 国产欧美视频一区二区| 亚洲一区二区在线免费观看视频| 亚洲免费在线看| 国产精品久久久久毛片大屁完整版 | 欧美华人在线视频| 亚洲高清在线播放| 一本色道久久99精品综合| 欧美精品日韩一区| 亚洲毛片在线观看| 亚洲欧美99| 国产一区二区日韩精品欧美精品| 欧美在线精品免播放器视频| 麻豆9191精品国产| 亚洲精品视频在线| 欧美日韩在线亚洲一区蜜芽 | aa亚洲婷婷| 欧美伦理a级免费电影| 一区二区三区四区在线| 欧美一区二区福利在线| 国产一区再线| 欧美成人日本| 亚洲一二三区精品| 久久夜精品va视频免费观看| 亚洲国产精品悠悠久久琪琪| 欧美日韩91| 午夜精品一区二区三区在线| 欧美成人69| 亚洲永久免费| 一区二区三区中文在线观看| 欧美77777| 亚洲自拍高清| 欧美成人xxx| 亚洲视频在线观看网站| 国产日韩一区二区| 欧美大尺度在线| 欧美一进一出视频| 亚洲国产另类久久久精品极度| 亚洲欧美日韩国产精品| 在线观看视频免费一区二区三区| 欧美日韩综合另类| 久久午夜激情| 在线一区视频| 亚洲国产精品视频| 久久精品国产亚洲a| 亚洲人成久久| 国产美女精品一区二区三区| 欧美激情在线有限公司| 欧美影片第一页| 亚洲天堂激情| 91久久久久久久久久久久久| 久久激情网站| 亚洲一区二区免费视频| 在线播放日韩欧美| 国产精品亚洲欧美| 欧美午夜不卡在线观看免费| 免费日韩av片| 久久久精品国产一区二区三区| 中文一区二区| 亚洲欧洲日韩在线| 欧美α欧美αv大片| 久久精彩视频| 亚洲一区国产一区| 99xxxx成人网| 亚洲国产一区二区三区a毛片| 国产欧美一区二区精品仙草咪| 欧美精品v日韩精品v国产精品| 久久国产手机看片| 香蕉免费一区二区三区在线观看 | 娇妻被交换粗又大又硬视频欧美| 欧美日韩精品久久| 欧美黄色aa电影| 麻豆精品在线视频| 久久久久在线观看| 新狼窝色av性久久久久久| 亚洲视频精品在线| 亚洲激情视频在线播放| 欧美va天堂va视频va在线| 久久精品国产免费观看| 欧美在线一级va免费观看| 一区二区三区视频在线播放| 亚洲日本激情| 亚洲美女黄网| 夜夜嗨av一区二区三区四区| 亚洲黄色免费网站| 亚洲成人在线视频播放| 一区在线视频观看| 伊人久久大香线蕉av超碰演员| 狠狠狠色丁香婷婷综合久久五月| 国产日韩欧美a| 国产欧美日韩| 国产在线精品成人一区二区三区| 国产视频亚洲精品| 激情欧美一区二区三区| 亚洲第一福利视频| 亚洲国产精品一区在线观看不卡| 亚洲二区视频| 欧美国产日韩在线观看| 欧美大秀在线观看| 欧美日韩激情小视频| 国产精品久久久久免费a∨大胸| 国产精品乱子久久久久| 国产欧美日本一区二区三区| 韩日视频一区| 亚洲精品日日夜夜| 午夜精品一区二区三区电影天堂 | 国产精品久久久久免费a∨| 国产精品伊人日日| 好吊色欧美一区二区三区视频| 亚洲国产精品成人一区二区| 亚洲精品在线一区二区| 一区二区三区免费观看| 亚洲欧美一区二区三区久久| 久久福利视频导航| 亚洲国产精品成人| 中文国产成人精品| 久久xxxx| 欧美日本韩国| 国产一区在线看| a91a精品视频在线观看| 久久av在线看| 欧美激情一区二区三区在线视频| 99pao成人国产永久免费视频| 午夜精品视频一区| 免费亚洲电影在线| 国产人成精品一区二区三| 在线观看欧美日韩| 亚洲男人的天堂在线aⅴ视频| 久久精品国产免费观看| 日韩午夜三级在线| 久久九九99视频| 欧美日韩中文字幕在线视频| 激情av一区二区| 亚洲一区二区视频在线| 免费成人性网站| 在线视频你懂得一区| 免费观看一级特黄欧美大片| 国产精品欧美日韩一区| 亚洲精品一线二线三线无人区| 欧美在线观看网站| 亚洲精品一区二区三区99| 久久精品天堂| 国产精品九九久久久久久久| 亚洲激情在线| 狼人社综合社区|