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

C++ Coder

HCP高性能計算架構,實現,編譯器指令優化,算法優化, LLVM CLANG OpenCL CUDA OpenACC C++AMP OpenMP MPI

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

#

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

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

 

一、前言

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

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

二、HRESULT 函數返回值

每個人在做程序設計的時候,都有他們各自的哲學思想。拿函數返回值來說,就有好多種形式。

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

浮點數值

計算正玄值
BOOL DeleteFile(LPCTSTR)

布爾值

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

內存指針

內存申請,如果失敗,返回空指針 NULL
LONG RegDeleteKey(HKEY,LPCTSTR)

整數

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

整數

取得拖放文件信息。以不同的參數調用,則返回不同的含義:
一會兒表示文件個數,一會兒表示文件名長度,一會兒表示字符長度
...... ......

...

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

 

 

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

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

還記得剛才我們說的原則嗎?COM 組件是運行在分布式環境中的。也就是說,這個函數可能運行在“地球另一邊”的計算機上,既然運行在那么遙遠的地方,就有可能出現服務器關機、網絡掉線、運行超時、對方不在服務區......等異常。于是,這個加法函數,除了需要返回運算結果以外,還應該返回一個值------函數是否被正常執行了。

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

如果函數正常執行,則返回 S_OK,同時真正的函數運行結果則通過參數指針返回。如果遇到了異常情況,則COM系統經過判斷,會返回相應的錯誤值。常見的返回值有:

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

圖一、HRESULT 的結構

HRESULT 其實是一個雙字節的值,其最高位(bit)如果是0表示成功,1表示錯誤。具體參見 MSDN 之"Structure of COM Error Codes"說明。我們在程序中如果需要判斷返回值,則可以使用比較運算符號;switch開關語句;也可以使用VC提供的宏:

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

三、UNICODE

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

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

咳......說中文的人就是聰明!中國人利用連續2個擴展ASCII碼的擴展區域(0xA0以后)來表示一個漢字,該方法的標準叫GB-2312。后來,日文、韓文、阿拉伯文、臺灣繁體(BIG-5)......都使用類似的方法擴展了本地字符集的定義,現在統一稱為 MBCS 字符集(多字節字符集)。這個方法是有缺陷的,因為各個國家地區定義的字符集有交集,因此使用GB-2312的軟件,就不能在BIG-5的環境下運行(顯示亂碼),反之亦然。

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

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

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

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

四、BSTR

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

圖二、BSTR 內存結構

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

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

有函數 fun(const BSTR bstr),則你調用 LPCOLESTR p=...; fun(p); 錯誤!!!

有關 BSTR 的處理函數:

API 函數 說明
SysAllocString() 申請一個 BSTR 指針,并初始化為一個字符串
SysFreeString() 釋放 BSTR 內存
SysAllocStringLen() 申請一個指定字符長度的 BSTR 指針,并初始化為一個字符串
SysAllocStringByteLen() 申請一個指定字節長度的 BSTR 指針,并初始化為一個字符串
SysReAllocStringLen() 重新申請 BSTR 指針

CString 函數

說明

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

CComBSTR 函數

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

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

五、各種字符串類型之間的轉換

1、函數 WideCharToMultiByte(),轉換 UNICODE 到 MBCS。使用范例:

  1.     LPCOLESTR lpw = L"Hello,你好";  
  2.     size_t wLen = wcslen( lpw ) + 1;  // 寬字符字符長度,+1表示包含字符串結束符  
  3.       
  4.     int aLen=WideCharToMultiByte(  // 第一次調用,計算所需 MBCS 字符串字節長度  
  5. CP_ACP,  
  6. 0,  
  7. lpw,  // 寬字符串指針  
  8. wLen, // 字符長度  
  9. NULL,  
  10. 0,  // 參數0表示計算轉換后的字符空間  
  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,  // 轉換后的字符串指針  
  22. aLen, // 給出空間大小  
  23. NULL,  
  24. NULL);  
  25.   
  26.     // 此時,lpa 中保存著轉換后的 MBCS 字符串  
  27.     ... ... ... ...  
  28.     delete [] lpa;  

