關於綁定 Binding是一個英文單字,意思為綁定。
當一個物件被指派給一個物件變數時,就會發生一個稱為綁定的過程。
有兩種方法,早期綁定和後期綁定(也稱為延遲綁定),
而綁定具體取決於它們的編寫方式。
關於早期綁定和後期綁定概念
可參考微軟網頁
https://learn.microsoft.com/zh-tw/dotnet/visual-basic/programming-guide/language-features/early-late-binding/
對於早期綁定對象,在應用程式運行之前,
編譯器執行記憶體分配和其他最佳化。
然而,對於外部對象,需要參考設定。
這種類型的物件可以保存對任何物件的引用,但是它幾乎沒有早期綁定物件的優點。
如果FSO物件要使用後期綁定,則需使用CreateObject函數進行綁定。
簡單的說
目錄
{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。
