Маршаллирование по умолчанию для объектов

Параметры и поля, типизированные как System.Object, могут предоставляться в неуправляемый код в виде одного из следующих типов:

  • Вариант, если объект является параметром.

  • Интерфейс, если объект является полем структуры.

Только COM-взаимодействие поддерживает маршаллирование для типов объектов. По умолчанию выполняется маршалинг объектов в варианты COM. Эти правила применяются только к типу Object и не относятся к строго типизированным объектам, производным от класса Object.

Параметры маршаллинга

В следующей таблице показаны параметры маршалинга для типа данных объекта . Атрибут MarshalAsAttribute предоставляет несколько значений перечисления UnmanagedType для маршалинга объектов.

Тип перечисления Описание неуправляемого формата
UnmanagedType.Struct

(по умолчанию для параметров)
Вариант в стиле COM.
UnmanagedType.Interface Интерфейс IDispatch, если возможно. В противном случае интерфейс IUnknown.
UnmanagedType.IUnknown

(по умолчанию для полей)
Интерфейс IUnknown.
UnmanagedType.IDispatch Интерфейс IDispatch.

В следующем примере показано определение управляемого интерфейса для MarshalObject.

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)
}

Примечание.

Маршализатор взаимодействия автоматически освобождает любой выделенный объект внутри варианта после вызова.

В следующем примере показан форматированный тип значения.

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 для получения любого другого интерфейса, явно реализуемого управляемым типом.

Маршалирование объекта в variant

Когда объект маршалируется в вариант, внутренний тип варианта определяется во время выполнения на основе следующих правил:

  • Если ссылка на объект имеет значение NULL (Nothing в Visual Basic), объект маршалируется в вариант типа VT_EMPTY.

  • Если объект является экземпляром любого типа, указанного в следующей таблице, результирующий тип варианта определяется правилами, встроенными в маршаллировщик и показанными в таблице.

  • Другие объекты, которые должны явно контролировать поведение маршаллинга, могут реализовать IConvertible интерфейс. В этом случае тип варианта определяется в соответствии с кодом типа, возвращаемым из метода IConvertible.GetTypeCode. В противном случае объект маршалируется как вариант типа VT_UNKNOWN.

Маршалирование типов систем в variant

В следующей таблице показаны управляемые типы объектов и соответствующие им варианты модели COM. Эти типы преобразуются только в том случае, если сигнатура вызываемого метода относится к типу System.Object.

Тип объекта Тип варианта COM
Ссылка на объект NULL (Nothing в Visual Basic). 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 демонстрируется передача различных типов вариантов на 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, , DispatchWrapperUnknownWrapperи CurrencyWrapper. В следующем примере кода демонстрируется использование этих классов-оболочек для передачи различных типов вариантов на 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
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.ToType, в котором ToType — это подпрограмма преобразования, которая соответствует типу, возвращаемому из IConvertible.GetTypeCode. Например, объект, возвращающий TypeCode.Double из IConvertible.GetTypeCode, маршалируется как COM-вариант типа VT_R8. Чтобы получить значение варианта (хранится в поле dblVal варианта COM), можно выполнить приведение к интерфейсу IConvertible и вызвать метод ToDouble.

Маршалирование вариантов в объект

При маршалинге варианта в объект тип, а иногда и значение маршаллированного варианта определяет тип создаваемого объекта. В следующей таблице определяется каждый тип варианта и соответствующий тип объекта, который маршаллерирует m, когда вариант передается из COM в платформа .NET Framework.

