共用方式為


物件的預設封送處理

類型為 System.Object 的參數和欄位可以向 Unmanaged 程式碼公開為下列類型之一:

  • Variant,當物件是參數時。

  • 介面,當物件是結構欄位時。

只有 COM Interop 支援封送處理物件類型。 預設行為是封送處理 COM Variant 的物件。 這些規則僅適用於型別 Object ,不適用於源自該 Object 類別的強型別物件。

封送處理選項

下表顯示資料 Object 型別的編組選項。 MarshalAsAttribute 屬性提供幾種 UnmanagedType 列舉值來封送處理物件。

列舉類型 Unmanaged 格式的描述
UnmanagedType.Struct

(參數預設值)
COM 樣式的 Variant。
UnmanagedType.Interface(非受控類型.介面) 如果可能,就是一個 IDispatch 介面;否則,就是一個 IUnknown 介面。
UnmanagedType.IUnknown

(欄位預設值)
IUnknown 介面。
UnmanagedType.IDispatch IDispatch 介面。

下例顯示 MarshalObject 的 Managed 介面定義。

Interface MarshalObject
   Sub SetVariant(o As Object)
   Sub SetVariantRef(ByRef o As Object)
   Function GetVariant() As Object

   Sub SetIDispatch( <MarshalAs(UnmanagedType.IDispatch)> o As Object)
   Sub SetIDispatchRef(ByRef <MarshalAs(UnmanagedType.IDispatch)> o _
      As Object)
   Function GetIDispatch() As <MarshalAs(UnmanagedType.IDispatch)> Object
   Sub SetIUnknown( <MarshalAs(UnmanagedType.IUnknown)> o As Object)
   Sub SetIUnknownRef(ByRef <MarshalAs(UnmanagedType.IUnknown)> o _
      As Object)
   Function GetIUnknown() As <MarshalAs(UnmanagedType.IUnknown)> Object
End Interface
interface MarshalObject {
   void SetVariant(Object o);
   void SetVariantRef(ref Object o);
   Object GetVariant();

   void SetIDispatch ([MarshalAs(UnmanagedType.IDispatch)]Object o);
   void SetIDispatchRef([MarshalAs(UnmanagedType.IDispatch)]ref Object o);
   [MarshalAs(UnmanagedType.IDispatch)] Object GetIDispatch();
   void SetIUnknown ([MarshalAs(UnmanagedType.IUnknown)]Object o);
   void SetIUnknownRef([MarshalAs(UnmanagedType.IUnknown)]ref Object o);
   [MarshalAs(UnmanagedType.IUnknown)] Object GetIUnknown();
}

下列程式碼會將 MarshalObject 介面匯出至型別程式庫。

interface MarshalObject {
   HRESULT SetVariant([in] VARIANT o);
   HRESULT SetVariantRef([in,out] VARIANT *o);
   HRESULT GetVariant([out,retval] VARIANT *o)
   HRESULT SetIDispatch([in] IDispatch *o);
   HRESULT SetIDispatchRef([in,out] IDispatch **o);
   HRESULT GetIDispatch([out,retval] IDispatch **o)
   HRESULT SetIUnknown([in] IUnknown *o);
   HRESULT SetIUnknownRef([in,out] IUnknown **o);
   HRESULT GetIUnknown([out,retval] IUnknown **o)
}

注意

Interop 封送處理器會在呼叫之後,自動釋放變體內所有已配置的物件。

下例會顯示格式化的實值型別。

Public Structure ObjectHolder
   Dim o1 As Object
   <MarshalAs(UnmanagedType.IDispatch)> Public o2 As Object
End Structure
public struct ObjectHolder {
   Object o1;
   [MarshalAs(UnmanagedType.IDispatch)]public Object o2;
}

下列程式碼會將格式化的類型匯出至型別程式庫。

struct ObjectHolder {
   VARIANT o1;
   IDispatch *o2;
}

將物件封送處理為介面

當物件以介面的形式暴露給 COM 時,該介面是受管理型別Object的類別的介面(_Object 介面)。 此介面在產生的型態庫中會以 IDispatchUnmanagedType) 或 IUnknownUnmanagedType.IUnknown) 型別。 COM 用戶端可透過介面動態呼叫受管理類別的成員,或其衍生類別 _Object 實作的任何成員。 用戶端也可以呼叫 QueryInterface 以獲取由受管理型別明確實作的其他介面。

