物件的預設封送處理
型別為 System.Object 的參數和欄位可以公開 (Expose) 給 Unmanaged 程式碼,做為下列其中一個型別:
Variant - 當物件是參數時。
Interface - 當物件是結構欄位時。
只有 COM Interop 支援物件型別的封送處理 (Marshaling)。預設行為是將物件封送處理至 COM Variant。這些規則只能套用至 Object 型別,而且無法套用至衍生自 Object 類別的強型別 (Strongly Typed) 物件。
這個主題提供以下有關封送處理物件型別的其他資訊:
封送處理選項
將物件封送處理至介面
將物件封送處理至 Variant
將 Variant 封送處理至物件
封送處理 ByRef Variant
封送處理選項
下表顯示 Object 資料型別的封送處理選項。MarshalAsAttribute 屬性提供多個 UnmanagedType 列舉值以封送處理物件。
列舉型別 | Unmanaged 格式的說明 |
---|---|
UnmanagedType.Struct |
COM-Style 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 封送處理器會在呼叫後自動釋放 Variant 內的任何配置物件。 |
下列範例顯示格式化的實值型別 (Value Type)。
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.IDispatch) 或 IUnknown (UnmanagedType.IUnknown)。COM 用戶端可以動態地透過 _Object 介面叫用 Managed 類別的成員或其衍生類別 (Derived Class) 所實作的任何成員。用戶端也可以呼叫 QueryInterface,取得任何其他由 Managed 型別明確實作的介面。
將物件封送處理至 Variant
將物件封送處理至 Variant 時,會根據以下規則來決定執行階段時內部 Variant 型別:
如果物件參考是 null (在 Visual Basic 中為 Nothing),則該物件會封送處理至 VT_EMPTY 型別的 Variant。
如果物件是任何列在下表中的任何型別之執行個體,則內建在封送處理器的規則會決定產生的 Variant 型別,如下表所示。
其他需要明確控制封送處理行為的物件可以實作 IConvertible 介面。在這種情況下,從 IConvertible.GetTypeCode 方法傳回的型別程式碼會決定 Variant 型別。否則,物件會封送處理為 VT_UNKNOWN 型別的 Variant。
將系統型別封送處理至 Variant
下表顯示 Managed 物件型別和其對應的 COM Variant 型別。這些型別只有在呼叫的方法簽章 (Singature) 是 System.Object 型別時才會進行轉換。
物件型別 | COM Variant 型別 |
---|---|
Null 物件參考 (在 Visual Basic 中為 Nothing) |
VT_EMPTY |
VT_NULL |
|
VT_ERROR |
|
VT_ERROR 和 E_PARAMNOTFOUND |
|
VT_DISPATCH |
|
VT_UNKNOWN |
|
VT_CY |
|
VT_BOOL |
|
VT_I1 |
|
VT_UI1 |
|
VT_I2 |
|
VT_UI2 |
|
VT_I4 |
|
VT_UI4 |
|
VT_I8 |
|
VT_UI8 |
|
VT_R4 |
|
VT_R8 |
|
VT_DECIMAL |
|
VT_DATE |
|
VT_BSTR |
|
VT_INT |
|
VT_UINT |
|
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.
您可以使用包裝函式類別 (Wrapper Class) 如 ErrorWrapper、DispatchWrapper、UnknownWrapper 和 CurrencyWrapper,封送處理沒有對應 Managed 型別的 COM 型別。下列程式碼範例示範如何使用這些包裝函式,將不同的 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.InteropSevices 命名空間中。
將 IConvertible 介面封送處理至 Variant
除了在上一節中所列的型別之外,型別可以藉由實作 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.ToType** 介面決定,其中 ToType** 為轉換常式,會對應從 IConvertible.GetTypeCode 傳回的型別。例如,從 IConvertible.GetTypeCode 傳回 TypeCode.Double 的物件,會封送處理為 VT_R8 型別的 COM Variant。透過轉型 (Casting) 至 IConvertible 介面和呼叫 ToDouble 方法,您可以取得 Variant (存放在 COM Variant 的 dblVal 欄位) 的值。
將 Variant 封送處理至物件
將 Variant 封送處理至物件時,封送處理的 Variant 之型別 (有時候是值) 會決定產生的物件型別。下表識別每一個 Variant 型別和對應的物件型別 (此物件型別是 Variant 從 COM 傳遞到 .NET Framework 時由封送處理器所建立的)。
COM Variant 型別 | 物件型別 |
---|---|
VT_EMPTY |
Null 物件參考 (在 Visual Basic 中為 Nothing) |
VT_NULL |
|
VT_DISPATCH |
System.__ComObject 或如果 (pdispVal == null) 則為 null |
VT_UNKNOWN |
System.__ComObject 或如果 (punkVal == null) 則為 null |
VT_ERROR |
|
VT_BOOL |
|
VT_I1 |
|
VT_UI1 |
|
VT_I2 |
|
VT_UI2 |
|
VT_I4 |
|
VT_UI4 |
|
VT_I8 |
|
VT_UI8 |
|
VT_R4 |
|
VT_R8 |
|
VT_DECIMAL |
|
VT_DATE |
|
VT_BSTR |
|
VT_INT |
|
VT_UINT |
|
VT_ARRAY | VT_* |
|
VT_CY |
|
VT_RECORD |
對應的 Boxed 實值型別 |
VT_VARIANT |
不支援 |
從 COM 傳遞到 Managed 程式碼然後回到 COM 的 Variant 型別可能無法在呼叫期間保持相同的 Variant 型別。請考量當 VT_DISPATCH 型別的 Variant 是從 COM 傳遞到 .NET Framework 時會發生什麼狀況。在封送處理期間,Variant 會轉換成 System.Object。如果接著將 Object 傳回 COM,它會封送處理回 VT_UNKNOWN 型別的 Variant。無法保證在從 Managed 程式碼將物件封送處理至 COM 時產生的 Variant,會和最初用來產生該物件的 Variant 有相同的型別。
封送處理 ByRef Variant
雖然 Variant 本身可以利用傳值 (By Value) 或傳址 (By Reference) 方式進行傳遞,但 VT_BYREF 旗標也可以與任何 Variant 型別一起使用,來指示 Variant 的內容是以傳址方式而非傳值方式傳遞。以傳址方式封送處理 Variant 和以 VT_BYREF 旗標組封送處理 Variant 之間的差異可能會令人產生混淆。下圖會澄清這個差異。
以傳值或傳址方式傳遞的 Variant
以傳值方式封送處理物件和 Variant 的預設行為
將物件從 Managed 程式碼傳遞至 COM 時,會使用將物件封送處理至 Variant 中定義的規則 (Rule),物件的內容複製到封送處理器所建立的新 Variant 中。對 Unmanaged 端上的 Variant 所做的變更並不會在從呼叫中返回時傳回至原始物件。
將 Variant 從 COM 傳遞至 Managed 程式碼時,會使用將 Variant 封送處理至物件中定義的規則,將 Variant 的內容複製到全新建立的物件中。對 Managed 端上的物件所做的變更並不會在從呼叫中返回時傳回至原始 Variant。
以傳址方式封送處理物件和 Variant 的預設行為
若要將變更傳回至呼叫端,參數必須是以傳址方式傳遞。例如,您可以使用 C# 中的 ref 關鍵字 (或 Visual Basic Managed 程式碼中的 ByRef),以傳址方式傳遞參數。在 COM 中,傳址參數是使用指標 (例如 variant *) 傳遞的。
以傳址方式將物件傳遞至 COM 時,封送處理器會建立新的 Variant,並且在呼叫之前將物件參考的內容複製到 Variant。Variant 會傳遞到使用者可以隨意變更 Variant 內容的 Unmanaged 函式中。從呼叫中返回時,任何對 Unmanaged 端上的 Variant 所做的變更會傳回至原始物件。如果 Variant 的型別不同於傳回給呼叫的 Variant 的型別,則變更會傳回至不同型別的物件。也就是說,傳遞至呼叫中的物件型別可以不同於從呼叫中傳回的物件型別。
以傳址方式將 Variant 傳遞至 COM 時,封送處理器會建立新的物件,並且在呼叫之前將 Variant 的內容複製到物件。物件的參考會傳遞到使用者可以隨意變更物件的 Managed 函式中。從呼叫中返回時,任何對參考的物件所做的變更會傳回至原始 Variant。如果物件的型別不同於傳入至呼叫的物件型別,則會變更原始 Variant 的型別,而且將值傳回至 Variant。再者,傳遞至呼叫中的 Variant 型別可以不同於從呼叫中傳回的 Variant 型別。
以 VT_BYREF 旗標組封送處理 Variant 的預設行為
以傳值方式傳遞到 Managed 程式碼的 Variant 可以具有 VT_BYREF 旗標組,以指示 Variant 包含參考而不是值。在這種情況下,因為 Variant 是以傳值方式傳遞,因此仍然會封送處理至物件。封送處理器會在產生呼叫之前,自動將 Variant 的內容解除參考,並將它複製到新建立的物件。這個物件接著會傳遞到 Managed 函式,但是在從呼叫中返回時,物件不會傳回至原始的 Variant。而對 Managed 物件所做的變更將會遺失。
警告
即使 Variant 已設定 VT_BYREF 旗標,還是無法變更以傳值方式傳遞的 Variant 值。
以傳址方式傳遞到 Managed 程式碼的 Variant 也可以具有 VT_BYREF 旗標組來指示 Variant 包含其他參考。如果如此,Variant 會封送處理至 ref 物件,因為 Variant 是以傳址方式傳遞。封送處理器會在產生呼叫之前,自動將 Variant 的內容解除參考,並將它複製到新建立的物件。只有在該物件的型別是和傳入的物件一樣時,在從呼叫中返回的時候,這個物件的值會傳回至原始 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 |
只有在未變更型別時 |
請參閱
概念
Blittable 和非 Blittable 型別
方向屬性
複製和 Pin