Маршалинг по умолчанию для объектов
Параметры и поля, типизированные как 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.To Type, где To Type — функция преобразования, соответствующая типу, возвращенному 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 может быть запутана. На следующем рисунке показаны различия между этими способами:
Варианты, передаваемые по значению и по ссылке
Поведение по умолчанию для маршалинга объектов и вариантов по значению
При передаче объектов из управляемого кода в 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.
В следующей таблице описываются общие правила распространения для вариантов и объектов.
С дт. | По | Возвращаемые изменения |
---|---|---|
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 | Только если тип не был изменен. |