將物件封送處理為變體

當物件被編組到變體時,內部變體類型會在執行時根據以下規則決定:

  • 如果物件參考為 null (在 Visual Basic 中為Nothing),物件會封送處理為 VT_EMPTY 類型的變體。

  • 如果物件是下表所列任一類型的執行個體,則產生的變體類型是由封送處理器內建的規則決定,並顯示在資料表中。

  • 需要明確控制封送處理行為的其他物件,可以實作 IConvertible 介面。 在此情況下,Variant 類型是由 IConvertible.GetTypeCode 方法傳回的類型程式碼所決定。 否則,物件會封送處理為 VT_UNKNOWN 類型的變體。

將系統類型封送處理為變體

下表顯示 Managed 物件類型及其對應的 COM Variant 類型。 只有當正在呼叫的方法簽章是 System.Object 類型時,這些類型才會轉換。

物件類型 COM Variant 類型
Null 物件參考 (在 Visual Basic 中為 Nothing)。 VT_EMPTY
System.DBNull VT_NULL
System.Runtime.InteropServices.ErrorWrapper VT_ERROR
System.Reflection.Missing VT_ERRORE_PARAMNOTFOUND
System.Runtime.InteropServices.DispatchWrapper VT_DISPATCH
System.Runtime.InteropServices.UnknownWrapper VT_UNKNOWN
System.Runtime.InteropServices.CurrencyWrapper VT_CY
System.Boolean VT_BOOL
System.SByte VT_I1
System.Byte VT_UI1
System.Int16 VT_I2
System.UInt16 VT_UI2
System.Int32 VT_I4
System.UInt32 VT_UI4
System.Int64 VT_I8
System.UInt64 VT_UI8
System.Single VT_R4
System.Double VT_R8
System.Decimal VT_DECIMAL
System.DateTime VT_DATE
System.String VT_BSTR
System.IntPtr VT_INT
System.UIntPtr VT_UINT
System.Array VT_ARRAY

使用前一個範例中定義的 MarshalObject 介面,下列程式碼範例示範如何將各種類型的 Variant 傳送到 COM 伺服器。

Dim mo As New MarshalObject()
mo.SetVariant(Nothing)         ' Marshal as variant of type VT_EMPTY.
mo.SetVariant(System.DBNull.Value) ' Marshal as variant of type VT_NULL.
mo.SetVariant(CInt(27))        ' Marshal as variant of type VT_I2.
mo.SetVariant(CLng(27))        ' Marshal as variant of type VT_I4.
mo.SetVariant(CSng(27.0))      ' Marshal as variant of type VT_R4.
mo.SetVariant(CDbl(27.0))      ' Marshal as variant of type VT_R8.
MarshalObject mo = new MarshalObject();
mo.SetVariant(null);            // Marshal as variant of type VT_EMPTY.
mo.SetVariant(System.DBNull.Value); // Marshal as variant of type VT_NULL.
mo.SetVariant((int)27);          // Marshal as variant of type VT_I2.
mo.SetVariant((long)27);          // Marshal as variant of type VT_I4.
mo.SetVariant((single)27.0);   // Marshal as variant of type VT_R4.
mo.SetVariant((double)27.0);   // Marshal as variant of type VT_R8.

沒有對應受控類型的 COM 類型,可以使用 ErrorWrapperDispatchWrapperUnknownWrapperCurrencyWrapper 等包裝函式類別予以封送處理。 下列程式碼範例示範如何使用這些包裝函式將各種類型的 Variant 傳送到 COM 伺服器。

Imports System.Runtime.InteropServices
' Pass inew as a variant of type VT_UNKNOWN interface.
mo.SetVariant(New UnknownWrapper(inew))
' Pass inew as a variant of type VT_DISPATCH interface.
mo.SetVariant(New DispatchWrapper(inew))
' Pass a value as a variant of type VT_ERROR interface.
mo.SetVariant(New ErrorWrapper(&H80054002))
' Pass a value as a variant of type VT_CURRENCY interface.
mo.SetVariant(New CurrencyWrapper(New Decimal(5.25)))
using System.Runtime.InteropServices;
// Pass inew as a variant of type VT_UNKNOWN interface.
mo.SetVariant(new UnknownWrapper(inew));
// Pass inew as a variant of type VT_DISPATCH interface.
mo.SetVariant(new DispatchWrapper(inew));
// Pass a value as a variant of type VT_ERROR interface.
mo.SetVariant(new ErrorWrapper(0x80054002));
// Pass a value as a variant of type VT_CURRENCY interface.
mo.SetVariant(new CurrencyWrapper(new Decimal(5.25)));