2、函數 MultiByteToWideChar(),轉換 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 提供的轉換宏。

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

上表中的宏函數,其實非常容易記憶:

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

使用范例:

  1. #include < atlconv.h >  
  2.   
  3. void fun()  
  4. {  
  5.     USES_CONVERSION;  // 只需要調用一次,就可以在函數中進行多次轉換  
  6.       
  7.     LPCTSTR lp = OLE2CT( L"Hello,你好") );  
  8.     ... ... ... ...  
  9.     // 不用顯式釋放 lp 的內存,因為  
  10.     // 由于 ATL 轉換宏使用棧作為臨時空間,函數結束后會自動釋放棧空間。  
  11. }  

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

1、只適合于進行短字符串的轉換;

2、不要試圖在一個次數比較多的循環體內進行轉換;

3、不要試圖對字符型文件內容進行轉換,因為文件尺寸一般情況下是比較大的;

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

六、VARIANT

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

學生:我想用 VARIANT 表示一個4字節長的整數,如何做?

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

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

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

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

老師:不可以!因為 

類型 字節長度 假值 真值
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) 的時候會出問題(-1 != 1)。但是你注意觀察,任何布爾類型的“假”都是0,因此作為一個好習慣,在做布爾判斷的時候,不要和“真值”相比較,而要與“假值”做比較。

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

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

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

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

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

學生:老師,我再問最后一個問題,我如何用 VARIANT 保存一個數組?

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

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

七、小結

以上所介紹的內容,是基本功,必須熟練掌握。先到這里吧,休息一會兒......更多精彩內容,敬請關注《COM 組件設計與應用(四)》


注1:在后續的 ISupportErrorInfo 接口中介紹。

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

注3:跨語言就是各種語言中都能使用COM組件。但啥時候能跨平臺呢?

注4:CComVariant/COlevariant/_variant_t 請參看 MSDN。

注5:關于安全數組 SafeArray 的使用,在后續的文章中討論。

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

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

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

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


圖一、CLSID 的概念
  這的確是一個簡單的方法,但同時問題也很嚴重。在“張三”的計算機上,Excel 的路徑是:"c:\office\Excel.exe",如果把這個 doc 文件復制到“李四”的計算機上使用,而“李四”的 Excel 的路徑是:
