Excel VBA 教學:VBA 早期綁定與後期綁定



關於綁定 Binding是一個英文單字,意思為綁定。
當一個物件被指派給一個物件變數時,就會發生一個稱為綁定的過程。
有兩種方法,早期綁定和後期綁定(也稱為延遲綁定),
而綁定具體取決於它們的編寫方式。

關於早期綁定和後期綁定概念
可參考微軟網頁
https://learn.microsoft.com/zh-tw/dotnet/visual-basic/programming-guide/language-features/early-late-binding/


目錄

{tocify} $title={目錄} 


早期綁定

當物件指派給宣告為特定物件類型的變數時,物件會被預先綁定(在編譯時)。
對於早期綁定對象,在應用程式運行之前,
編譯器執行記憶體分配和其他最佳化。
然而,對於外部對象,需要參考設定。


早期綁定的優點

在撰寫程式時盡可能使用早期綁定物件。
這允許編譯器執行重要的最佳化,使應用程式能更有效率。
早期綁定物件的處理速度比後期綁定物件快。
因為它清楚地表明正在使用什麼樣的物件,這使得程式碼更具可讀性並且更易於維護。
另一個好處是啟用了自動程式碼完成,因此可以看到屬性和方法的清單。

例如要使用 FSO (FileSystemObject)物件時,可從VBE編譯器的工具功能表,
選擇設定引用項目,選擇Miscrosoft Scripting Runtime,  按下確定即可使用。

關於FSO物件




    由於設定引用FSO物件,因此在使用時,使用New 物件,
    也因為啟用了自動程式碼完成,因此可看到屬性和方法的清單。





上述程式碼可撰寫為

範例

'第一種寫法
Dim myFSO As New FileSystemObject
    
'第二種寫法
Dim myFSO As FileSystemObject
Set myFSO = New FileSystemObject


後期綁定

當物件被指派給宣告為 Object 類型的變數時,該物件會被延遲綁定(在執行時)。
這種類型的物件可以保存對任何物件的引用,但是它幾乎沒有早期綁定物件的優點。


如果FSO物件要使用後期綁定,則需使用CreateObject函數進行綁定。




範例

Dim myFSO As Object
Set myFSO = CreateObject("Scripting.FileSystemObject")



簡單的說
宣告變數的型態,如果是Object就是後期綁定,如果是特定物件型態就是早期綁定。


一句話總結

早期綁定:在編譯期就知道物件型別(例如 Dim wd As Word.Application)。有 IntelliSense、型別檢查、通常較快,但需要在「工具 ▸ 參考」勾對應程式庫。

後期綁定:只宣告成 Object,到執行期才解析(CreateObject/GetObject)。彈性高、跨版本友好,但沒 IntelliSense、列舉常數要改寫成數值,呼叫通常較慢。


對照表

面向 早期綁定 後期綁定 何時優先
定義 編譯期已知具體型別 執行期動態解析 ——
變數宣告 Dim wd As Word.Application Dim wd As Object ——
建立
/取得物件
New
CreateObject/GetObject(但型別仍已知)
CreateObject("Word.Application")
 或 GetObject
