Поделиться через


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

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

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

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

Только COM-взаимодействие поддерживает маршаллирование для типов объектов. По умолчанию выполняется маршалинг объектов в варианты COM. Эти правила применяются только к типу Object и не применяются к строго типизированному объекту, который является производным от класса 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, где Toтип — это подпрограмма преобразования, соответствующая типу, возвращенному из 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 Если Object затем передается обратно в COM, он маршалируется обратно в вариант типа VT_UNKNOWN. Нет никаких гарантий, что вариант, созданный при маршалинге объекта из управляемого кода в COM, будет таким же типом, как и вариант, изначально используемый для создания объекта.

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

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

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

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

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

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

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

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

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

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

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

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

    Внимание

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

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

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

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

См. также