"d:\Program files\Microsoft Office\Office\Excel.exe",完蛋了:-(
  于是,微軟想出了一個解決方案,那就是不使用直接的路徑表示方法,而使用一個叫 CLSID(注1)的方式間接描述這些對象數據的處理程序路徑。CLSID 其實就是一個號碼,或者說是一個16字節的數。觀察注冊表(上圖),在HKCR\CLSID\{......}主鍵下,LocalServer32(DLL組件使用InprocServer32) 中保存著程序路徑名稱。CLSID 的結構定義如下:
typedef struct _GUID {
  
DWORD Data1; // 隨機數
  WORD Data2; // 和時間相關
  WORD Data3; // 和時間相關
BYTE Data4[8]; // 和網卡MAC相關
} 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;

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

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

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

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

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

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

 

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

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

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

 

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

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


圖二、VTAB 的結構

4、還不夠好,需要改進一下,因為所有的函數地址都放在一個表中會不靈活、不好修改、不易擴展。恩,有了!按照函數功能的類型進行分類:


圖三、多個 VTAB 的結構

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


圖四、COM 接口結構

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


圖五、COM 接口結構的簡潔圖示


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

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

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

  容器 協商部分 組件 應答部分
1 根據CLSID啟動組件 。
CoCreateInstance()
生成對象,執行構造函數,執行初始化動作。
2 你有IUnknown接口嗎? 有,給你!
3 恩,太好了,那么你有IPersistStorage接口嗎?(注9)
IUnknown::QueryInterface(IID_IPersistStorage...)
沒有!
4 真差勁,連這個都沒有。那你有IPersistStreamInit接口嗎?(注10)
IUnknown::QueryInterface(IID_IPersistStreamInit...)
哈,這個有,給!
5 好,好,這還差不多。你現在給我初始化吧。
IPersistStreamInit::InitNew()
OK,初始化完成了。
6 完成了?好!現在你讀數據去吧。
IPersistStreamInit::Load()
讀完啦。我根據數據,已經在窗口中顯示出來了。
7 好,現在咱們各自處理用戶的鼠標、鍵盤消息吧...... ......
8 哎呀!用戶要保存退出程序了。你的數據被用戶修改了嗎?
IPersistStreamInit::IsDirty()
改了,用戶已經修改啦。
9 那好,那么用戶修改后,你的數據需要多大的存儲空間呀?
IPersistStreamInit::GetSizeMax()
恩,我算算呀......好了,總共需要500KB。
10 暈,你這么個小玩意居然占用這么大空間?!......好了,你可以存了。
IPersistStreamInit::Save()
謝謝,我已經存好了。
11 恩。拜拜了您那。(注11)
IPersistStreamInit::Release();IUnknown::Release()
執行析構函數,刪除對象。
12 我自己也該退出了......
PostQuitMessage()
 

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

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

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

 


 

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

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

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

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

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

方案

優點

缺點

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

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


圖一、左側表示一個磁盤下的文件組織方式,右側表示一個復合文件內部的數據組織方式。

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

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

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

WIN API 函數

功能說明

StgCreateDocfile() 建立一個復合文件,得到根存儲對象
StgOpenStorage() 打開一個復合文件,得到根存儲對象
StgIsStorageFile() 判斷一個文件是否是復合文件

 

IStorage 函數

功能說明

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

IStream 函數

功能說明

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

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

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

 


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

示例二:打開一個復合文件,枚舉其根存儲下的所有對象。

  1. #include // ANSI、MBCS、UNICODE 轉換  
  2. void SampleEnum()   
  3. {   
  4.   // 假設你已經做過 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 ); // 轉換T類型為寬字符  
  11.   hr = ::StgIsStorageFile( lpwFileName ); // 是復合文件嗎?  
  12.   if( FAILED(hr) )  
  13.     return;  
  14.   hr = ::StgOpenStorage( // 打開復合文件  
  15.         lpwFileName, // 文件名稱  
  16.         NULL,  
  17.         STGM_READ | STGM_SHARE_DENY_WRITE,  
  18.         0,  
  19.         0,  
  20.         &pStg); // 得到根存儲接口指針  
  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 保存著對象類型 STGTY_STREAM 或 STGTY_STORAGE  
  28.     // statstg.pwcsName 保存著對象名稱  
  29.     // ...... 還有時間,長度等很多信息。請查看 MSDN  
  30.     ::CoTaskMemFree( statstg.pwcsName ); // 釋放名稱所使用的內存(注6)  
  31.   }  
  32.   
  33.   if( pEnum ) pEnum->Release();  
  34.   if( pStg ) pStg->Release();  
  35. }  

 

六、小結
  復合文件,結構化存儲,是微軟組件思想的起源,在此基礎上繼續發展出了持續性、命名、ActiveX、對象嵌入、現場激活......一系列的新技術、新概念。因此理解和掌握 復合文件是非常重要的,即使在你的程序中并沒有全面使用組件技術,復合文件技術也是可以單獨被應用的。祝大家學習快樂,為社會主義軟件事業而奮斗:-)

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

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

