疑難排解互通性 (Visual Basic)
當您在 COM 與.NET Framework的 Managed 程式碼之間交互操作時,可能會遇到下列一或多個常見問題。
Interop 封送處理
有時候,您可能必須使用不屬於.NET Framework的資料類型。 Interop 組件會處理 COM 物件的大部分工作,但您可能必須控制受控物件公開至 COM 時所使用的資料類型。 例如,類別庫中的結構必須在傳送至 Visual Basic 6.0 和舊版所建立 COM 物件的字串上指定 BStr
非受控類型。 在這種情況下,您可使用 MarshalAsAttribute 屬性將受控類型公開為非受控類型。
將固定長度字串匯出至非受控程式碼
在 Visual Basic 6.0 和舊版中,字串會以位元組序列匯出至 COM 物件,而不會有 Null 終止字元。 為了與其他語言相容,Visual Basic .NET 會在匯出字串時包含終止字元。 解決不相容的最佳方式是,將缺少終止字元的字串匯出為 Byte
或 Char
的陣列。
匯出繼承階層
當公開為 COM 物件時,便會壓扁合併受控類別。 例如,如果您使用成員來定義基底類別,接著在公開為 COM 物件的衍生類別中繼承基底類別,則使用 COM 物件中衍生類別的用戶端將無法使用繼承的成員。 基底類別成員僅能從 COM 物件存取為基底類別的執行個體,僅當基底類別也建立為 COM 物件時才可存取。
多載方法
即使您可使用 Visual Basic 建立多載方法,但 COM 不支援這些方法。 當包含多載方法的類別公開為 COM 物件時,便會產生多載方法的新方法名稱。
例如,請考慮句有兩個 Synch
方法多載的類別。 當類別公開為 COM 物件時,新產生的方法名稱可為 Synch
和 Synch_2
。
重新命名可能會導致 COM 物件的取用者發生兩個問題。
用戶端可能未預期產生的方法名稱。
當新的多載新增至類別或其基底類別時,類別中公開為 COM 物件的產生方法名稱可能會變更。 這可能會導致版本設定問題。
若要解決這兩個問題,請在您開發將公開為 COM 物件的物件時,將每個方法命名為唯一名稱,而不是使用多載。
透過 Interop 組件使用 COM 物件
您會使用 Interop 組件,如同所代表 COM 物件的受控程式碼替代項目一般。 不過,由於這些組件為包裝函式且不是實際的 COM 物件,因此使用 Interop 組件和標準組件之間有一些差異。 這些差異的區域包含類別的公開和參數和傳回值的資料類型。
公開為介面和類別的類別
不同於標準組件中的類別,COM 類別會在 Interop 組件中公開為介面和代表 COM 類別的類別。 介面的名稱與 COM 類別的名稱相同。 Interop 類別的名稱與原始 COM 類別的名稱相同,但附加 "Class" 一詞。 例如,假設您有專案,其中包含 COM 物件的 Interop 組件參考。 如果 COM 類別的名稱為 MyComClass
,IntelliSense 和物件瀏覽器顯示名為 MyComClass
的介面和名為 MyComClassClass
的類別。
建立.NET Framework類別的實例
一般而言,您會使用 New
具有類別名稱的 語句,建立.NET Framework類別的實例。 若您有 Interop 組件所代表的 COM 類別,則可在搭配使用 New
陳述式與介面。 除非您搭配使用 COM 類別與 Inherits
陳述式,否則您可使用介面,如同使用類別一般。 下列程式碼示範如何在具有 Microsoft ActiveX Data Objects 2.8 Library COM 物件參考的專案中建立 Command
物件:
Dim cmd As New ADODB.Command
不過,如果您使用 COM 類別作為衍生類別的基底,則必須使用代表 COM 類別的 Interop 類別,如下列程式碼所示:
Class DerivedCommand
Inherits ADODB.CommandClass
End Class
注意
Interop 組件會隱含實作代表 COM 類別的介面。 您不應嘗試使用 Implements
陳述式來實作這些介面,否則會發生錯誤。
參數和傳回值的資料類型
不同於標準組件的成員,Interop 組件成員的資料類型可能與原始物件宣告中使用的資料類型不同。 即使 Interop 組件會隱含將 COM 類型轉換成相容的通用語言執行平台類型,您仍應注意兩端所使用的資料類型以防止執行階段錯誤。 例如,在 Visual Basic 6.0 和舊版中建立的 COM 物件中,類型的 Integer
值假設.NET Framework對等類型。 Short
建議您先使用物件瀏覽器來檢查匯入成員的特性,再加以使用。
模組層級 COM 方法
在使用 New
關鍵字建立 COM 類別的執行個體並呼叫方法的物件後,會使用大部分 COM 物件。 此規則的其中一個例外狀況涉及包含 AppObj
或 GlobalMultiUse
COM 類別的 COM 物件。 這類類別類似于 Visual Basic .NET 類別中的模組層級方法。 Visual Basic 6.0 和舊版會在您初次呼叫其中一個方法時,為您隱含建立這類物件的執行個體。 例如,在 Visual Basic 6.0 中,您可新增 Microsoft DAO 3.6 Object Library 的參考並呼叫 DBEngine
方法,而無須先建立執行個體:
Dim db As DAO.Database
' Open the database.
Set db = DBEngine.OpenDatabase("C:\nwind.mdb")
' Use the database object.
Visual Basic .NET 會要求您一律建立 COM 物件的實例,才能使用其方法。 若要在 Visual Basic 中使用這些方法,請宣告所需類別的變數,並使用 new 關鍵字來將物件指派給物件變數。 當要確定僅建立一個類別的執行個體時,您可使用 Shared
關鍵字。
' Class level variable.
Shared DBEngine As New DAO.DBEngine
Sub DAOOpenRecordset()
Dim db As DAO.Database
Dim rst As DAO.Recordset
Dim fld As DAO.Field
' Open the database.
db = DBEngine.OpenDatabase("C:\nwind.mdb")
' Open the Recordset.
rst = db.OpenRecordset(
"SELECT * FROM Customers WHERE Region = 'WA'",
DAO.RecordsetTypeEnum.dbOpenForwardOnly,
DAO.RecordsetOptionEnum.dbReadOnly)
' Print the values for the fields in the debug window.
For Each fld In rst.Fields
Debug.WriteLine(fld.Value.ToString & ";")
Next
Debug.WriteLine("")
' Close the Recordset.
rst.Close()
End Sub
事件處理常式中未處理的錯誤
其中一個常見的 Interop 問題包含處理 COM 物件所引發事件的事件處理常式錯誤。 除非您特別使用 On Error
或 Try...Catch...Finally
陳述式檢查錯誤,否則會忽略這類錯誤。 例如,下列範例來自 Visual Basic .NET 專案,該專案具有 Microsoft ActiveX Data Objects 2.8 Library COM 物件的參考。
' To use this example, add a reference to the
' Microsoft ActiveX Data Objects 2.8 Library
' from the COM tab of the project references page.
Dim WithEvents cn As New ADODB.Connection
Sub ADODBConnect()
cn.ConnectionString = "..."
cn.Open()
MsgBox(cn.ConnectionString)
End Sub
Private Sub Form1_Load(ByVal sender As System.Object,
ByVal e As System.EventArgs) Handles MyBase.Load
ADODBConnect()
End Sub
Private Sub cn_ConnectComplete(
ByVal pError As ADODB.Error,
ByRef adStatus As ADODB.EventStatusEnum,
ByVal pConnection As ADODB.Connection) Handles cn.ConnectComplete
' This is the event handler for the cn_ConnectComplete event raised
' by the ADODB.Connection object when a database is opened.
Dim x As Integer = 6
Dim y As Integer = 0
Try
x = CInt(x / y) ' Attempt to divide by zero.
' This procedure would fail silently without exception handling.
Catch ex As Exception
MsgBox("There was an error: " & ex.Message)
End Try
End Sub
此範例會如預期引發錯誤。 不過,如果您嘗試相同範例但不使用 Try...Catch...Finally
區塊,就會與使用 OnError Resume Next
陳述一樣忽略此錯誤。 若未錯誤處理,則除數為零會以無訊息方式失敗。 由於這類錯誤一律不會引發未處理的例外狀況錯誤,因此請務必在從 COM 物件處理事件的處理常式中使用某種形式的例外狀況處理。
瞭解 COM Interop 錯誤
若未錯誤處理,Interop 呼叫通常會產生提供少量資訊的錯誤。 盡可能使用結構化錯誤處理以在發生問題時提供該問題的詳細資訊。 這在您偵錯應用程式時特別實用。 例如:
Try
' Place call to COM object here.
Catch ex As Exception
' Display information about the failed call.
End Try
您可透過檢查例外狀況物件的內容,藉此找到錯誤描述、HRESULT 和 COM 錯誤來源等資訊。
ActiveX 控制項問題
大部分使用 Visual Basic 6.0 的 ActiveX 控制項都與 Visual Basic .NET 搭配運作,而不會發生問題。 主要例外狀況為容器控制項或視覺上包含其他控制項的控制項。 一些不適用於 Visual Studio 的舊版控制項範例如下:
Microsoft Forms 2.0 框架控制項
上下按鈕控制項,也稱為微調控制項
Sheridan 索引標籤控制項
針對不知援 ActiveX 控制項問題,提供一些因應措施。 如果您擁有原始程式碼,則可將現有控制項移轉至 Visual Studio。 否則,您可以向軟體廠商檢查是否有更新的 。與 NET 相容的控制項版本,以取代不支援的 ActiveX 控制項。
傳遞 ByRef 控制項的 ReadOnly 屬性
當您將某些舊版 ActiveX 控制項的屬性當做 ByRef
參數傳遞 ReadOnly
至其他程式時,Visual Basic .NET 有時會引發 COM 錯誤,例如「錯誤0x800A017F CTL_E_SETNOTSUPPORTED」。 透過 Visual Basic 6.0 的相似程序呼叫不會引發錯誤,而且參數會視為依值傳遞。 Visual Basic .NET 錯誤訊息指出您嘗試變更沒有屬性程式的屬性 Set
。
如果您可存取所呼叫程序,您便可使用 ByVal
關鍵字來宣告接受 ReadOnly
屬性的參數以防至此錯誤。 例如:
Sub ProcessParams(ByVal c As Object)
'Use the arguments here.
End Sub
如果您無法存取所呼叫程序的原始程式碼,您可透過在呼叫程式旁新增一組額外括弧,強制依值傳遞屬性。 例如,在具有 Microsoft ActiveX Data Objects 2.8 Library COM 物件參考的專案中,您可使用:
Sub PassByVal(ByVal pError As ADODB.Error)
' The extra set of parentheses around the arguments
' forces them to be passed by value.
ProcessParams((pError.Description))
End Sub
部署公開 Interop 的組件
部署公開 COM 介面的組件時可能會面臨一些獨特的挑戰。 例如,當個別應用程式參考相同的 COM 組件時,就會發生潛在問題。 當安裝新版本組件而其他應用程式仍使用舊版本組件時,這種情況便十分常見。 如果您解除安裝共用 DLL 的組件,您可能會不小心讓其他組件無法使用。
若要避免此問題,您應將共用組件安裝至全域組件快取 (GAC) 並使用 MergeModule 作為元件。 如果您無法在 GAC 中安裝應用程式,則應安裝在版本特定子目錄中的 CommonFilesFolder。
未共用的組件應與呼叫應用程式並排在目錄中。