1. 程式人生 > >第13章 動態資料交換和物件連結與嵌入

第13章 動態資料交換和物件連結與嵌入

剪貼簿

  在Windows作業系統中大量使用剪貼簿使使用者能夠在同一應用程式之中或不同應用程式之間傳輸資料。
13.1.1 剪貼簿資料格式
  當用戶選中某些資料並對它進行復制操作時,Windows把這些資料從選中區中讀出並傳送到剪貼簿上:當用戶使用貼上命令時,該資料從剪貼簿上讀出並貼上到插入位置。剪貼簿傾向於對使用者保持透明,使用者甚至不會意識到它的存在。
注意:剪貼簿只是一個簡單的資料暫存場所,這些資料需要使用者初始化並完成傳輸任務。
Windows系統定義了幾種標準的剪貼簿資料格式,這些格式如表所示。

格 式 說 明
CF_BITMAP 與裝置有關的點陣圖格式
CF_DIB 與裝置無關的點陣圖記憶體塊
CF_DIF 包含資料交換格式的全域性記憶體塊
CF_DSPBITMAP 和一種私有格式相關聯的點陣圖顯示格式
CF_DSPENHAMETAFILE 和一種私有格式相關聯的增強型圖元顯示格式
CF_DSPTEXT 和一種私有格式相關聯的文字顯示格式
CF_METAFILEPICT 圖元檔案格式
CF_OEMTEXT OEM字符集的文字格式
CF_OWNERDISPLAY 自畫格式
CF_PALETTE 調色盤控制代碼,定義點陣圖所使用的調色盤
CF_SYLK 包含符號連結格式的全域性記憶體塊
CF_TEXT NULL,結尾的ASCII字符集的文字格式
CF_WAVE 波形音訊
CF_TIFF 包含符號影象格式資料的全域性記憶體塊

在任何時候,只有一個程式可以開啟剪貼簿。函式OpenClipboard用於開啟一個剪貼簿,其函式原型定義如下:
BOOL OpenClipboard(
  HWND hWndNewOwner //視窗控制代碼
);
當其他程式正在使用剪貼簿時,函式OpenClipboard將返回一個FALSE值。關閉剪貼簿可以通過呼叫函式CloseClipboard來實現,這個函式的原型定義如下所示:
BOOL CloseClipboard(VOID);
  如果要把文字複製到剪貼簿上,首先必須使用GlobalAlloc函式分配所需的全域性記憶體塊,這個全域性記憶體塊是可移動的,剪貼簿中的文字資料就記錄在其中。GlobalAlloc函式成功呼叫後會返回一個標識該記憶體塊的控制代碼,GlobalAlloc函式原型定義如下:
HGLOBAL GlobalAlloc(
  UFNT uFlags,//記憶體分配屬性
  DWORD dwBytes //記憶體塊分配的大小
);
分配好記憶體塊後,就可以呼叫函式GlobalLock鎖定這個記憶體塊並得到指向這個記憶體塊的指標。函式GlobalLock的原型定義如下:
LPVOID GlobalLock(
  HGLOBAL hMem //記憶體塊旬柄
);
有了指向記憶體塊的指標就可以通過多種手段向該記憶體塊中複製資料了。函式SetClipboardData可以實現將資料複製到剪貼簿上。函式SetClipboardData的原型定義如下:
HANDLE SetClipboardData(
  UINT uFormat, //剪貼簿格式
  HANDLE hMem //存放資料的記憶體塊控制代碼
);
  當開啟剪貼簿並把資料傳送給它時,必須先呼叫 EmptyClipboard函式通知 Windows系統釋放或刪除剪貼簿上的內容,還不能在現有的剪貼內容中附加其他內容。 
  因此,剪貼簿每次只能保持一個數據項。不過可以在開啟和關閉剪貼簿之間,多次呼叫SetClipboardData函式來設定剪貼簿中的資料,並且每次可以指定不同的資料格式。
  獲取剪貼簿上的文字是把文字複製到剪貼簿的逆過程,但它的操作過程相對複雜一些。首先,必須判斷剪貼簿上的資料是否為文字內容,這可以通過呼叫函式IsClipboardFormatAvailable來實觀。如果剪貼簿上的資料為CF_TEXT,則該函式的返回值為TRUE,否則為FALSE,這個函式的原型定義如下:
BOOL IsClipboardFormatAvailable(
  UINT format //剪貼簿格式
);
如果剪貼簿上的資料格式是文字格式,則繼續呼叫函式OpenClipboard打什剪貼權,然後呼叫函式GetClipboardData獲得剪貼簿上的資料。函式GetClipboardData的原型定義如下:
HANDLE GetClipboardData(
  UINT uFormat //剪貼簿資料格式
);
13.1.2 剪貼簿應用例項


下面是一個在程式中操縱剪貼簿的例子。
在例的訊息處理函式中,首先通過呼叫函式setClipboardViewer把程式的主視窗加入到剪貼簿例覽器連結串列中。函式SetClipboardViewer的原型定義如下:
HWND SetClipboardViewer(
  HWND hWndNewViewer //視窗控制代碼
);
如果希望從剪貼簿測覽器連結串列中刪除某一個視窗,可以呼叫函式 ChangeClipboardChain來實現,這個函式的原型定義如下:
BOOL ChangeClipboardChain(
  HWND hWndRemove,//清除的視窗控制代碼
  HWND hwndNewNext//連結串列中的下一個視窗控制代碼
);
  剪貼簿連結串列中的元素髮生變化時將觸發WM-CHANGECBCHAIN訊息,在WM_CHANGECBCHAIN訊息的wParam引數中記錄了當前正被清除的視窗控制代碼,而lParam引數中記錄了連結串列中的下一個視窗的控制代碼。
  當應用程式的主視窗進行重繪時,呼叫函式 OpenClipboard開啟當前正在使用的剪貼簿,然後通過函式GetClipboardData獲取剪貼簿中的資料。獲取的資料儲存在由函式GlobalLock鎖定的記憶體塊中,然後再通過函式DrawText就可以把剪貼簿中的資料顯示在應用程式的主視窗中。