posted @ 2012-10-17 22:23 jackdong 閱讀(419) | 評論 (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

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

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

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

著名技術作家、微軟亞洲研究院研究員潘愛民老師將在這次課程中跟聽眾分享Windows的發展歷程和技術精萃,描繪操作系統的體系架構、Vista的內核變更以及今后版本Windows的發展趨勢。

深入研究Windows內部原理系列之二:Windows體系結構-從操作系統的角度
 

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

操作系統是計算機系統的靈魂和管理中心,也是軟件系統中最復雜的部分。本講座將以生動的講解和豐富的演示帶您領略Windows操作系統的核心架構和主要組件,包括HAL、內核、執行體、系統進程(IDLE、SMSS.EXE、WinLogon.EXE)和Windows子系統(CSRSS.EXE、WIN32K.SYS以及子系統DLL)等。并討論中斷管理、對象管理、和異常分發等系統機制和實現這些機制的基本數據結構。

深入研究Windows內部原理系列之三:Windows體系結構-從應用程序的角度
 

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

從服務器軟件到Office辦公應用,從聯網游戲到即時消息,不管這些應用的復雜程度如何,他們都是一個個在操作系統控制和管理之下的可執行程序。本次課程邀請微軟全球技術中心專家級工程師,為各位講解一個程序是如何經歷從啟動、分配資源、運行、結束這一連串的過程,并且介紹其中的重要概念和排錯診斷技巧。

深入研究Windows內部原理系列之四:Windows操作系統中的重要基本概念
 

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

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

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

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

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

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

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

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

深入研究Windows內部原理系列之七:開機引導過程
 

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

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

深入研究Windows內部原理系列之八:內存管理揭秘
 

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

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

深入研究Windows內部原理系列之九:Windows的安全機制和實現
 

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

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

深入研究Windows內部原理系列之十:驅動和硬件的管理
 

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

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

深入研究Windows內部原理系列之十一:存儲和文件系統
 

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

課程將在宏觀上簡要介紹Windows的存儲體系, 觀察磁盤上的扇區怎樣變成用戶眼中的文件. 然后深入觀察磁盤上的數據結構. 在分析枯燥的16進制數據的同時, 也會和大家討論一些有趣和常見的錯誤現象。

深入研究Windows內部原理系列之十二:網絡協議的構成和實現
 

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

課程內容包括Windows中的網絡組件, 網絡協議,重要網絡服務的實現與特點。 Windows中的TCP/IP以及其上的服務將是本節的主要部分。

深入研究Windows內部原理系列之十三:如何診斷和調試藍屏錯誤
 

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

當Windows操作系統檢測到來源于系統硬件或內核代碼的嚴重錯誤時,為了避免繼續運行可能導致的更嚴重后果,Windows會通過藍屏報告錯誤并讓整個系統以可控的方式停止運行(BSOD)。Windows提供了多種方法來診斷和調試藍屏錯誤,包括故障轉儲文件(DUMP)、內核調試以及通過驅動程序注冊并接收錯誤信息。本講座將解釋藍屏產生的原因和過程,引發藍屏錯誤典型的根源,并向您介紹使用WinDbg分析DUMP文件的高級技巧。

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

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

“該程序執行了非法操作,即將被關閉”,這是我們耳熟能詳的出錯報告。程序為什么會崩潰?如何發現崩潰的原因并進行解決?在全面了解了Windows的體系結構和程序運行方式后,我們將進一步介紹訪問越界、緩沖溢出、內存泄露等故障的原理,并理論聯系實際,帶領大家使用調試工具來解決一些常見的問題。

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

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

“該程序執行了非法操作,即將被關閉”,這是我們耳熟能詳的出錯報告。程序為什么會崩潰?如何發現崩潰的原因并進行解決?在全面了解了Windows的體系結構和程序運行方式后,我們將進一步介紹訪問越界、緩沖溢出、內存泄露等故障的原理,并理論聯系實際,帶領大家使用調試工具來解決一些常見的問題。

深入研究Windows內部原理系列之十六:使您成為Windows專家的一些學習習慣
 

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

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

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

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

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

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

最近從架構的角度做了一個 Windows 8 下 Metro Style 應用程序開發介紹的講座。以下是講稿。

如有問題歡迎指正。

下載地址:

1          概述

這篇的標題叫做Windows RT Introduction而非Windows 8 Introduction是想強調此次介紹是從開發人員的角度而不是普通用戶的角度出發的。同時,我們關注的是Metro Style應用而不是傳統的Win32應用程序的開發。

實際上,使用C#或者HTML + Javascript書寫一個Hello world應用的代碼例子已經在網上泛濫了。但是僅有一個Hello world并不能夠說你掌握了Win RT的開發。從Pro的角度來說我們應該弄清楚整件事情的細節。那么首先就應當是他的架構。這樣寫起程序來才能心定。

2          Windows 8 Metro與Desktop模式

2.1         兩種模式

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

2.2         Metro和Desktop的一些不同

既然有兩種模式那么我們自然就會關注他們的不同點。這個問題應該從架構圖上做一下說明但是我們可以先有一些直觀的認識。

2.2.1          Message Loop

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

 

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

    ::TranslateMessage(&message);

    ::DispatchMessage(&message);

}

 