Тип варианта COM Тип объекта
VT_EMPTY Ссылка на объект NULL (Nothing в Visual Basic).
VT_NULL System.DBNull
VT_DISPATCH System.__ComObject или значение NULL если (pdispVal == null)
VT_UNKNOWN System.__ComObject или значение NULL если (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 Соответствующий тип упакованного значения.
VT_VARIANT Не поддерживается.

Типы вариантов, передаваемые из модели COM в управляемый код и обратно в COM, могут не сохранять тип варианта во время вызова. Рассмотрим, что произойдет при передаче типа варианта VT_DISPATCH из модели COM в .NET Framework. Во время маршаллинга вариант преобразуется в .System.Object Если объект передается обратно в COM, он маршалируется обратно в вариант типа VT_UNKNOWN. Нет никаких гарантий, что вариант, созданный при маршалинге объекта из управляемого кода в COM, будет таким же типом, как и вариант, изначально используемый для создания объекта.

Маршаллирование вариантов ByRef

Сами по себе варианты могут передаваться по значению или по ссылке. Несмотря на это, также можно использовать флаг VT_BYREF с любым типом варианта, чтобы указать, что содержимое варианта передается по ссылке, а не по значению. Разница между вариантами маршаллинга по ссылке и маршаллинга вариантом с набором флагов VT_BYREF может быть запутана. На следующем рисунке показаны различия между этими способами:

Diagram that shows variant passed on the stack. Варианты, передаваемые по значению и по ссылке

Поведение по умолчанию для маршалинга объектов и вариантов по значению

  • При передаче объектов из управляемого кода в COM содержимое объекта копируется в новый вариант, созданный маршаллизатором, с помощью правил, определенных в объекте Маршаллинга в Variant. Изменения, внесенные в вариант в неуправляемом коде, не применяются к исходному объекту после возврата из вызова.

  • При передаче вариантов из COM в управляемый код содержимое варианта копируется в только что созданный объект с помощью правил, определенных в маршалинга varianting Variant to Object. Изменения, внесенные в объект в управляемом коде, не применяются к исходному варианту после возврата из вызова.

Поведение по умолчанию для маршалинга объектов и вариантов по ссылке

Для распространения изменений в вызывающем объекте параметры необходимо передавать по ссылке. Например, можно использовать ключевое слово ref в C# (или ByRef в управляемом коде Visual Basic) для передачи параметров по ссылке. В COM параметры ссылок передаются с помощью указателя, например варианта *.

  • При передаче объекта в COM по ссылке маршаллизатор создает новый вариант и копирует содержимое ссылки на объект в вариант перед вызовом. Вариант передается в неуправляемую функцию, в которой пользователь может изменять его содержимое. После возврата из вызова изменения, внесенные в вариант в неуправляемом коде, применяются к исходному объекту. Если тип варианта отличается от типа варианта, переданного в вызов, изменения применяются к объекту другого типа. Таким образом, тип переданного в вызов объекта может отличаться от типа объекта, возвращаемого из вызова.

  • При передаче варианта управляемому коду по ссылке маршаллизатор создает новый объект и копирует содержимое варианта в объект перед вызовом. Ссылка на объект передается в управляемую функцию, в которой пользователь может изменять сам объект. После возврата из вызова изменения, внесенные в указываемый по ссылке объект, применяются к исходному варианту. Если тип объекта отличается от типа объекта, переданного в вызов, тип исходного варианта изменяется и значение передается обратно в вариант. Аналогичным образом, тип переданного в вызов варианта может отличаться от типа варианта, возвращаемого из вызова.

Поведение по умолчанию для маршалирования варианта с набором флагов VT_BYREF

  • Для варианта, передаваемого в управляемый код по значению, может быть установлен флаг VT_BYREF, который указывает, что вариант содержит ссылку вместо значения. В этом случае вариант по-прежнему маршалируется объекту, так как вариант передается по значению. Маршаллизатор автоматически разыменовывает содержимое варианта и копирует его в только что созданный объект перед вызовом. После этого объект передается в управляемую функцию. Тем не менее при возврате из вызова объект не применяется к исходному варианту. Изменения, внесенные в управляемый объект, утрачиваются.

    Внимание

    Изменить значение варианта, переданного по значению, нельзя, даже если для варианта установлен флаг VT_BYREF.

  • Для варианта, передаваемого в управляемый код по ссылке, также может быть установлен флаг VT_BYREF, который указывает, что вариант содержит еще одну ссылку. Если это так, вариант маршалируется в объект ссылок , так как вариант передается по ссылке. Маршаллизатор автоматически разыменовывает содержимое варианта и копирует его в только что созданный объект перед вызовом. При возврате из вызова значение объекта возвращается по ссылке с исходным вариантом только в том случае, если тип объекта совпадает с типом переданного объекта. Таким образом, при передаче не изменяется тип варианта с установленным флагом VT_BYREF. Если во время вызова тип объекта изменяется, при возврате из него возникает исключение InvalidCastException.

В следующей таблице описываются общие правила распространения для вариантов и объектов.

С дт. По Возвращаемые изменения
Вариантv Объектo Никогда
Объектo Вариантv Никогда
Вариант*pv Ссылочный объектo Всегда
Ссылочный объектo Вариант*pv Всегда
Variantv(VT_BYREF VT_|*) Объектo Никогда
Variantv(VT_BYREF VT_|) Ссылочный объектo Только если тип не был изменен.

См. также