#include <windows.h> 
static char szAppName[] = "剪貼簿"; 
HWND hwnd; 
//函式宣告 
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; 
BOOL MyRegisterClass(HINSTANCE hInstance); 
BOOL InitInstance(HINSTANCE hInstance,int iCmdShow); 
//函式:WinMain 
//作用:程式入口
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) 
{
  MSG msg; 
  if(!MyRegisterClass(hInstance)) 
  {
    return FALSE;
  } 

  if(!InitInstance(hInstance,iCmdShow)) 
  {
    return FALSE;
  }
  //訊息迴圈
  while (GetMessage (&msg, NULL, 0, 0))
  {
    TranslateMessage (&msg) ;
    DispatchMessage (&msg) ;
  }
  return msg.wParam;
}
//函式:WndProc 
//作用:訊息處理 
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) 

  static HWND hwndNextViewer; 
  HGLOBAL hGMem;//全域性記憶體控制代碼 
  HDC hdc;//裝置描述表控制代碼 
  PSTR pGMem;//全域性記憶體指標 
  PAINTSTRUCT ps; 
  RECT rect; 
  switch (iMsg) 
  { 
    case WM_CREATE://視窗建立 
    hwndNextViewer = SetClipboardViewer (hwnd); 
    return 0;

    case WM_CHANGECBCHAIN://剪貼簿連結串列發生改變 
    if ((HWND)wParam == hwndNextViewer) 
      hwndNextViewer = (HWND) lParam; 
    else 
      if (hwndNextViewer) 
        SendMessage (hwndNextViewer, iMsg, wParam, lParam); 
    return 0; 

    case WM_DRAWCLIPBOARD://更新對剪貼簿資料的顯示 
    if(hwndNextViewer) 
      SendMessage (hwndNextViewer, iMsg, wParam, lParam); 
    InvalidateRect (hwnd, NULL, TRUE); 
    return 0;

    case WM_PAINT://重繪視窗 
    hdc = BeginPaint (hwnd, &ps); 
    GetClientRect (hwnd, &rect);//獲取客戶區 
    OpenClipboard (hwnd);//開啟剪貼簿 
    hGMem = GetClipboardData (CF_TEXT);//獲取剪貼簿資料 
    if (hGMem != NULL) 
    { 
      pGMem = (PSTR) GlobalLock (hGMem);//鎖定記憶體塊 
      DrawText (hdc, pGMem, -1, &rect, DT_EXPANDTABS); 
      GlobalUnlock (hGMem);//解鎖記憶體塊 
    } 
    CloseClipboard ();//關閉剪貼簿 
    EndPaint (hwnd, &ps); 
    return 0;

    case WM_PAINT://重繪視窗 
    hdc = BeginPaint (hwnd, &ps); 
    GetClientRect (hwnd, &rect);//獲取客戶區 
    OpenClipboard (hwnd);//開啟剪貼簿 
    hGMem = GetClipboardData (CF_TEXT);//獲取剪貼簿資料 
    if (hGMem != NULL) 
    { 
      pGMem = (PSTR) GlobalLock (hGMem);//鎖定記憶體塊 
      DrawText (hdc, pGMem, -1, &rect, DT_EXPANDTABS); 
      GlobalUnlock (hGMem);//解鎖記憶體塊 
    } 
    CloseClipboard ();//關閉剪貼簿 
    EndPaint (hwnd, &ps); 
    return 0;

    case WM_DESTROY://銷燬視窗 
    ChangeClipboardChain (hwnd, hwndNextViewer); 
    PostQuitMessage (0); 
    return 0; 
  } 
  return DefWindowProc (hwnd, iMsg, wParam, lParam); 

//函式:MyRegisterClass 
//作用:註冊視窗類 
BOOL MyRegisterClass(HINSTANCE hInstance) 

  WNDCLASSEX wndclass; 
  wndclass.cbSize = sizeof (wndclass); 
  wndclass.style = CS_HREDRAW | CS_VREDRAW; 
  wndclass.lpfnWndProc = WndProc;
  wndclass.cbClsExtra = 0; 
  wndclass.cbWndExtra = 0; 
  wndclass.hInstance = hInstance; 
  wndclass.hIcon = NULL; 
  wndclass.hCursor = LoadCursor (NULL, IDC_ARROW); 
  wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH); 
  wndclass.lpszMenuName = NULL; 
  wndclass.lpszClassName = szAppName; 
  wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION); 
  RegisterClassEx (&wndclass); 
  return TRUE; 
}
//函式:InitInstance 
//作用:建立視窗 
BOOL InitInstance(HINSTANCE hInstance,int iCmdShow) 

  hwnd = CreateWindow (szAppName, "剪貼簿監控器", 
             WS_OVERLAPPEDWINDOW, 
             CW_USEDEFAULT, CW_USEDEFAULT, 
             CW_USEDEFAULT, CW_USEDEFAULT, 
             NULL, NULL, hInstance, NULL); 
  ShowWindow (hwnd, iCmdShow); 
  UpdateWindow (hwnd); 
  return TRUE; 
}
執行結果如圖所示。

動態資料交換

  在應用程式之間經常要進行資料交換。動態資料交換(Dynamic Data Exchange簡稱DDE)使用普通的Windows通訊聯絡系統進行內部程序之間的通訊聯絡,物件連結與嵌入 ( Object Link and Embed簡稱 OLE)則是DDE的高階實現。DDE是基於訊息的,而OLE則是基於文字的。
  DDE協議能夠使一個應用程式和其他不同的應用程式之間交換資料,而且還可以利用Windows訊息這一手段傳遞遠端命令。Windows是基於訊息傳遞系統建立起來的,利用訊息傳送資料。不過,在Windows作業系統中,訊息只利用IParam和wParam兩個引數來傳送資料,如果有多於這兩個引數的訊息需要在不同的應用程式之間傳送,那麼這兩個引數必須間接指向其他的資料塊。 DDE協議指明瞭引數 IPat8111和 WPat8Ill兩個引數如何通過全域性原子和全域性共享記憶體等手段來實現資料傳遞。
  全域性原子實際上是一個字串,在DDE協議中,這些字串用於指明進行該資料交換的應用程式、要交換的資料特徵和交換的資料內容。全域性共享記憶體實際上是指一塊由函式GlobalAlloc。分配的記憶體塊。在DDE協議中,全域性共享記憶體物件是用於不同應用程式之間傳遞大的資料結構。DDE中有明確的規則用於指定客戶和伺服器的不同責任,這些責任包括對全域性原子以及共享記憶體物件的分配、重新分配以及刪除等。
13.2.1動態資料交換的用途
  在大多數場合DDE被用來傳遞那些不需要使用者不斷干涉的資料流。使用者只要建立原始的鏈路,然後讓有關的應用程式接管過去,而不需要使用者進一步的介入。
一般說來,DDE可以應用在以下場合:
  1、與實時資料相連:
  2、建立複合文字:
  3、訪問不同型別的文字。 