而在Window創建之前候你一定指定了

 

WNDCLASS wndClass;

// ...

wndClass.lpfnWndProc = WndProc;

 

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

但在Metro App中這些都已經隱藏了,而且消息的細節也可能發生了變化。Metro App中你看不到消息循環。一切關于界面消息的分發都隱藏在了CoreDispatcher中。因此如果你用Spy++去試探Metro App的消息循環那么你什么都抓不到。

2.2.2          Display

在傳統的Desktop應用程序中,繪圖可能通過GDI,GDI+,DirectDraw,DirectX進行。同樣通過捕獲WM_PAINT消息或者當系統處于IDLE的時候進行繪圖(對于游戲編程來說)。

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

2.2.3          Life Cycle

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

2.2.4          Share & Communication

傳統的桌面應用程序有多種手段進行公共組建的公用或IPC。但是在Metro App中,隔離是一個很重要的概念,應用的可執行部分,運行庫,Isolated Storage都是獨立的,不能夠共用。同樣,不能夠使用傳統的IPC機制。應用程序的互動僅僅可以通過內置的Contracts進行,關于這一部分內容可以查看MSDN:

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

2.2.5          Portability

傳統的Desktop應用程序的支持大多為x86/64架構的處理器。由于Metro環境可以完整運行在ARM處理器上是一個重要的特性,因此Metro App可以運行在ARM處理器上,即同時部署在PC和移動設備上。

2.2.6          OS Access

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

3          從Windows 8 API的架構圖看Windows RT

我們對Windows RT的介紹都將圍繞著這個圖展開。

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

這個架構圖實際上說明了一切。并且消除了很多誤解:

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

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

3.1         Windows RT僅用于Metro應用

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

3.2         Windows RT構建與COM之上

這也是為什么說Windows RT是構建與Win32之上,因為COM是Win32重要的組成部分。這意味著:

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

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

3.3         Windows RT限制了系統API的調用

Win RT是基于COM的,但是COM僅僅是一個二進制協議而已。在COM Interface實現中從技術上講還是在調用Win32 API。但由于前面介紹的Win RT的設計要求,系統API的調用需要受到嚴格的限制。僅僅支持有限的API調用,因此在你希望使用一個Win32 API時,一定要查詢MSDN上的Applied To一節,看看是否是Metro Style App | desktop App。

同樣的道理,.NET的某些方法也在進行著系統調用,因此在使用.NET開發Metro Style應用程序的時候也并不是所有的程序集都能夠支持。當然,如果使用P-Invoke的方式調用Win32 API那么危險性就會更大。

總之,在Metro應用中調用不支持的Win32 API會有如下的后果:

(1)       發生一個Runtime Exception;

(2)       應用程序失去響應,尤其是在使用和消息循環相關的代碼時。例如對Metro App進程使用WaitForSingleObject(hProcess)。

