在自動化中使用早期系結和晚期系結

摘要

系結至自動化伺服器的方式可能會影響程式中的許多專案,例如效能、彈性和可維護性。

本文說明自動化用戶端可用的系結類型,並權衡每個方法的兩端。

其他相關資訊

自動化是一種程式,其中一個軟體元件會使用 Microsoft 的元件物件模型與和/或控制另一個軟體元件進行通訊, (COM) 。 它是大部分在 Visual Basic 或 Visual Basic for Applications 等語言中使用的跨元件通訊基礎,而且已成為大部分程式的一般部分。

在過去,Automation 物件是支援 IDispatch 介面的任何物件。 此介面可讓用戶端在執行時間呼叫方法和屬性,而不需要知道它們在設計階段與之通訊的確切物件;稱為晚期系結的進程。 不過,目前「自動化物件」一詞幾乎可以套用至任何 COM 物件,即使是不支援 IDispatch 的物件 (,因此無法晚期繫結) 。 本文假設您要自動化的物件支援這兩種系結方法。

什麼是系結?

系結是程式設計人員撰寫之函式呼叫與實作函式之內部或外部) (實際程式碼的比對程式。 它會在編譯應用程式時完成,而且程式碼中呼叫的所有函式都必須先系結,才能執行程式碼。

若要瞭解程式,請考慮在發行書籍方面進行「系結」。 假設您的程式碼就像是書籍的文字,在特定段落中,您已撰寫類似「如需詳細資訊,請參閱第 12 章,第 x 頁」之類的內容。在完成書籍之前,您不知道頁碼為何,因此在可以如預期般讀取段落之前,必須將書籍的所有頁面系結在一起,並將正確的頁碼插入段落中。 您等候書籍「系結」,才能參考書籍的其他部分。

系結軟體很類似。 您的程式碼是由必須提取在一起才能「讀取」程式碼的元件所組成。系結是將函式名稱取代為記憶體位址 (或記憶體位移的動作,更精確) 在呼叫函式時程式碼將會「跳至」的位置。 對於 COM 物件,位址是指標資料表中的記憶體位移, (稱為物件所持有的 v-table) 。 當 COM 函式系結時,它會透過 v 資料表系結。

COM 物件的結構很簡單。 當您的程式碼保存物件的參考時,它會保留 v 資料表頂端的間接指標。 v-table 是記憶體位址的陣列,其中每個專案都是可在該物件上呼叫的不同函式。 若要在 COM 物件上呼叫第三個函式,您可以在資料表中向下跳下三個專案,然後跳到該處指定的記憶體位置。 這會執行函式的程式碼,並在完成時,傳回您準備好執行下一行程式碼。

+-[Code]------------+  +.................................[COM Object]...+
|                   |  : +-------------+                                :
|Set obj = Nothing -|--->| obj pointer |                                :
|                   |  : +-|-----------+                                :
+-------------------+  :   |   +-----------------+                      :
                       :   +-->| v-table pointer |                      :
                       :       +--|--------------+                      :
                       :          |                                     :
                       :          |  +----------------------------+     :
                       :  (3rd)   |  | Function 1 Address pointer |     :
                       : (Offset) |  +----------------------------+     :
                       :          |  | Function 2 Address pointer |     :
                       :          |  +----------------------------+     :
                       :          +->| Function 3 Address pointer |     :
                       :             +----------------------------+     :
                       +................................................+

上述範例顯示釋放 COM 物件時會發生什麼事。 因為所有 COM 物件都繼承自 IUnknown,所以資料表中的前三個專案是 IUnknown 的方法。 當您需要釋放物件時,您的程式碼會呼叫 v-table 中的第三個函式 (IUnknown::Release) 。

幸運的是,此工作是由 Visual Basic 在幕後完成。 身為 Visual Basic 程式設計人員,您永遠不需要直接處理 v 資料表。 但是,此結構是所有 COM 物件的系結方式,而且請務必熟悉它,以瞭解什麼是系結。

早期系結

上述範例稱為早期 (或 v-table) 系結。 針對所有 COM 物件,每當呼叫 COM 物件的 IUnknown 介面時,就會發生這種形式的系結。 但是物件的其他函式呢? 如何呼叫其 Refresh 方法或其 Parent 屬性? 這些是一般對 物件而言是唯一的自訂函式。 如果無法假設其在 v-table 中的位置,您如何找到呼叫它們所需的函式位址?

當然,答案取決於您是否事先知道物件的 v 資料表外觀。 如果您這樣做,您可以對物件的自訂方法執行與對其 IUnknown 方法相同的早期系結程式。 這通常是指「早期系結」。

若要在物件上使用早期系結,您必須知道其 v 資料表的外觀。 在 Visual Basic 中,您可以將參考新增至描述物件的型別程式庫、其介面 (v-table) ,以及可在物件上呼叫的所有函式,來執行此動作。 完成之後,您可以將物件宣告為特定類型,然後使用 v-table 設定並使用該物件。 例如,如果您想要使用早期系結將 Microsoft Office Excel 自動化,您會從 Project 新增 「Microsoft Excel 8.0 物件程式庫」 的參考|參考對話方塊,然後將變數宣告為 「Excel.Application」 類型。從此之後,對物件變數進行的所有呼叫都會提早綁定:


' Set reference to 'Microsoft Excel 8.0 Object Library' in
' the Project|References dialog (or Tools|References for VB4 or VBA).

' Declare the object as an early-bound object
  Dim oExcel As Excel.Application

  Set oExcel = CreateObject("Excel.Application")

' The Visible property is called via the v-table
  oExcel.Visible = True

這個方法在大部分情況下都很棒,但如果您不知道您將在設計階段使用的確切物件,該怎麼辦? 例如,如果您需要與多個 Excel 版本交談,或可能完全與「未知」物件交談,該怎麼辦?

晚期繫結

COM 包含 IDispatch。 實作 IDispatch 的物件,如果是它們唯一支援) 或雙重介面 (的介面,則表示它們具有可及早系結至) 的自訂介面,則表示其具有分介面 (。 系結至 IDispatch 的用戶端稱為「晚期繫結」,因為他們所呼叫的確切屬性或方法是在執行時間使用 IDispatch 的方法來尋找它們所決定。 回到稍早的書籍範例,請將它視為註腳,將您導向至目錄,而您必須在「讀取時間」「查閱」頁碼,而不是在文字中列印頁碼。

介面的魔術是由兩個函式所控制:GetIDsOfNames 和 Invoke。 第一個會將函式名稱 (字串) 對應至識別碼, (稱為代表函式的 dispid) 。 一旦您知道要呼叫之函式的識別碼,就可以使用 Invoke 函式來呼叫它。 這種形式的方法調用稱為「晚期系結」。

同樣地,在 Visual Basic 中,您指定物件系結方式的方式是透過物件宣告。 如果您將物件變數宣告為「物件」,事實上,您會告訴 Visual Basic 使用 IDispatch,因此系結較晚:

' No reference to a type library is needed to use late binding.
' As long as the object supports IDispatch, the method can 
' be dynamically located and invoked at run-time.

' Declare the object as a late-bound object
  Dim oExcel As Object

  Set oExcel = CreateObject("Excel.Application")

' The Visible property is called via IDispatch
  oExcel.Visible = True

如您所見,其餘的程式碼是相同的。 就您撰寫) 的程式碼而言,早期系結和晚期系結 (的唯一差異是在變數宣告中。

請務必注意,「晚期繫結」是所呼叫的函式,而不是呼叫它的方式。 在先前關於系結的一般討論中,您應該會注意到 IDispatch 本身是「早期繫結」,也就是說,Visual Basic 會呼叫 ,透過 v 資料表專案設定 Visible 屬性, (IDispatch::Invoke) ,就像任何 COM 呼叫一樣。 COM 物件本身負責將呼叫轉送至正確的函式,讓 Excel 可見。 此間接取用可讓 Visual Basic 用戶端編譯 (也就是系結至有效的函式位址) ,但仍不知道實際執行工作的確切函式。

Dispid 系結

某些自動化用戶端 (最明顯的 MFC 和 Visual Basic 3.0,但與 ActiveX 控制項相關的 Visual Basic 5.0 和 6.0) 使用稱為 dispid 系結的混合式晚期系結形式。 如果在設計階段知道 COM 物件,則可以快取所呼叫之函式的 dispids,並直接傳遞至 IDispatch::Invoke,而不需要在執行時間呼叫 GetIDsOfNames。 這可以大幅提升效能,因為您只需要對每個函式進行兩次 COM 呼叫,而不需要進行一次。

Dispid 系結不是您通常可以在 Visual Basic 5.0 或 6.0 中選擇的選項。 它用於類型程式庫中參考的物件,但不包含自訂介面 (也就是,對於只有 dispinterface 的物件) 和匯總的 ActiveX 控制項,但一般而言,Visual Basic 會使用早期系結,而您通常會使用 dispid 系結的任何位置。

我應該使用哪種形式的系結?

這個問題的答案與任何其他專案一樣取決於您的專案設計。 Microsoft 建議在幾乎所有情況下都使用早期系結。 不過,選擇晚期系結可能會有一些原因。

早期系結是慣用的方法。 這是最佳效能,因為您的應用程式會直接系結至所呼叫函式的位址,而且執行執行時間查閱不會產生額外的額外負荷。 就整體執行速度而言,其速度至少是晚期繫結速度的兩倍。

早期系結也提供型別安全性。 當您將參考設定為元件的類型程式庫時,Visual Basic 會提供 IntelliSense 支援,協助您正確地撰寫每個函式的程式碼。 如果參數或傳回值的資料類型不正確,Visual Basic 也會警告您,這會在撰寫和偵錯程式碼時節省許多時間。

在設計階段不知道物件的確切介面時,晚期繫結仍然很有用。 如果您的應用程式想要與多部未知的伺服器通訊,或需要使用 Visual Basic 6.0 CallByName 函式 (依名稱叫用函式,例如) 則您需要使用晚期系結。 晚期系結也有助於解決元件的多個版本之間的相容性問題,該元件在版本之間未正確修改或調整其介面。

提供早期系結的優點可讓您盡可能成為最佳選擇。

維護多個版本之間的相容性

如果您將使用未與安裝套件一起轉散發的元件,而且無法確保您將在執行時間與 之通訊的確切版本,您應該特別注意早期系結至與所有元件版本相容的介面,或在某些情況下 () 使用晚期系結來呼叫可能存在於特定版本中的方法,並在正常情況下失敗該方法不存在於用戶端系統上安裝的版本中。

Microsoft Office 應用程式提供這類 COM 伺服器的良好範例。 Office 應用程式通常會擴充其介面,以新增功能或更正版本之間的先前缺點。 如果您需要將 Office 應用程式自動化,建議您提早系結至您預期可以安裝在用戶端系統上的最早版本產品。 例如,如果您需要能夠將 Excel 95、Excel 97、Excel 2000 和 Excel 2002 自動化,您應該使用 Excel 95 的類型程式庫 (XL5en32.olb) 來維持與這三個版本的相容性。

Office 應用程式也示範具有大型雙重介面的物件模型在某些平臺上封送處理時可能會受到限制。 若要讓程式碼在所有平臺上運作最佳,請使用 IDispatch。

參考

如需 COM、v-tables 和 Automation 的詳細資訊,請參閱下列書籍:

在 COM、MSPRESS、ISBN 內,消費者:1-57231-349-8。

Curland、Matt、Advanced Visual Basic 6、DevelopMentor 0201707128。