13.2.2動態資料交換的基本概念
1.客戶和伺服器對話

  DDE需要兩個應用程式參與一個對話以完成一個數據交換。使對話開始的應用程式被定義為DDE的客戶,響應對話的應用程式被定義為DDE的伺服器。客戶可以從伺服器中查詢有關資訊,也可以向伺服器傳送有關資訊。客戶與伺服器的區別在於誰首先初始化DDE對話。
  原則上一個DDE會話發生在兩個視窗之間。一個視窗對應於客戶,另一個視窗對應於伺服器,任何一個視窗都可以參與一個DDE會話,它可以是一個應用程式的主視窗,一個文字視窗,也可以是一個隱含視窗。
  一個DDE會話由參與會話的視窗的視窗控制代碼的有序對來進行標識。所以,任何一個視窗都不應該參與和其他視窗之間的多於一個的DDE會話過程。如果一個客戶和一個伺服器之間有多於一個的對話過程存在,它們就必須為每一個新的會話過程在一對一的基礎之上提供一個附加的視窗。
2. DDE訊息
  DDE訊息是由應用程式名稱、主題名稱以及項名稱組成的一個三級層次進行標識。
  一個特定的DDE會話唯一地被它的應用程式名稱以及主題名稱加以定義。初始化一個DDE會話時,DDE客戶訪問特定的DDE伺服器的應用程式的名稱和主題名稱。
  DDE主題是一個包含多個數據項的資料型別。有效的主題以及項的選擇由DDE伺服器任意設定。對於一個字處理程式,主題可以是一個文字或者檔名,項可能是檔案中的某些段落。
  因為客戶與伺服器視窗一起來識別一個DDE會話,所以在對話方塊中不能改變應用程式或主題。不過,在需要的情況下,可以任意地改變主題中的項。
  一個DDE的資料項是真正要傳送的資訊內容,資料項的值能夠從客戶傳向伺服器或者從伺服器傳送到客戶。
  因為DDE是基於Windows中訊息傳送協議的基礎之上的,因此它不需要任何特別的功能或者庫。所有的對話都是由在客戶或者伺服器視窗之間傳送某些定義的DDE訊息來完成的。其中,比較重要的 DDE訊息如表所示。

消 息 定義描述
WM_DDE_ACK 對已經收到的訊息的響應訊號
WM_DDE_ADVISE 客戶向伺服器傳送一個請求,要求伺服器無論何時,只要一個數據項改變就立即提供該資料或者向用戶傳送一個數據已經改變的通知
WM_DDE_DATA 伺服器向客戶方傳送資料項
WM_DDE_EXECUTE 客戶向伺服器傳送一個命令序列,伺服器應當將此視為一系列命令而加以處理
WM_DDE_INITIAL 客戶向伺服器方傳送一個初始化訊息
WM_DDE_POKE 客戶向伺服器方傳送一個數據項
WM_DDE_REQUEST 客戶請求伺服器提供有關一個數據項
WM_DDE_TERMINATE 客戶終止對話過程
WM_DDE_UNADVISE 客戶終止資料鏈路的使用

一個DDE會話實際上可以分成下面幾個子過程。
1、客戶應用程式開始啟動DDE會話,同時伺服器應用程式響應這一過程。
2、會話過程以下述方法之一進行資料交換:
  客戶請求有關訊息,同時伺服器響應:
  客戶向伺服器傳送未請求的訊息:
  任何時刻客戶請求伺服器,只要資料一改變就立刻把改變後的資料傳送給客戶:
  任何時刻客戶請求伺服器,只要資料一改變就立刻通知客戶:
客戶向伺服器傳送一系列命令碼,使其在伺服器上執行。
3、DDE會話結束。
3.DDE管理庫
  DDE管理庫 DDEML(Dynamic Data Exchange Management Library)通過把訊息、原子管理和記憶體管理封裝在同一個函式呼叫介面中,從而大大簡化了DDE程式設計。在DDEML.H標頭檔案中定義了30個函式和處理16種DDE事務的回撥函式。儘管所有的DDEML事務仍然基於應用程式、主題和資料項,但是在DDEML中應用程式名被稱為“伺服器”名。使用DDLML的任何程式均須使用函式DdeInitialize在DDEML中註冊自己。該函式得到一個應用程式例項識別符,它被用於所有其他DDEML函式。當程式結束時,還必須呼叫函式DdeUnInitialize來解除DDE所佔的資源。
  在應用程式間傳遞資料時,程式不直接分配共享的全域性記憶體,而是通過呼叫函式DdeCreateDataHandle從包含資料的緩衝區建立資料控制代碼。接收資料的程式通過該控制代碼,利用DdeAccessData或DdeGetData函式獲得資料,呼叫DdeFreeDataHandle函式釋放資料控制代碼。
13.2.3 動態資料交換的實現
下面是一個動態資料交換的實際例子。
  動態資料交換的實現在例中,首先呼叫函式 DdeInitialize初始化一個 DDE會話,這個函式的原型定義如下:
UINT DdeInitialize(
  LPDWORD pidInst, //應用程式控制代碼
  PFNCALLBACK pfnCallback, //DDE回撥函式
  DWORD afCmd, //命令過濾條件
  DWORD ulRes //系統保留
);
  如果應用程式不能正確地建立立視窗,則呼叫DdeUnInitialize結束DDE,並且釋放DDE佔有的所有資源。
函式DdeUnInitialize的原型定義如下:
BOOL DdeUnlnitialize(
  DWORD idInst //應用程式控制代碼
);
  函式DdeCreateStringHandle可以返回一個用於標識指定的字串的控制代碼,這個控制代碼可以在其他的DDEML函式中引用。
函式DdeCreateStringHandle的原型定義如下:
HSZ DdeCreateStringHandle(
  DWORD idlnst, //應用程式例項控制代碼
  LPTSTR psz, //字串指標
  int iCodePage //內碼表識別符號
);
  自定義的剪貼簿資料格式必須通過函式RegisterCliPboardFormat向系統註冊後才能使用,因此在例子的 WinMain函式中,緊接函式DdeCreateStringHandle後又呼叫了函式RegisterClipboardFormat來註冊自定義的剪貼簿資料格式。
函式RegisterClipboardFormat的原型定義如下:
UINT RegisterClipboardFormat(
  LPCTSTR lpszFormat //自定義剪貼簿資料格式名指標
);
函式DdeNameService用於命名DDE服務名,其函式原型定義如下:
HDDEDATA DdeNameService(
  DWORD idlnst, //應用程式控制代碼
  HSZ hszl, //記錄服務名的字串的控制代碼
  HSZ hsz2, //系統保留
  UINT afCmd //服務名標誌
);
  接下來呼叫的函式 DdeConnectList 用來建立一個和其他支援同一服務型別的伺服器之間的連線列表。