(3)       調用成功,但是你的Metro App應用會被Windows Store駁回。

按照上述分析,那么即使你存在相當可觀的COM代碼庫,也需要巨大的努力才能夠保證他們在Metro App上正確運行(消除非法的系統調用)。對于新的應用來說,為了避免書寫大量的COM開發代碼,最好使用C++/CX進行開發了。

3.4         C++/CX

為什么會有C++/CX呢?這可以聯想n年前我們為了避免C++開發COM的冗長的代碼,轉而使用C開發關鍵程序,而使用Visual Basic創建COM組件。現在時間到了2012年,VB6已經不在考慮范圍之內了,于是C++/CX取代了他的位置。

C++/CX是Native的,但是它的語法為什么能夠和C++ CLI保持近乎一致呢?這是因為Win RT本身雖然是Native的,但它以.NET兼容的方式暴露了元數據。但是我們在編程中要時刻想到,我們在操作實打實的Native對象。根本沒有什么垃圾收集器在幫助我們。

那么為什么不單純使用.NET開發Metro App呢?這是因為對于移動設備來說,CPU的速度和電池是兩大局限,因此在近一年,Go Native的大潮終于襲來。目前:

(1)       iOS使用Objective-C進行程序開發,而且在移動設備上也是沒有垃圾收集器的,需要手動釋放使用的內存;

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

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

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

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

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