散佈到多版本環境 → 後期
參考設定 需要在 VBE 勾「參考」 不需要(靠 ProgID) 內部環境一致 → 早期
IntelliSense 開發效率 → 早期
型別檢查 編譯期即檢查 執行期才出錯 穩定性 → 早期
效能 較快(走 v-table) 較慢(IDispatch 查找) 高頻呼叫 → 早期
版本/部署 對版本敏感、易出現 MISSING 較彈性、跨版本相容 大規模發佈 → 後期
列舉常數
/成員
直接用名稱(如 wdFormatPDF 需用數值(如 17 大量常數 → 早期
具名參數 支援 不支援(只能位置參數) 需大量選填參數 → 早期
事件 可用(需已知型別) 不適合 需要事件 → 早期
除錯體驗 較弱 開發期 → 早期
維護成本 需維護參考版本 需記數值常數 看團隊控管


什麼情況用哪種?


1.    公司內部、版本統一、重視效能/可讀性 → 早期綁定。

2.    要發給許多人、Office 版本/位元數不一致 → 後期綁定(避免參考遺失)。

3.    需要 WithEvents 或大量使用列舉常數、具名參數 → 早期綁定。

4.    同時要爽開發又要穩發佈 → 開發用早期,上線切後期(條件編譯)。



實際範例

範例 1:自動化 Word(早期綁定 vs 後期綁定)


早期綁定(開發期建議)
(先勾:工具 ▸ 參考 ▸ Microsoft Word xx.x Object Library)
Option Explicit

Sub Word_早期綁定()
    Dim wdApp As Word.Application
    Dim wdDoc As Word.Document

    Set wdApp = New Word.Application   ' 也可 CreateObject,重點是型別已知
    wdApp.Visible = True

    Set wdDoc = wdApp.Documents.Add
    wdDoc.Content.Text = "Hello, Word (早期綁定)"
    wdDoc.SaveAs2 ThisWorkbook.Path & "\early.docx"
    wdDoc.Close False
    wdApp.Quit
End Sub

後期綁定(發佈給多版本時)
Option Explicit

Sub Word_後期綁定()
    Dim wdApp As Object
    Dim wdDoc As Object

    Set wdApp = CreateObject("Word.Application")
    wdApp.Visible = True

    Set wdDoc = wdApp.Documents.Add
    wdDoc.Content.Text = "Hello, Word (後期綁定)"
    ' 常數要用數值;具名參數不可用,只能位置參數
    wdDoc.SaveAs2 ThisWorkbook.Path & "\late.docx"
    wdDoc.Close False
    wdApp.Quit
End Sub


重點:綁定的差別在「變數型別宣告」,不是你用 New/CreateObject 的方式。


範例 2:Scripting.Dictionary(常數差異)

早期綁定(勾 Microsoft Scripting Runtime)
Sub Dict_早期()
    Dim dict As Scripting.Dictionary
    Set dict = New Scripting.Dictionary
    dict.CompareMode = TextCompare   ' 可用名稱
    dict("A") = 1
End Sub


後期綁定(不勾參考)
Sub Dict_後期()
    Dim dict As Object
    Set dict = CreateObject("Scripting.Dictionary")
    dict.CompareMode = 1   ' TextCompare = 1;BinaryCompare = 0
    dict("A") = 1
End Sub


範例 3:ADO 連線(具名參數與常數)


早期綁定(勾 Microsoft ActiveX Data Objects x.x Library)
Sub ADO_早期()
    Dim cn As ADODB.Connection
    Set cn = New ADODB.Connection
    cn.Open "Provider=SQLOLEDB;Data Source=.;Initial Catalog=Test;Integrated Security=SSPI;"
End Sub


後期綁定
Sub ADO_後期()
    Dim cn As Object
    Set cn = CreateObject("ADODB.Connection")
    cn.Open "Provider=SQLOLEDB;Data Source=.;Initial Catalog=Test;Integrated Security=SSPI;"
End Sub

若要用 adOpenKeyset 等常數,後期綁定需改用對應數值;且具名參數不可用。

折衷:開發用早期,上線切後期(條件編譯)
Option Explicit
#Const EARLY_BIND = True   ' 上線前切成 False

Sub Word_混合()
#If EARLY_BIND Then
    Dim wdApp As Word.Application
    Dim wdDoc As Word.Document
    Set wdApp = New Word.Application
#Else
    Dim wdApp As Object, wdDoc As Object
    Set wdApp = CreateObject("Word.Application")
#End If

    Set wdDoc = wdApp.Documents.Add
    wdDoc.Content.Text = "Hybrid Binding"
    wdDoc.SaveAs2 ThisWorkbook.Path & "\hybrid.docx"
    wdDoc.Close False
    wdApp.Quit
End Sub

清理與釋放(兩種綁定規則相同)


1.    有新開外部應用程式實例(例如 CreateObject("Word.Application"))→ 關文件、Quit、Set = Nothing。

2.    握外部資源(ADO 連線、檔案流、Stream 等)→ Close 後 Set = Nothing。

3.    只是宿主內部物件(如 Range/Worksheet),且沒有新開 Application → 讓它跟著 Sub 結束即可,不必到處 Set = Nothing。

快速選擇清單(結論)


1.    要 IntelliSense/型別保護/效能 → 早期綁定。

2.    要 最大相容/免參考設定 → 後期綁定。

3.    需要事件、具名參數、或大量列舉常數 → 早期綁定。

4.    兩者都想要 → 開發期早期綁定,上線前改後期綁定(條件編譯切換)。


綁定後的物件是否要在最後釋放?

要不要釋放?原理先講清楚

    VBA 作用域結束就會自動減少參考計數:區域變數離開 Sub/Function(或 With 區塊不算)時,參考計數會少 1;當計數歸零,COM 物件可被銷毀。

    Set obj = Nothing 的作用:手動把你的那一份參考歸零,提早斷開;不是「強制 GC」,而是提早減一。


為何仍會有殭屍 EXCEL/WORD.EXE?

    你新開了外部應用程式(CreateObject("Word.Application")),卻沒 Quit、或還有其他物件(文件、Range、Selection…)握著參考。

    或你用 GetObject 接上別人已開的實例,卻把它 Quit 掉,把使用者手上的程式關了(雖不是洩漏,但會挨罵)。


什麼時候一定要釋放(或關閉)?


優先記這幾個規則,就很少出事:

1.    你「新開」了外部應用程式的實例(Word/Outlook/PowerPoint/IE/WMI…)

範例:Set app = CreateObject("Word.Application")

要做:先關子物件(文件/簡報/錄影),再 app.Quit,最後 Set app = Nothing。

原因:你是「實例的擁有者」。不關,背景行程就會留著、檔案也可能被鎖。


2.    可關閉的資源型物件

ADO/DAO:rs.Close、cn.Close 後 Set … = Nothing

檔案流/TextStream:ts.Close 後 Set … = Nothing

ADODB.Stream、FileSystemObject 開的檔案把手、WScript.Shell 回傳的執行程序控制物件…

原因:這些握著外部資源/把手,不關就會鎖檔或拖資源。


3.    有事件(WithEvents)的物件

表單上的 WithEvents Outlook.Application、檔案系統監聽等。

要做:物件不再需要時 Set … = Nothing,避免表單關掉後還在收事件,衝到無效事件接收端。


4.    長時間跑的巨集

批次腳本、迴圈大量建立大物件。

建議:用完一批就釋放,避免記憶體上上下下、效能飄。




什麼時候可以不必特地釋放?


1.    純區域、生命周期很短的 Excel 物件(Range、Worksheet 變數)且你沒有新開 Application 實例。

這類通常讓它「跟著 Sub 結束一起回收」就好。到處加 Set x = Nothing 反而雜訊多。

2.    宿主本尊的 Application(你正在 Excel 裡跑 VBA 的那個 Application)

不要對它 Quit。設 Nothing 也沒意義。

3.    只讀到的現成物件參考,例如 Set ws = ActiveSheet,Sub 結束就會自然釋放你手上的那份參考。



若沒有妥善釋放,會出什麼狀況?


1.    背景行程殭屍:EXCEL/WORD/POWERPNT.EXE 留在工作管理員。

2.    檔案鎖住:下次開檔跳「唯讀/已被使用中」。

3.    事件亂竄:表單關了還在觸發事件,冒出「物件變數未設定」之類執行期錯誤。

4.    長跑腳本越跑越慢/越吃記憶體。

好用的清理範式(含「我開的才關」)

Sub UseWordSafely()
    Dim wd As Object, doc As Object
    Dim iOwnApp As Boolean

    On Error Resume Next
    Set wd = GetObject(, "Word.Application") ' 先接現有
    On Error GoTo 0
    If wd Is Nothing Then
        Set wd = CreateObject("Word.Application") ' 接不到就新開
        iOwnApp = True
    End If

    Set doc = wd.Documents.Add
    ' ... 做事 ...

CleanExit:
    On Error Resume Next
    If Not doc Is Nothing Then
        ' 視需求是否儲存:doc.Save
        doc.Close SaveChanges:=False
        Set doc = Nothing
    End If

    ' 只有在「我開的實例」才負責關
    If iOwnApp Then wd.Quit
    Set wd = Nothing
    On Error GoTo 0
End Sub


為什麼這樣寫?

1.    不會把使用者本來就開著的 Word 關掉。

2.    先關「子」再關「母」,避免父層因子物件仍被參考而離不開。

3.    無論中途是否錯誤,最後都會走到 CleanExit 清乾淨。



Excel 物件的小眉角


1.    有人會養成「依層級反向釋放」的習慣:Set rng = Nothing → Set ws = Nothing → Set wb = Nothing → 最後 app.Quit。不是硬性規定,但能降低遺留參考的風險。

2.    With 區塊不會生成獨立的參考;真正會影響的是你用 Set 指向某個 COM 物件的變數。

3.    你如果沒有新開 Excel,只是在 Excel 的 VBA 里跑,不要 Application.Quit。


ADO / 檔案流的簡潔收尾

If Not rs Is Nothing Then If rs.State <> 0 Then rs.Close: Set rs = Nothing
If Not cn Is Nothing Then If cn.State <> 0 Then cn.Close: Set cn = Nothing

If Not ts Is Nothing Then ts.Close: Set ts = Nothing   'TextStream



別被這兩個迷思騙了


1.   「所有物件都要 Set … = Nothing」:
        過度。區域小物件讓它自然離開作用域即可。

2.    「設成 Nothing 會立刻把記憶體還給系統」:
        你只是在減參考數;何時釋放底層資源,由 COM 物件自己決定


快速判斷表


1.    我有 CreateObject 開外部 App? 
        → 關文件/Quit/Set = Nothing(一定做)。

2.    我只是碰宿主 Excel 的 Range/Sheet? 
        → 可不特地釋放(Sub 結束就好)。

3.    這物件握外部資源(連線/檔案把手/Stream)? 
        → Close + Set = Nothing。

4.    有 WithEvents 嗎? 
        → 不用時就 Set = Nothing 斷訂閱。

5.    我用 GetObject 接上他人實例? 
        → 不要 Quit,只 Set = Nothing。



延伸閱讀推薦:


張貼留言 (0)
較新的 較舊