函式 DdeConnectList 的原型定義如下:
HCONVLIST DdeConnectList(
  DWORD idInst, //應用程式控制代碼
  HSZ hszService, //月艮務名控制代碼
  HSZ hszTopic, //主題名控制代碼
  HCONVLIST hConvList, //連線列表控制代碼
  PCONVCONTEXT pCC //記錄上下文資料的指標
);
函式 BroadcastTransaction是一個自定義的函式,在例13-2中的具體定義如下:
VOID BroadeastTransaction(PBYTE pSrc,DWORD cbData,UINT fint,UINT xtyp)
{
  HCONV hConv;
  DWORD dwResult;
  int cConvsOrg;
  cConvsOrg=cConvs;
  cConvs=0;
  if(hConvList)
  {
    hConv=DdeQueryNextServer(hCoavList,0);//查詢下一個伺服器
    while(hConv)
    {
      cConvs++; //計數器
      if(DdeClientTransaction(pSrc,cbData,hConv,hszAppName,fmt,xtyp,TIMEOUT_ASYNC,&dwResult))
      {
        DdeAbandonTransaction(idlnst,hConv,dwResult);
      }
      hConv=DdeQueryNextServer(hConvList,hConv);
    }
  }
  if(cConvs!=cConvsOrg)
  {
    InvalidateRect(hWndMain,NULL,TRUE);
  }
}
  在上述程式段中,函式DdeQueryNextServer用於獲得連線列表中的下一個連線控制代碼。
其函式原型定義如下:
HCONV DdeQueryNextServer(
  HCONVLIST hConvList, //連線列表
  HCONV hConvPrev //前一個連線控制代碼
);
  如果函式 DdeQueryNextServer 返回值不為空,則程式將進入一個 while 迴圈,在 while 迴圈體中定義了一個計數器,用於記錄伺服器的個 數。同時迴圈體通過函式 DdeClientTransaction 來開始一個客戶事務處理, DdeClientTransaction 函式的原型定義如下:
HDDEDATA DdeClientTransaction(
  LPBYTE pData, //傳向伺服器的資料指標
  DWORD cbData, //資料長度
  HCONV hConv, //連線控制代碼
  HSZ hszItem, //標識主題某一項的名稱的控制代碼
  UINT wFmt, //剪貼簿資料格式
  UINT wType, //事務型別
  DWORD dwTimeout, //延時
  LPDWORD pdwResult //事務處理結果指標
);
  如果伺服器不能對客戶方的事務進行處理,程式中通過呼叫函式 DdeAbandonTransaction放棄事務處理並回收所有的資源。
函式DdeAbandonTransaction的原型定義如下:
BOOL DdeAbandonTransaction(
  DWORD idlnst, //應用程式例項控制代碼
  HCONV hConv, //轉換控制代碼
  DWORD idTransaction //事務識別符號
);
  在主視窗的訊息處理函式MainwindProc中,處理WM_TIMER訊息時還用到了一個DdePostAdvise函式,這個函式將使系統傳送一條XTYP_ADVREQ訊息給DDE回撥函式,DdePostAdvise函式的原型定義如下:
BOOL DdePostAdvise(
  DWORD idlnst, //應用程式控制代碼
  HSZ hszTopic, //標識主題名的控制代碼
  HSZ hszltem //標識主題中的某一項的控制代碼
);
例的核心是DDE回撥函式,本例中定義的回撥函式如下:
HDDEDATA CALLBACK DdeCallback(WORD wType,WORD wFmt,HCONV hConv,HSZ hszTopic,HSZ hszItem,
                HDDEDATA hData,DWORD IData1,DWORD IData2)
{
  LPTSTR pszExec;
  switch(wType)
  {
    case XTYP_CONNECT: //連線請求
    return((HDDEDATA)TRUE);
    case XTYP_ADVREQ:
    case XTYP_REQUEST:
    //建立資料控制代碼
    return(DdeCreateDataHandle(idInst,(PBYTE)&count,sizeof(count),0,
    hszAppName,OurFormat,0));
    case XTYP_ADVSTART:
    return(HDDEDATA)((UINT)wFmt=OurFormat&&hszItem==hszAppName);
    case XTYP_ADVDATA:
    //獲取資料
    if(DdeGetData(hData,(PBYTE)&InCount,sizeof(InCount),0))
    {
      DdeSetUserHandle(hConv,QID_SYNC,InCount);
    }
    InvalidateRect(hWndMain,NULL,TRUE);
    return((HDDEDATA)DDE_FACK);
    case XTYP_EXECUTE:
    pszExec=(LPTSTR)DdeAccessData(hData,NULL);
    if(pszExec)
    {
      if(fActive && !STRICMP(szPause,pszExec))
      {
        KillTimer(hWndMain,1); //銷燬定時器
        fActive=FALSE;
        InvalidateRect(hWndMain,NULL,TRUE);
        UpdateWindow(hWndMain);
      }
      else if(!fActive && !STRICMP(szResume,pszExec))
      {
        //設定定時器
        SetTimer(hWndMain,1,BASE_TIMEOUT+(rand()& 0xff),NULL);
        fActive=TRUE;
        InvalidateRect(hWndMain,NULL,TRUE);
        UpdateWindow(hWndMain);
      }
      MessageBeep(0);
    }
    break;
    case XTYP DISCONNECT: //斷開連線
    InvalidateRect(hWndMain,NULL,TRUE);
    break;
    case XTYP REGISTER: //註冊伺服器
    BroadeastTransaction(NULL,0,OurFormat,XTYP_ADVSTOP);
    //建立連線列表
    hConvList=DdeConnectList(idlnst,hszItcm,hszAppName,hConvList,NULL);
    BroadcastTransaction(NULL,0,OurFormat,XTYP_ADVSTART);
    SetWindowPos(hWndMain,0,0,0,cxText,(cyText*(eConvs+1))+cyTitle,SWP_NOMOVE|SWP_NOZORDER);
    UpdateWindow(hWndMain);
    return((HDDEDATA)TRUE);
  }
  return(0);
}
  回撥函式引數表中的wType引數用於指定回撥型別;hszTopic引數用於指定主題名:引數hszItem比m用於指定主題中的某一項的名稱。不難看出,上面的回撥函式和訊息處理函式有幾分相似。
  當伺服器處理完XTYP_CONNECT事務並返回TRUE時,DDE會話也就開始了。伺服器和客戶之間的通訊實際上都是藉助上述的事務型別來實現的。當客戶向伺服器傳送一個請求時,DDE管理庫就會用相應的事務型別呼叫伺服器的回撥函式。
  回撥函式接收的事務確定於應用程式在DdeInitialize中指定的回撥過濾標誌,以及應用程式是否是客戶或伺服器,還是二者兼是。DDE事務型別如表所示。

