類型為 System.Object 的參數和欄位可以以其中一種下列類型向非受控程式碼公開:
當物件作為參數時的變體。
當物件是結構欄位時的界面。
只有 COM Interop 支援封送處理物件類型。 預設行為是將物件封送至 COM 各種變體。 這些規則僅適用於型別 Object ,不適用於源自該 Object 類別的強型別物件。
封送處理選項
下表顯示資料 Object 型別的編組選項。
MarshalAsAttribute 屬性提供幾個 UnmanagedType 列舉值以封送物件。
| 列舉類型 | 對未管理格式的描述 |
|---|---|
|
UnmanagedType.Struct (參數預設值) |
COM 樣式的變體。 |
| 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 變體類型 |
|---|---|
| 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 介面封送處理為 Variant 變體
上節中未列出的其他類型,可以透過實作 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 變體類型 | 物件類型 |
|---|---|
| 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 時,封送處理器會依據將物件封送處理成變體中定義的規則,將物件的內容複製到所建立的新變體中。 對非受控端的變體所做的更動,在從呼叫傳回時,不會傳播回原始物件。
當從 COM 將變體傳遞至受控程式碼時,會按照變體封送處理至物件所定義的規則,將變體的內容複製到新建立的物件中。 對管理端物件所做的變更,在呼叫返回時不會傳回至原始變體。
依據參考封送處理物件和變體的預設行為
若要將變更傳播回呼叫端,必須以傳址方式傳遞參數。 例如,你可以在 C#(或 ref Visual Basic 管理程式碼)中使用ByRef關鍵字來透過引用傳遞參數。 在 COM 中,參考參數是使用 Variant * 等指標傳遞。
以傳址方式將物件傳遞給 COM 時,封送處理器會建立新的變體,先將物件參考的內容複製到變體,再進行呼叫。 Variant 會傳遞至 Unmanaged 函式,使用者可在此任意變更 Variant 的內容。 在呼叫返回時,任何對非托管端 Variant 所做的更改會傳回到原始物件。 如果 Variant 的類型和傳遞至呼叫的 Variant 類型不同,則變更會傳播回不同類型的物件。 也就是說,傳遞至呼叫的物件類型,可以不同於從呼叫傳回的物件類型。
以傳址方式將變體傳遞給受控程式碼時,封送處理器會建立新的物件,先將變體的內容複製到物件,再進行呼叫。 物件參考會傳遞至 Managed 函式,使用者可在此任意變更物件。 對被引用的物件所做的任何變更,會在從呼叫返回時傳回至原始的變體。 如果物件的類型和傳遞給呼叫的物件類型不同,原始 Variant 類型就會變更,值也會傳播回 Variant。 同樣地,傳遞至呼叫的 Variant 類型,可以不同於從呼叫傳回的 Variant 類型。
封送處理設有 VT_BYREF 旗標的變體的預設行為
將變體以值傳遞給管理程式碼時,可以設定
VT_BYREF旗標表示該變體包含參考而非值。 如果是這樣,變體仍會被轉換成物件,因為變體正以傳值方式傳遞。 封送處理器會自動取值變體的內容,並將它先複製到新建立的物件中,再進行呼叫。 然後該物件會被傳遞到受管理的函式中,但在函式呼叫返回時,該物件並不會被傳回至原始的變體。 對 受管理物件 的變更已遺失。警告
即使變體已
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 | 只有當類型不變更時。 |