物件的預設封送處理
類型為 System.Object 的參數和欄位可以向 Unmanaged 程式碼公開為下列類型之一:
Variant,當物件是參數時。
介面,當物件是結構欄位時。
只有 COM Interop 支援封送處理物件類型。 預設行為是封送處理 COM Variant 的物件。 這些規則只適用於物件類型,不適用於衍生自物件類別的強型別物件。
封送處理選項
下表顯示 [物件] 資料類型的封送處理選項。 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 將物件公開為介面時,該介面是 Managed 類型 Object 的類別介面 (_Object 介面)。 在產生的型別程式庫中,這個介面類型為 IDispatch (UnmanagedType) 或 IUnknown (UnmanagedType.IUnknown)。 COM 用戶端可以動態方式叫用 Managed 類別的成員,或其衍生類別透過 _Object 介面所實作的任何成員。 用戶端也可以呼叫 QueryInterface 取得 Managed 類型明確實作的任何其他介面。
將物件封送處理為變體
當物件封送處理為變體時,會根據下列規則,在執行階段決定內部變體類型:
如果物件參考為 null (在 Visual Basic 中為Nothing),物件會封送處理為 VT_EMPTY 類型的變體。
如果物件是下表所列任一類型的執行個體,則產生的變體類型是由封送處理器內建的規則決定,並顯示在資料表中。
需要明確控制封送處理行為的其他物件,可以實作 IConvertible 介面。 在此情況下,Variant 類型是由 IConvertible.GetTypeCode 方法傳回的類型程式碼所決定。 否則,物件會封送處理為 VT_UNKNOWN 類型的變體。
將系統類型封送處理為變體
下表顯示 Managed 物件類型及其對應的 COM Variant 類型。 只有當正在呼叫的方法簽章是 System.Object 類型時,這些類型才會轉換。
Object type | 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 介面,從 IConvertible.GetTypeCode 方法傳回的 TypeCode 列舉值會在執行階段決定 COM Variant 類型。
下表顯示可能的 TypeCode 列舉值和每個值對應的 COM Variant 類型。
TypeCode | 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 Variant 的值是透過呼叫 IConvertible.To Type 介面所決定;其中 To Type 是轉換常式,對應到從 IConvertible.GetTypeCode 傳回的類型。 例如,從 IConvertible.GetTypeCode 傳回 TypeCode.Double 的物件,會封送處理成 VT_R8 類型的 COM 變體。 您可以透過轉換為 IConvertible 介面及呼叫 ToDouble 方法,取得 Variant 的值 (儲存在 COM Variant 的 dblVal 欄位)。
將變體封送處理為物件
將變體封送處理為物件時,已封送處理的變體類型 (有時是值),可以判斷所產生的物件類型。 下表可以識別當變體從 COM 傳遞至 .NET Framework 時,封送處理器建立的每個變體類型和對應的物件類型。
COM Variant 類型 | Object type |
---|---|
VT_EMPTY | Null 物件參考 (在 Visual Basic 中為 Nothing)。 |
VT_NULL | System.DBNull |
VT_DISPATCH | System.__ComObject or null if (pdispVal == null) |
VT_UNKNOWN | System.__ComObject or 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 類型的 Variant 從 COM 傳遞至 .NET Framework 時,請考慮會發生什麼情況。 在封送處理期間,變體會轉換成 System.Object。 如果接著將物件傳送回 COM,它會封送處理回 VT_UNKNOWN 類型的變體。 當物件從受控程式碼封送處理到 COM 時產生的變體,不保證和最初用來產生物件的變體是同一類型。
封送處理 ByRef 變體
雖然 Variant 本身可以傳值方式或傳址方式傳遞,VT_BYREF 旗標也可搭配任何 Variant 類型使用來表示 Variant 的內容正在以傳址方式傳遞,不是以傳值方式傳遞。 以傳址方式封送處理的變體和封送處理設有 VT_BYREF 旗標的變體之間的差異,會造成混淆。 下圖將釐清這些差異:
藉傳值傳遞和藉傳址傳遞的變體
以值方式封送處理物件和變體的預設行為
當從受控程式碼將物件傳送至 COM 時,會使用將物件封送處理成變體中定義的規則,將物件的內容複製到封送處理器所建立的新變體中。 對 Unmanaged 端的 Variant 所做的變更,在從呼叫傳回時,不會傳播回原始物件。
當從 COM 將變體傳送至受控程式碼時,會使用將變體封送處理成物件中定義的規則,將變體的內容複製到新建立的物件中。 對 Unmanaged 端的 Variant 所做的變更,在從呼叫傳回時,不會傳播回原始物件。
以傳址方式封送處理物件和變體的預設行為
若要將變更傳播回呼叫端,必須以傳址方式傳遞參數。 例如,您可以在 C# 中使用 ref 關鍵字 (或在 Visual Basic 的 Managed 程式碼中使用 ByRef),以傳址方式傳遞參數。 在 COM 中,參考參數是使用 Variant * 等指標傳遞。
以傳址方式將物件傳遞給 COM 時,封送處理器會建立新的變體,先將物件參考的內容複製到變體,再進行呼叫。 Variant 會傳遞至 Unmanaged 函式,使用者可在此任意變更 Variant 的內容。 對 Unmanaged 端的 Variant 所做的變更,在從呼叫傳回時,會傳播回原始物件。 如果 Variant 的類型和傳遞至呼叫的 Variant 類型不同,則變更會傳播回不同類型的物件。 也就是說,傳遞至呼叫的物件類型,可以不同於從呼叫傳回的物件類型。
以傳址方式將變體傳遞給受控程式碼時,封送處理器會建立新的物件,先將變體的內容複製到物件,再進行呼叫。 物件參考會傳遞至 Managed 函式,使用者可在此任意變更物件。 對參考的物件所做的任何變更,在從呼叫傳回時,會傳播回原始的 Variant。 如果物件的類型和傳遞給呼叫的物件類型不同,原始 Variant 類型就會變更,值也會傳播回 Variant。 同樣地,傳遞至呼叫的 Variant 類型,可以不同於從呼叫傳回的 Variant 類型。
封送處理設有 VT_BYREF 旗標的變體的預設行為
正以傳值方式傳遞至 Managed 程式碼的 Variant 可設定 VT_BYREF 旗標,以表示 Variant 包含的是參考,不是值。 如果是這樣,變體仍會被封送處理成物件,因為變體正以傳值方式傳遞。 封送處理器會自動取值變體的內容,並將它先複製到新建立的物件中,再進行呼叫。 然後物件會傳遞到 Managed 函式,但在從呼叫傳回時,該物件不會傳播回原始的 Variant。 遺失對 Managed 物件所做的變更。
警告
沒有任何方法可以變更以傳值方式傳遞的 Variant 值,即使 Variant 設定了 VT_BYREF 旗標。
正以傳址方式傳遞至 Managed 程式碼的 Variant 也可以設定 VT_BYREF 旗標,以表示 Variant 包含另一個參考。 如果是這樣,變體會被封送處理成 ref 物件,因為變體正以傳址方式傳遞。 封送處理器會自動取值變體的內容,並將它先複製到新建立的物件中,再進行呼叫。 在從呼叫傳回時,只有當物件的類型和傳入的物件相同時,物件的值才會傳播回原始 Variant 內的參考。 也就是傳播不會變更設有 VT_BYREF 旗標的 Variant 類型。 如果物件的類型在呼叫期間變更,從呼叫傳回時,就會發生 InvalidCastException。
下表摘要說明 Variant 和物件的傳播規則。
從 | 至 | 變更傳播回 |
---|---|---|
Variant v | Object o | 永不 |
Object o | Variant v | 永不 |
Variant * pv | Ref Object o | 永遠 |
Ref object o | Variant * pv | 永遠 |
Variant v (VT_BYREF | VT_*) | Object o | 永不 |
Variant v (VT_BYREF | VT_) | Ref Object o | 只有當類型不變更時。 |