包裝函式類別是在 System.Runtime.InteropServices 命名空間中定義。

將 IConvertible 介面封送處理為變體

上節中未列出的其他類型,可以透過實作 IConvertible 介面來控制封送處理的方式。 若物件實作該IConvertible介面,COM變體類型會在執行時由方法回傳的TypeCode列舉值IConvertible.GetTypeCode來決定。

下表顯示了列舉的可能值 TypeCode 及每個值對應的 COM 變體類型。

類型代碼 COM Variant 類型
TypeCode.Empty VT_EMPTY
TypeCode.Object VT_UNKNOWN
TypeCode.DBNull VT_NULL
TypeCode.Boolean VT_BOOL
TypeCode.Char VT_UI2
TypeCode.Sbyte VT_I1
TypeCode.Byte VT_UI1
TypeCode.Int16 VT_I2
TypeCode.UInt16 VT_UI2
TypeCode.Int32 VT_I4
TypeCode.UInt32 VT_UI4
TypeCode.Int64 VT_I8
TypeCode.UInt64 VT_UI8
TypeCode.Single VT_R4
TypeCode.Double VT_R8
TypeCode.Decimal VT_DECIMAL
TypeCode.DateTime VT_DATE
TypeCode.String VT_BSTR
不支援。 VT_INT
不支援。 VT_UINT
不支援。 VT_ARRAY
不支援。 VT_RECORD
不支援。 VT_CY
不支援。 VT_Variant

COM 變體的值是透過呼叫 IConvertible.To型別 介面來決定的,其中 ToType 是對應於 IConvertible.GetTypeCode 回傳的型別的轉換例程。 例如,從 IConvertible.GetTypeCode 傳回 TypeCode.Double 的物件,會封送處理成 VT_R8 類型的 COM 變體。 你可以將其型別轉換為dblVal介面,並呼叫IConvertible方法來取得變體的值(該值儲存在 COM 變體的ToDouble欄位中)。

將變體封送處理為物件

將變體封送處理為物件時,已封送處理的變體類型 (有時是值),可以判斷所產生的物件類型。 下表可以識別當變體從 COM 傳遞至 .NET Framework 時,封送處理器建立的每個變體類型和對應的物件類型。

COM Variant 類型 物件類型
VT_EMPTY Null 物件參考 (在 Visual Basic 中為 Nothing)。
VT_NULL System.DBNull
VT_DISPATCH System.__ComObject 或 null if (pdispVal == null)
VT_UNKNOWN System.__ComObject 或 null if (punkVal == null)
VT_ERROR System.UInt32
VT_BOOL System.Boolean
VT_I1 System.SByte
VT_UI1 System.Byte
VT_I2 System.Int16
VT_UI2 System.UInt16
VT_I4 System.Int32
VT_UI4 System.UInt32
VT_I8 System.Int64
VT_UI8 System.UInt64
VT_R4 System.Single
VT_R8 System.Double
VT_DECIMAL System.Decimal
VT_DATE System.DateTime
VT_BSTR System.String
VT_INT System.Int32
VT_UINT System.UInt32
VT_ARRAY | VT_* System.Array
VT_CY System.Decimal
VT_RECORD 對應 Boxed 實值型別。
VT_Variant 不支援。

從 COM 傳遞至 Managed 程式碼再回到 COM 的 Variant 類型,在呼叫期間可能不會保留相同的 Variant 類型。 想想當 類型的 VT_DISPATCH 變體從 COM 傳到 .NET Framework 時會發生什麼事。 在封送處理期間,變體會轉換成 System.Object。 若 Object 再交回 COM,則會被編組回 Type VT_UNKNOWN 的變體。 當物件從受控程式碼封送處理到 COM 時產生的變體,不保證和最初用來產生物件的變體是同一類型。