僅列出標題
共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>
            久久久久久久综合日本| 羞羞漫画18久久大片| 91久久久久久久久久久久久| 久久国产66| 久久嫩草精品久久久久| 亚洲人久久久| 毛片一区二区三区| 久久综合九色综合欧美狠狠| 在线精品视频免费观看| 好看的日韩视频| 91久久精品视频| 亚洲精品自在久久| 在线亚洲欧美视频| 欧美一区二区在线免费播放| 欧美日韩亚洲一区二区三区在线观看| 久久一区视频| 亚洲精品一区二区三区不| 久久综合色婷婷| 亚洲国产成人在线播放| 久久视频国产精品免费视频在线| 午夜视频一区在线观看| 亚洲一区二区三区在线播放| 亚洲欧洲精品成人久久奇米网| 欧美xxxx在线观看| 一区二区三区四区国产精品| 亚洲精品一区在线| 欧美亚洲一区三区| 亚洲尤物视频在线| 另类人畜视频在线| 亚洲精品色图| 欧美日韩福利在线观看| 欧美国产丝袜视频| 在线观看亚洲精品| 久久精品123| 亚洲免费不卡| 99在线视频精品| 国产日韩在线播放| 久久综合色8888| 久久av一区二区三区漫画| 欧美日韩午夜| 亚洲性感激情| 先锋影音久久| 在线不卡亚洲| 欧美黄色一区| 欧美日韩第一页| 久久一区视频| 欧美电影免费观看高清完整版| 亚洲婷婷综合色高清在线| 国产精品午夜av在线| 精品动漫3d一区二区三区免费版 | 欧美粗暴jizz性欧美20| 亚洲欧美日韩国产| 国产精品女人久久久久久| 亚洲七七久久综合桃花剧情介绍| 国产精品一区二区三区免费观看| 亚洲电影视频在线| 日韩视频永久免费观看| 玖玖精品视频| 欧美诱惑福利视频| 欧美日韩国产综合网| 久久国产精品99国产精| 欧美一区二区三区四区夜夜大片| 亚洲丰满在线| 久久久久久国产精品mv| 99精品福利视频| 免费欧美在线| 亚洲人成免费| 99re66热这里只有精品3直播| 一本久道久久综合狠狠爱| 亚洲国产精品久久久久久女王| 欧美a级片网站| 久久国产精品99精品国产| 久久国产精品亚洲77777| 一区二区三区产品免费精品久久75 | 欧美日韩在线观看一区二区三区| 久久久久久一区| 精品成人一区| 欧美黄色一级视频| 亚洲综合精品自拍| 午夜精品免费视频| 尤物在线观看一区| 欧美日韩国产高清| 欧美一区午夜精品| 亚洲国产欧美在线| 欧美一区二区三区在线看| 欧美视频成人| 久久久久久久国产| 最近看过的日韩成人| 亚洲激情成人| 国产精品一二| 久久综合亚州| 亚洲欧美综合精品久久成人| 亚洲欧美综合网| 亚洲经典自拍| 亚洲经典在线看| 亚洲国产人成综合网站| 欧美日韩一区精品| 欧美sm视频| 欧美激情一区二区| 欧美成人有码| 老司机成人在线视频| 欧美日韩免费一区二区三区| 国产精品久久久久久久app| 亚洲线精品一区二区三区八戒| 久久精品国产亚洲精品| 一区二区三区高清视频在线观看 | 亚洲欧美日韩一区在线| 亚洲成色最大综合在线| 欧美大片一区二区| 米奇777在线欧美播放| 亚洲一区在线观看免费观看电影高清| 欧美电影电视剧在线观看| 亚洲午夜在线观看| 日韩亚洲精品视频| 亚洲人成网站999久久久综合| 国产欧美日韩三区| 欧美日韩一区在线观看| 欧美国产视频日韩| 美女视频黄免费的久久| 午夜精品福利视频| 亚洲欧美日韩国产中文| 亚洲片在线观看| 亚洲一区二区免费| 久久九九国产精品怡红院| 久久国产精品一区二区| 欧美在线视频导航| 欧美日韩国产成人在线免费| 欧美freesex交免费视频| 久久久久久日产精品| 午夜日韩av| 美腿丝袜亚洲色图| 国产欧美日韩综合一区在线播放| 欧美日本三区| 韩日精品视频一区| 亚洲综合色丁香婷婷六月图片| 日韩午夜在线观看视频| 日韩小视频在线观看| 亚洲女与黑人做爰| 欧美成年人在线观看| 亚洲东热激情| 午夜精品福利在线| 亚洲国产高清在线| 欧美一区二区三区在线| 久久精品亚洲精品| 国产精品一区在线观看| 永久91嫩草亚洲精品人人| 亚洲欧美日本国产有色| 99视频超级精品| 国产亚洲成年网址在线观看| 亚洲精品国产日韩| 亚洲精品国精品久久99热一| 久久精品综合网| 在线观看欧美视频| 在线亚洲自拍| 亚洲激情综合| 性欧美大战久久久久久久久| 欧美日韩妖精视频| 亚洲综合三区| 欧美日韩国产精品成人| 久久久青草婷婷精品综合日韩| 久久一区视频| 久久精品成人一区二区三区| 亚洲精品色图| 日韩亚洲国产精品| 国产精品久久9| 在线午夜精品| 亚洲午夜精品在线| 欧美紧缚bdsm在线视频| 国产精品高清网站| 久久久久久色| 欧美日本一道本| 蜜臀久久99精品久久久久久9| 久久精品欧洲| 99国产精品久久久| 久久精品视频在线免费观看| 国内精品伊人久久久久av影院| 欧美中在线观看| 欧美女同在线视频| 亚洲精品中文字幕女同| 欧美日韩喷水| 免播放器亚洲一区| 国产综合色产| 欧美呦呦网站| 久久久久欧美精品| 国产精品vvv| 一区二区三区高清在线| 亚洲午夜在线观看| 亚洲狼人精品一区二区三区| 免费不卡在线观看| 一区二区三区在线视频免费观看| 亚洲黄色三级| 欧美高清一区| 蜜臀久久99精品久久久久久9 | 黄色成人小视频| 亚洲免费高清| 亚洲欧美日韩区| 国产偷国产偷精品高清尤物| 亚洲第一精品福利| 亚洲精品在线视频| 欧美欧美全黄|