疑難解答互操作性 (Visual Basic)
當您在 COM 與 .NET Framework 的 Managed 程式代碼之間互操作時,可能會遇到下列一或多個常見問題。
Interop Marshalling
有時候,您可能必須使用不屬於 .NET Framework 的數據類型。 Interop 元件會處理 COM 物件的大部分工作,但您可能必須控制 Managed 物件公開至 COM 時所使用的數據類型。 例如,類別庫中的結構必須在傳送至由 Visual Basic 6.0 和較早版本所建立之 COM 物件的字串上指定 BStr
unmanaged 類型。 在這種情況下,您可以使用 MarshalAsAttribute 屬性,將 Managed 類型公開為 Unmanaged 類型。
將 Fixed-Length 字串匯出至非受控程式碼
在 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 物件時,幾乎就像它們是 Managed 程式代碼的替代方案。 不過,因為它們是包裝函式,而不是實際的 COM 物件,所以使用 Interop 元件和標準元件之間有一些差異。 這些差異領域包括類別的曝光,以及參數和傳回值的數據類型。
同時作為介面和類別公開的類別
不同於標準元件中的類別,COM 類別會在 Interop 元件中公開為介面和代表 COM 類別的類別。 介面的名稱與 COM 類別的名稱相同。 Interop 類別的名稱與原始 COM 類別的名稱相同,但附加 「Class」 這個字。 例如,假設您有一個專案,並且參考了一個用於 COM 物件的互操作程序集。 如果 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 類型轉換成相容的 Common Language Runtime 類型,但您應該注意雙方所使用的數據類型,以防止運行時錯誤。 例如,在 Visual Basic 6.0 和舊版中建立的 COM 物件中,Integer
類型的值假設 .NET Framework 對等類型 Short
。 建議您先使用對象瀏覽器來檢查匯入成員的特性,再使用它們。
模組層級的 COM 方法
大部分的 COM 物件都是使用 New
關鍵詞建立 COM 類別的實例,然後呼叫 物件的方法。 此規則的一個例外狀況涉及包含 COM 類別 AppObj
或 GlobalMultiUse
COM 類別的 COM 物件。 這類類別類似於 Visual Basic .NET 類別中的模組層級方法。 Visual Basic 6.0 和舊版會在您第一次呼叫其中一個方法時,隱含地為您建立這類對象的實例。 例如,在 Visual Basic 6.0 中,您可以將參考新增至 Microsoft DAO 3.6 物件庫,並呼叫 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 呼叫通常會產生提供很少信息的錯誤。 盡可能使用結構化錯誤處理來提供有關問題發生時的詳細資訊。 當您除錯應用程式時,這會特別有幫助。 例如:
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 Frame 控件
Up-Down 控件,也稱為旋轉控件
謝里丹索引標籤控件
不支援的 ActiveX 控件問題只有一些解決方法。 如果您擁有原始原始原始程式碼,可以將現有的控件移轉至 Visual Studio。 否則,您可以向軟體廠商檢查是否有更新的 。與 NET 相容的控制項版本,以取代不支援的 ActiveX 控制件。
傳遞 ByRef 控制件的 ReadOnly 屬性
當您將某些舊版 ActiveX 控制件的 ReadOnly
屬性當作 ByRef
參數傳遞至其他程式時,Visual Basic 有時會引發 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。
未共用的元件應該與呼叫的應用程式位於相同的目錄中。