事務型別 事務原因
XTYP_ADVDATA 客戶通過返回資料控制代碼應答XTYP_ADVREQ事務
XTYP_ADVREQ 伺服器呼叫DdePostAdvise函式,指示協商鏈路中一個數據專案的值已經發生變化
XTYP_ADVSTART 客戶在呼叫DdeClientTransaction函式中指定XTYP_ADVSTART事務型別
XTYP_ADVSTOP 客戶在呼叫DdeClientTransaction函式中指定XTYP_ADVSTOP事務型別
XTYP_CONNECT 客戶呼叫DdeConnect函式並指定由伺服器支援的服務名和話題名
XTYP_CONNECT_CONFIRM 伺服器在應答XTYP_CONNECT和XTYP_WILDCONNECT事務
XTYP_DISCONNECT 會話中一個成員呼叫DisConnect函式,使兩個成員都接收此事務
XTYP_ERROR 發生嚴重錯誤
XTYP_EXECUTE 客戶在呼叫DdeClientTransaction中指定XTYP_EXECUTE事務型別

例13-2的源程式
#include <windows.h>
#include <ddeml.h>
#include <stdlib.h>
#include <string.h>
#ifdef UNICODE
#define STRICMP wcsicmp
#define ITOA(c, sz, b) (itoa(sizeof(szA), szA, b),
mbstowcs(sz, szA, b), sz)
#else
#define STRICMP stricmp
#define ITOA itoa
#endif 
//函式宣告
HDDEDATA CALLBACK DdeCallback(WORD wType, WORD wFmt, HCONV hConv, HSZ hszTopic,HSZ hszItem,
               HDDEDATA hData, DWORD lData1, DWORD lData2);