封送處理 ByRef 變體

雖然變體本身可以以值或參考傳遞,但該 VT_BYREF 旗標也可以用於任何變體類型,以表示變體內容是透過參考而非值傳遞。 以參考編組變體與設置VT_BYREF旗標的編組變體之間的差異可能會造成混淆。 下圖將釐清這些差異:

顯示堆疊上傳遞的變數的圖表。 藉傳值傳遞和藉傳址傳遞的變體

以值方式封送處理物件和變體的預設行為

  • 當從受控程式碼將物件傳送至 COM 時,會使用將物件封送處理成變體中定義的規則,將物件的內容複製到封送處理器所建立的新變體中。 對 Unmanaged 端的 Variant 所做的變更,在從呼叫傳回時,不會傳播回原始物件。

  • 當從 COM 將變體傳送至受控程式碼時,會使用將變體封送處理成物件中定義的規則,將變體的內容複製到新建立的物件中。 對 Unmanaged 端的 Variant 所做的變更,在從呼叫傳回時,不會傳播回原始物件。

以傳址方式封送處理物件和變體的預設行為

若要將變更傳播回呼叫端,必須以傳址方式傳遞參數。 例如,你可以在 C#(或 ref Visual Basic 管理程式碼)中使用ByRef關鍵字來透過引用傳遞參數。 在 COM 中,參考參數是使用 Variant * 等指標傳遞。

  • 以傳址方式將物件傳遞給 COM 時,封送處理器會建立新的變體,先將物件參考的內容複製到變體,再進行呼叫。 Variant 會傳遞至 Unmanaged 函式,使用者可在此任意變更 Variant 的內容。 對 Unmanaged 端的 Variant 所做的變更,在從呼叫傳回時,會傳播回原始物件。 如果 Variant 的類型和傳遞至呼叫的 Variant 類型不同,則變更會傳播回不同類型的物件。 也就是說,傳遞至呼叫的物件類型,可以不同於從呼叫傳回的物件類型。

  • 以傳址方式將變體傳遞給受控程式碼時,封送處理器會建立新的物件,先將變體的內容複製到物件,再進行呼叫。 物件參考會傳遞至 Managed 函式,使用者可在此任意變更物件。 對參考的物件所做的任何變更,在從呼叫傳回時,會傳播回原始的 Variant。 如果物件的類型和傳遞給呼叫的物件類型不同,原始 Variant 類型就會變更,值也會傳播回 Variant。 同樣地,傳遞至呼叫的 Variant 類型,可以不同於從呼叫傳回的 Variant 類型。

封送處理設有 VT_BYREF 旗標的變體的預設行為

  • 將變體以值傳遞給管理程式碼時,可以設定 VT_BYREF 旗標表示該變體包含參考而非值。 如果是這樣,變體仍會被封送處理成物件,因為變體正以傳值方式傳遞。 封送處理器會自動取值變體的內容,並將它先複製到新建立的物件中,再進行呼叫。 然後物件會傳遞到 Managed 函式,但在從呼叫傳回時,該物件不會傳播回原始的 Variant。 遺失對 Managed 物件所做的變更。

    警告

    即使變體已 VT_BYREF 設定旗標,也無法更改由數值傳遞的變體值值。

  • 透過參考傳遞給管理程式碼的變體,也可以設定 VT_BYREF 旗標以表示該變體包含另一個參考。 如果有,該變體會被編組到物件 ref ,因為變體是透過參考傳遞。 封送處理器會自動取值變體的內容,並將它先複製到新建立的物件中,再進行呼叫。 在從呼叫傳回時,只有當物件的類型和傳入的物件相同時,物件的值才會傳播回原始 Variant 內的參考。 也就是說,傳播不會改變帶有 VT_BYREF 標誌設定的變體類型。 如果物件的類型在呼叫期間變更,從呼叫傳回時,就會發生 InvalidCastException

下表摘要說明 Variant 和物件的傳播規則。

變更傳播回
Variantv 物件o 永不
物件o Variantv 永不
變體*pv Ref 物件o 永遠
Ref 物件o 變體*pv 永遠
Variantv(VT_BYREF|VT_*) 物件o 永不
Variantv(VT_BYREF|VT_) Ref 物件o 只有當類型不變更時。

另請參閱