類型為 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 介面)。 此介面在產生的型態庫中會以 IDispatch (UnmanagedType) 或 IUnknown (UnmanagedType.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_ERROR 與 E_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 類型,可以使用 ErrorWrapper、DispatchWrapper、UnknownWrapper 和 CurrencyWrapper 等包裝函式類別予以封送處理。 下列程式碼範例示範如何使用這些包裝函式將各種類型的 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 | 只有當類型不變更時。 |