VOID PaintDemo(HWND hwnd);
LONG APIENTRY MainWndProc(HWND hwnd, UINT message, WPARAM wParam, LONG lParam);
VOID BroadcastTransaction(PBYTE pSrc,DWORD cbData,UINT fmt,UINT xtyp);
#define BASE_TIMEOUT 100
BOOL fActive; //標識資料是否正在被改變
DWORD idInst = 0; // DDEML物件
HANDLE hInst; //應用程式控制代碼
HCONVLIST hConvList = 0; //連線列表
HSZ hszAppName = 0; //應用程式名
HWND hwndMain; //主視窗控制代碼
TCHAR szT[20]; //用於重繪的靜態緩衝區控制代碼
#ifdef UNICODE
CHAR szA[20]; //用於UNICODE 轉化緩衝區
TCHAR szTitle[] = TEXT("DDEmo (U)");
#else
TCHAR szTitle[] = TEXT("DDEmo");
#endif
TCHAR szApp[] = TEXT("DDEmo"); // DDE服務名
TCHAR szPause[] = TEXT("PAUSE"); // DDE可執行命令
TCHAR szResume[] = TEXT("RESUME");// DDE可執行命令
UINT OurFormat; //定製的格式
int InCount = 0; //輸入資料緩衝區
int cConvs = 0; // 啟用的conversations數
int count = 0; //輸出資料
int cyText, cxText, cyTitle; //重繪區域
//函式:WinMain
//作用:程式入口
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,INT nCmdShow)
{
  MSG msg;
  WNDCLASS wc;
  TEXTMETRIC metrics;
  HDC hdc;
  //視窗初始化
  wc.style = 0;
  wc.lpfnWndProc = MainWndProc;
  wc.cbClsExtra = 0;
  wc.cbWndExtra = 0;
  wc.hInstance = hInstance;
  wc.hIcon = NULL;
  wc.hCursor = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground = NULL;
  wc.lpszMenuName = NULL;
  wc.lpszClassName = szTitle;
  if(!RegisterClass(&wc))
  return(FALSE);
  //DDE初始化
  if(DdeInitialize(&idInst,(PFNCALLBACK)MakeProcInstance((FARPROC)DdeCallback, hInstance),
    APPCMD_FILTERINITS|CBF_SKIP_CONNECT_CONFIRMS|CBF_FAIL_SELFCONNECTIONS|CBF_FAIL_POKES,0))
    return(FALSE);
    hInst = hInstance;
  //建立視窗
  hwndMain = CreateWindow(szTitle,szTitle,WS_CAPTION | WS_BORDER | WS_SYSMENU, CW_USEDEFAULT,
              CW_USEDEFAULT,0, 0, NULL, NULL,hInstance, NULL);
  if (!hwndMain) 
  {
    DdeUninitialize(idInst);//結束DDE
    return(FALSE);
  }
  hdc = GetDC(hwndMain);
  GetTextMetrics(hdc, &metrics);//獲取當前所用字型
  cyText = metrics.tmHeight + metrics.tmExternalLeading;
  cxText = metrics.tmMaxCharWidth * 8;
  cyTitle = GetSystemMetrics(SM_CYCAPTION);
  ReleaseDC(hwndMain, hdc);
  //建立字串控制代碼
  hszAppName = DdeCreateStringHandle(idInst, szApp, 0);
  //註冊自定義資料格式
  OurFormat = RegisterClipboardFormat(szApp);
  //註冊伺服器名
  DdeNameService(idInst, hszAppName, 0, DNS_REGISTER);
  //建立DDE會話列表
  hConvList = DdeConnectList(idInst, hszAppName, hszAppName, hConvList, NULL);
  //向所有的伺服器廣播事務
  BroadcastTransaction(NULL, 0, OurFormat, XTYP_ADVSTART);
  SetWindowPos(hwndMain, 0, 0, 0, cxText,(cyText * (cConvs + 1)) + cyTitle, 
         SWP_NOMOVE | SWP_NOZORDER);
  ShowWindow(hwndMain, nCmdShow);
  UpdateWindow(hwndMain);
  //訊息迴圈
  while(GetMessage(&msg, 0, 0, 0)) 
  {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
  DestroyWindow(hwndMain);
  UnregisterClass(szTitle, hInstance);
  return(FALSE);
}
//函式:BroadcastTransaction
//作用:向所有伺服器廣播客戶的事務請求
VOID BroadcastTransaction(PBYTE pSrc,DWORD cbData,UINT fmt,UINT xtyp)
{
  HCONV hConv;
  DWORD dwResult;
  int cConvsOrg;
  cConvsOrg = cConvs;
  cConvs = 0; //初始化連線數為0
  if (hConvList)
  { //下一個會話控制代碼
    hConv = DdeQueryNextServer(hConvList, 0);
    while(hConv) 
    {
      cConvs++;
      //處理客戶事務
      if (DdeClientTransaction(pSrc, cbData, hConv, hszAppName, fmt, xtyp,
                   TIMEOUT_ASYNC, &dwResult)) 
      {
        //放棄客戶事務
        DdeAbandonTransaction(idInst, hConv, dwResult);
      }
      hConv = DdeQueryNextServer(hConvList, hConv);
    }
  }
  if(cConvs != cConvsOrg)
  {//重繪視窗
    InvalidateRect(hwndMain, NULL, TRUE);
  }
}
//函式:MyProcessKey
//作用:處理鍵盤訊息
VOID MyProcessKey(TCHAR tchCode,LONG lKeyData)
{
  switch (tchCode) 
  {
    case TEXT('B'):
    case TEXT('b'):
    *((PBYTE)(-1)) = 0; 
    break;
  }
}
//函式:MainWndProc
//作用:主視窗訊息迴圈
LONG APIENTRY MainWndProc(HWND hwnd,UINT message,WPARAM wParam,LONG lParam)
{
  RECT rc;
  switch (message) 
  {
    case WM_CREATE://建立視窗
    fActive = FALSE;
    break;
    case WM_RBUTTONDOWN: //按下滑鼠右鍵
    if(GetKeyState(VK_CONTROL) & 0x8000) //判斷控制鍵的狀態
    {
      //廣播事務處理
      BroadcastTransaction((PBYTE)szPause, sizeof(szPause), 0, XTYP_EXECUTE);
      MessageBeep(0);
    }
    KillTimer(hwndMain, 1);//銷燬定時器
    fActive = FALSE;
    InvalidateRect(hwnd, NULL, TRUE);//重繪視窗
    UpdateWindow(hwnd);
    break;

    case WM_LBUTTONDOWN: //按下滑鼠左鍵
    if(GetKeyState(VK_CONTROL) & 0x8000) //判斷控制鍵狀態
    {
      //廣播事務處理 
      BroadcastTransaction((PBYTE)szResume, sizeof(szResume), 0, XTYP_EXECUTE);
      MessageBeep(0); //發出蜂鳴聲
    }
    //設定定時器
    SetTimer(hwndMain, 1, BASE_TIMEOUT + (rand() & 0xff), NULL);
    fActive = TRUE;
    InvalidateRect(hwnd, NULL, TRUE);//重繪視窗
    UpdateWindow(hwnd);
    break;

    case WM_CHAR://鍵盤訊息
    MyProcessKey((TCHAR)wParam, lParam);
    break;

    case WM_TIMER://定時器訊息
    count++;
    //傳送XTYP_ADVREQ事務給伺服器的回撥函式
    DdePostAdvise(idInst, hszAppName, hszAppName);
    SetRect(&rc, 0, 0, cxText, cyText);
    InvalidateRect(hwndMain, &rc, TRUE);//重繪視窗
    UpdateWindow(hwndMain);
    break;

    case WM_PAINT://繪製視窗
    PaintDemo(hwnd);
    break;

    case WM_CLOSE://結束應用程式
    KillTimer(hwnd, 1);
    DdeDisconnectList(hConvList);//斷開DDE會話
    //撤消已註冊的伺服器名
    DdeNameService(idInst, 0, 0, DNS_UNREGISTER);
    //銷燬字串控制代碼
    DdeFreeStringHandle(idInst, hszAppName);
    DdeUninitialize(idInst);//結束DDE會話
    PostQuitMessage(0);
    break;

    default://其他訊息
    return (DefWindowProc(hwnd, message, wParam, lParam));
  }
  return(0);
}
//函式:PaintDemo
//作用:繪製視窗
VOID PaintDemo(HWND hwnd)
{
  PAINTSTRUCT ps;
  RECT rc;
  HCONV hConv;
  CONVINFO ci;
  int cConvsOrg = cConvs;
  BeginPaint(hwnd, &ps);//開始繪製
  SetRect(&rc, 0, 0, cxText, cyText);//設定矩形區域
  SetBkMode(ps.hdc, TRANSPARENT);//設定背景模式
  SetTextColor(ps.hdc, 0x00FFFFFF); //文字顏色為白色
  //填充矩形區域
  FillRect(ps.hdc, &rc, (HBRUSH)GetStockObject(fActive ? BLACK_BRUSH : GRAY_BRUSH));
  //輸出文字
  DrawText(ps.hdc, ITOA(count, szT, 10), -1, &rc, DT_CENTER | DT_VCENTER);
  if (hConvList) 
  {
    OffsetRect(&rc, 0, cyText);
    SetTextColor(ps.hdc, 0); //設定文字顏色為黑色
    cConvs = 0;
    //查詢下一個伺服器
    hConv = DdeQueryNextServer(hConvList, 0);
    while (hConv) 
    {
      cConvs++;
      ci.cb = sizeof(CONVINFO);
      //獲取DDE會話資訊
      DdeQueryConvInfo(hConv, QID_SYNC, &ci);
      FillRect(ps.hdc,&rc, (HBRUSH)GetStockObject(WHITE_BRUSH));// 例中的源程式色背景
      //輸出文字
      DrawText(ps.hdc, ITOA(ci.hUser, szT, 10), -1, &rc,DT_CENTER | DT_VCENTER);
      OffsetRect(&rc, 0, cyText);
      hConv = DdeQueryNextServer(hConvList, hConv);
    }
  }
  EndPaint(hwnd, &ps);//結束繪製
  if (cConvsOrg != cConvs) 
  {
    SetWindowPos(hwndMain, 0, 0, 0, cxText, (cyText * (cConvs + 1)) + cyTitle,
           SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
  }
}
//函式:DdeCallback
//作用:DDE回撥函式
HDDEDATA CALLBACK DdeCallback(WORD wType,WORD wFmt,HCONV hConv,HSZ hszTopic,HSZ hszItem,
               HDDEDATA hData,DWORD lData1,DWORD lData2)
{
  LPTSTR pszExec;
  switch (wType) 
  {
    case XTYP_CONNECT://處理XTYP_CONNECT事務
    return((HDDEDATA)TRUE);
    case XTYP_ADVREQ:
    case XTYP_REQUEST:
    //建立資料控制代碼
    return(DdeCreateDataHandle(idInst,(PBYTE)&count, sizeof(count), 0, 
        hszAppName, OurFormat, 0));
    case XTYP_ADVSTART:
    return(HDDEDATA) ((UINT)wFmt == OurFormat && hszItem == hszAppName);
    case XTYP_ADVDATA:
    if (DdeGetData(hData, (PBYTE)&InCount, sizeof(InCount), 0))
    {
      //設定使用者控制代碼
      DdeSetUserHandle(hConv, QID_SYNC, InCount);
    }
    InvalidateRect(hwndMain, NULL, TRUE);
    return((HDDEDATA)DDE_FACK);
    case XTYP_EXECUTE:
    //訪問DDE資料
    pszExec = (LPTSTR)DdeAccessData(hData, NULL);
    if (pszExec) 
    {
      if (fActive && !STRICMP(szPause, pszExec)) 
      {
        //訊息定時器
        KillTimer(hwndMain, 1);
        fActive = FALSE;
        InvalidateRect(hwndMain, NULL, TRUE);
        UpdateWindow(hwndMain);
      }
      else if (!fActive && !STRICMP(szResume, pszExec)) 
      {
        //設定定時器
        SetTimer(hwndMain, 1, BASE_TIMEOUT + (rand() & 0xff), NULL);
        fActive = TRUE;
        InvalidateRect(hwndMain, NULL, TRUE);
        UpdateWindow(hwndMain);
      }
      MessageBeep(0);
    }
    break;
    case XTYP_DISCONNECT:
    InvalidateRect(hwndMain, NULL, TRUE);
    break;
    case XTYP_REGISTER:
    //向伺服器廣播事務
    BroadcastTransaction( NULL, 0, OurFormat, XTYP_ADVSTOP );
    hConvList = DdeConnectList(idInst, hszItem, hszAppName, hConvList, NULL);
    BroadcastTransaction(NULL, 0, OurFormat, XTYP_ADVSTART);
    SetWindowPos(hwndMain, 0, 0, 0, cxText,(cyText * (cConvs + 1)) + cyTitle, 
           SWP_NOMOVE | SWP_NOZORDER);
    UpdateWindow(hwndMain);
    return((HDDEDATA)TRUE);
  }
  return(0);
}
13.3物件連結與嵌入基礎知識
  物件連結與嵌入實際上包括連結和嵌入物件、觀察物件和編輯物件等涉及的所有技術。
11.3.1物件連結與嵌入
  在這裡一個物件被定義為任何一個文字中顯示的並且可以被終端使用者修改的資料。物件範圍可從小到一個電子表格單元大到一個完整的文字,當一個物件歸屬於一個文字時,它與建立它的應用程式保持著一種聯絡。當一個物件被嵌入到其他物件之中時,不僅相關的資料被嵌入,而且相應的資料規則也被嵌入,這樣使用者不必返回該源應用程式就可以對一個物件進行編輯和修改。
  當一個OLE文字包含了以各種不同格式表示的資料時,它就被稱為一個複合文字。如果沒有實現OLE技術,則每一種不同的資料型別都能夠被終端使用者修改。終端使用者不得不在不同的應用程式中取得不同的編輯工具;如果應用程式實現了OLE技術,在同一環境下就可以實現對不同格式的資料進行編輯和修改。OLE技術給予了終端使用者一個以文字為中心的參照框架系統,而不是一個以應用程式為中心的參照框架系統。
  複合文字使用應用程式的許多特性來修改包含在其中的資料型別,任何一種資料均可以包含在一個複合文字之中。終端使用者可以不必知道哪些資料格式與其他的資料格式相相容,終端使用者也不必知道怎樣尋找建立該資料的其他應用程式。當用戶在一個文字的一個部分裡工作時,負責那個部分的應用程式合自動地開始工作。
OLE的好處在於:
1、檔案的連結物件可以動態地加以改變;
2、當前的應用程式可以對未來的資料格式進行擴充套件,因為一個對
  象的內容與包含它的文字無關;
3、文字檔案可以更加緊湊,這是因為對一個物件的連結使得該文
  本使用了該物件, 而不必儲存該物件的資料;
4、一個應用程式可以很好地、專業化地做某項工作;
5、終端使用者面向的是~個以文字為中心的使用者系統;
6、實現了OLE協議就可以利用多種多樣不同型別的物件優點。
13.3.2物件插入與嵌入操作
  一般地,命令Copy將把一個物件的拷貝放到剪貼簿中。然後,命令Paste將該物件從剪貼簿中嵌入到目標檔案中。這個規則存在一個例外:如果該物件由一個備用的格式表示,而該備用格式完全代表原資料而且它能夠被目的應用程式編輯,那麼應用程式就可以選擇這個備用格式,而不嵌入該物件。當然,大多數目的應用程式並不能識別備用格式,只能依賴源物件提供必須的編輯內容。
  在一般意義上,命令Paste比在剪貼簿包含有另一個應用程式中的一個物件時將嵌入已剪貼的物件。在那些並不支援OLE的應用程式中,則插入一個靜態的拷貝。如果物件在某些時候不得不被編輯的話,該靜態拷貝不能很容易地被源編輯訪問。
  1、Paste Link:
  在物件中插入鏈與插入物件一樣簡單。首先使用Copy命令從一個源中複製選中的內容,然後選擇Paste Link 命令。這樣,一個鏈被加入到當前游標的位置。 儘管貼上內容顯示在目標檔案中,但是,實際上它並不存在那裡,而是儲存在原始檔中。這時,如果圖形在原始檔中被修改了,那麼它在目標檔案中也能得到修改。
  2、Paste Special和Insert Special:
  更先進的應用程式可以處理各種各樣的檔案格式,其中包括格式化的正文、點陣圖以及其他的檔案型別。在這些格式中,選擇Paste Special命令可以實現物件的連結與嵌入。Paste Special在從一個源應用程式中複製資訊時能夠給予使用者更多的格式控制。
  從剪貼簿中複製一個物件時,總有一個預設的格式用於將物件移動到目的應用程式中去。然而,如果有適用的備用格式,那麼使用者可以利用命令Paste Special把預設的格式替換成各用格式。
13.4 ActiveX簡介
  ActiveX最早是作為Internet策略提出的,現在涉及OLE/COM/Internet應用程式開發的各個方面,包括自動化伺服器、元件和COM物件等。
  Microsoft最早是在 1996年 3月的 Internet專業開發人員研討會(Internet PDC)上提出ActiveX一詞的。當時ActiveX指的是大會口號“Active the Internet”,這僅僅是一種號召而非具體的應用程式開發技術或體系結構。
  在Internet PDC期間, Microsoft與Netscape針對Internet Web瀏覽器市場的控制權展開了面對面的交鋒。PDC表現出Microsoft不僅對瀏覽器市場感興趣,而且還對其他業務也存在濃厚的興趣。會上還展示了從電子儲存前端到新的OLE元件、到虛擬現實交談軟體等一系列工具。
  隨後, ActiveX成了 Microsoft的新企業口號,它的含義很快就超過了“Active the Internet”。 ActiveX成了定義從Web頁面到 OLE(Object Link and Embedding,物件連結和嵌入)元件的所有內容的核心術語。一方面,它表示將你聯絡到Microsoft、Internet和業界新技術的小型快速可重用元件;另一方面,ActiveX代表了Internet與應用程式的一種整合策略。如今,如果哪個產品或公司在它的詞彙中不出現ActiveX,那無論是從內部還是從外部來看它都落伍了。事實上,ActiveX不是一種技術或—種體系,而是一種概念和潮流。
ActiveX和OLE有很深的淵源,以前所說的OLE元件現在已被稱作了ActiveX元件,OLE DocObjects現在稱為ActiveX文件。在一些情行下,有關如何實現OLE技術的文件已經全部更新為ActiveX技術文件。
雖然OLE和ActiveX已經有了很大的發展,而且看上去每天都有新的技術出現,但值得懷疑的是,是否這些都是 Internet引發的。對於小型快速可重用的元件(COM Objects)的要求已經提出了很多年,分散式元件(DCOM Objects)是在很多年前的OLE 2.0 PDC上最早演示的。事實上,ActiveX的每個特徵都可以追溯到對小型快速可重用元件的需求,而這些都是以OLE和COM為基礎的。
  ActiveX並不是為了替換OLE,而是將它擴充套件為適應Internet、Intranet幻、商業應用程式和家庭應用程式的開發,以及開發它們所使用的工具。除了生成AciveX元件的特定技術外, Microsoft還建立了一套使用和整合 ActiveX元件的標準,從 Visual Basic到Microsoft Word……的所有產品都具有使用 ActiveX元件的能力。
一般來說,ActiveX元件型別包含以下幾個方面的內容:
  1、自動化伺服器;
  2、自動化控制器;
  3、元件;
  4、COM物件;
  5、文件:
  6、容器。
  自動化伺服器是可以由其他應用程式程式設計驅動的元件。自動化伺服器至少包含一個,也可能是多個供其他應用程式生成或連線的基於IDispatch的介面。自動化伺服器可以包含也可以不包含使用者介面,這取決於伺服器的特性和功能。
  自動化伺服器可以在控制器的程序空間執行、在它自己的程式設計空間內執行,或在另一臺機器的程序空間執行。伺服器的特定實現方式決定了它將如何以及在何處執行,但也並非完全如此。一個DLL既可以在程序內,也可以是本地和遠端的,而EXE只能在本地或者遠端執行。
  自動化控制器是那些使用和操縱自動化伺服器的應用程式。一個自動化控制器可以是各種型別的應用程式和動態連結庫。所以可以通過程序內、本地或者遠端的各種方式訪問伺服器。通常,註冊項和自動化伺服器的實現表明伺服器將在相對於控制器的那個處理空間內執行。
  ActiveX元件等價於以前的OLE元件或者OCX元件。一個典型的元件包括設計時和執行時的使用者介面,唯一的IDispatch介面定義元件的方法和屬性,以及唯一的IConnectPoint介面用於元件可以引發的事件。除此之外,一個元件還可以包含對其整個生命週期的一致性的支援,以及對剪貼、拖放等使用者介面特性的支援。從結構上看,一個元件有大量必須支援的COM介面,以利用這些特性。
  從針對元件開發的 OLE和 ActiveX的新開發指南上看,一個元件的特性已不限於以上描述的內容,而且開發者也可以僅實現使用者感興趣且有用的應用程式特性。ActiveX元件永遠都是在其所放置的容器中程序內執行的,元件的典型副檔名是.ocx,若從執行模組的角度來看,它不過是一個標準的 Windows DLL而已。
  COM物件在結構上與自動化伺服器和控制器類似,它們有一個或多個COM介面,沒有或有很少的使用者介面。然而,這些物件不能像自動化伺服器那樣被典型的控制器應用程式所使用。或者為了使用介面,控制器必須具有和它進行對話的COM介面,Windows95和 NT作業系統有上百個 COM物件和 Customer介面,用於對作業系統進行擴充套件,控制包括桌面的外觀和顯示器的三維圖形渲染等各個方面。COM物件是一種組織相關功能和資料的好方式,同時還保留了DLL的高速效能。
  所謂 ActiveX文件實際上是指 DocObject,一個文件可以是從電子表格到財務報表等應用程式中的任何元素。與元件一樣,文件也有使用者介面並且包含於容器應用程式之中。
  ActiveX文件在結構上是對OLE連結和嵌入模型的擴充套件,並對其所在的容器具有更多的控制權,一種最顯著的變化是選單的顯示方式。一個典型的OLE文件的選單會與容器選單合併成一個新的選單,而ActiveX文件將替換整個菜單系統,只表現出文件的特性而不是文件與容器共同的特性。容器只是一種宿主機制,而由文件本身進行所有控制。文件特性的表達方式的不同是ActiveX文件和OLE文件所有差別的根源。
  其他區別是列印和儲存。一個OLE文件被認為是其容器文件的一部分,因此是作為宿主容器文件的一部分進行列印和儲存的。ActiveX文件自身具有列印和儲存功能,而不是集中在容器文件中。
  ActiveX文件在一個統一的表示結構中使用,而不是位於嵌入式文件結構中,後者是OLE文件的基礎, Microsoft Internet Explorer就是這方面的一個很好的例子。 IE只是將Web頁面展示給使用者,但它是作為一個單一的實體進行顯示、列印和儲存的。Microsoft Word和Microsoft Excel則是OLE文件結構的例子,如果一個Excel電子表格嵌入在一個Word文件中,電子表格實際上是儲存在W。rd文件中,併成了它的一個整合部分。
  ActiveX容器是一個可以作為自動化伺服器、元件和文件宿主的應用程式。Microsoft Internet Explorer可以作為自動化伺服器、元件和文件的宿主。容器必須足夠健壯,以便處理元件和文件中缺少的一些介面特性。容器應用程式與它所包含的文件和元件只進行很少或者根本就不進行互動,或者用某種特殊的方式來維護和表示所包含的宿主元件。