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


Маршалинг по умолчанию для типов значений

Большинство типов значений, такие как целые числа и числа с плавающей запятой, являются преобразуемыми и не требуют маршалинга. Другие, непреобразуемые, типы представлены в управляемой и неуправляемой памяти по-разному и действительно требуют маршалинг. Кроме того, другие типы требуют явного форматирования при пересечении границы взаимодействия.

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

  • Типы значений, используемые в вызове неуправляемого кода

  • Типы значений, используемые при COM-взаимодействии

Кроме описания форматированных типов, данный раздел определяет системные типы значений с необычным поведением при маршалинге.

Форматированный тип — это сложный тип, содержащий сведения, в явном виде управляющие размещением его членов в памяти. Сведения о размещении членов предоставляются с помощью атрибута StructLayoutAttribute. Размещение может определяться одним из следующих значений перечисления LayoutKind:

  • LayoutKind.Automatic

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

  • LayoutKind.Sequential

    Показывает, что члены этого типа должны размещаться в неуправляемой памяти в том же порядке, в котором они появляются в определении управляемого типа.

  • LayoutKind.Explicit

    Показывает, что члены размещаются в соответствии с атрибутом FieldOffsetAttribute, предоставляемым с каждым полем.

Типы значений, используемые в вызове неуправляемого кода

В следующем примере типы Point и Rect предоставляют сведения о размещении членов с помощью StructLayoutAttribute.

Imports System.Runtime.InteropServices
<StructLayout(LayoutKind.Sequential)> Public Structure Point
   Public x As Integer
   Public y As Integer
End Structure
<StructLayout(LayoutKind.Explicit)> Public Structure Rect
   <FieldOffset(0)> Public left As Integer
   <FieldOffset(4)> Public top As Integer
   <FieldOffset(8)> Public right As Integer
   <FieldOffset(12)> Public bottom As Integer
End Structure
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential)]
public struct Point {
   public int x;
   public int y;
}   

[StructLayout(LayoutKind.Explicit)]
public struct Rect {
   [FieldOffset(0)] public int left;
   [FieldOffset(4)] public int top;
   [FieldOffset(8)] public int right;
   [FieldOffset(12)] public int bottom;
}

При маршалинге в неуправляемый код эти форматированные типы маршалируются как структуры стиля С. Это обеспечивает простой способ вызова неуправляемого интерфейса API с аргументами в виде структур. Например, структуры POINT и RECT могут быть переданы в функцию PtInRect Microsoft Win32 API следующим образом:

BOOL PtInRect(const RECT *lprc, POINT pt);

Можно передать структуры с помощью следующего определения вызова неуправляемого кода.

Class Win32API    
   Declare Auto Function PtInRect Lib "User32.dll" _
    (ByRef r As Rect, p As Point) As Boolean
End Class
class Win32API {
   [DllImport("User32.dll")]
   public static extern Bool PtInRect(ref Rect r, Point p);
}

Тип значения Rect необходимо передавать по ссылке, потому что неуправляемый интерфейс API ожидает, что в функцию будет передан указатель на RECT. Тип значения Point передается по значению, потому что неуправляемый интерфейс API ожидает, что POINT будет передан в стек. Это небольшое различие очень важно. Ссылки передаются в неуправляемый код как указатели. Значения передаются в неуправляемый код в стек.

ПримечаниеПримечание

При маршалинге форматированного типа как структуры доступны только поля внутри этого типа.Если у такого типа есть методы, свойства или события, они недоступны из неуправляемого кода.

Классы тоже можно маршалировать в неуправляемый код как структуры стиля языка С при условии, что они имеют фиксированное размещение членов. Сведения о размещении членов класса также предоставляются с помощью атрибута StructLayoutAttribute. Основное различие между типами значений с фиксированным размещением и классами с фиксированным размещением заключает в способе маршалинга в неуправляемый код. Типы значений передаются по значению (в стек), и, следовательно, любые изменения, сделанные в членах этого типа вызываемым объектом не видны вызывающему объекту. Ссылочные типы передаются по ссылке (ссылка на тип передается в стек). Следовательно, все изменения, выполненные в членах преобразуемого типа вызываемым объектом, видны вызывающему объекту.

ПримечаниеПримечание

Если ссылочный тип содержит члены непреобразуемого типа, преобразование должно выполняться дважды: первый раз — когда аргумент передается на неуправляемую сторону, и второй раз — при возврате из вызова.В связи с появлением дополнительных издержек,параметры In/Out необходимо применять к аргументу в явном виде, если вызывающий объект должен учитывать изменения, выполненные вызываемым объектом.

В следующем примере класс SystemTime использует последовательное размещение членов в памяти и может быть передан в функцию GetSystemTime интерфейса Win32 API.

<StructLayout(LayoutKind.Sequential)> Public Class SystemTime
   Public wYear As System.UInt16
   Public wMonth As System.UInt16
   Public wDayOfWeek As System.UInt16
   Public wDay As System.UInt16
   Public wHour As System.UInt16
   Public wMinute As System.UInt16
   Public wSecond As System.UInt16
   Public wMilliseconds As System.UInt16
End Class
[StructLayout(LayoutKind.Sequential)]
   public class SystemTime {
   public ushort wYear; 
   public ushort wMonth;
   public ushort wDayOfWeek; 
   public ushort wDay; 
   public ushort wHour; 
   public ushort wMinute; 
   public ushort wSecond; 
   public ushort wMilliseconds; 
}

Функция GetSystemTime определяется следующим образом:

void GetSystemTime(SYSTEMTIME* SystemTime);

Эквивалентное определение вызова неуправляемого кода для GetSystemTime имеет следующий вид:

Public Class Win32
   Declare Auto Sub GetSystemTime Lib "Kernel32.dll" (ByVal sysTime _
   As SystemTime)
End Class
class Win32API {
   [DllImport("Kernel32.dll", CharSet=CharSet.Auto)]
   public static extern void GetSystemTime(SystemTime st);
}

Обратите внимание, что аргумент SystemTime типизирован не как ссылочный аргумент, так как SystemTime является классом, а не типом значения. В отличие от типов значений классы всегда передаются по ссылке.

В следующем примере кода показан другой класс Point, содержащий метод с именем SetXY. Поскольку этот тип имеет последовательное размещение, он может быть передан в неуправляемый код и маршалирован как структура. Однако член SetXY не может быть вызван из неуправляемого кода, даже когда объект передается по ссылке.

<StructLayout(LayoutKind.Sequential)> Public Class Point
   Private x, y As Integer
   Public Sub SetXY(x As Integer, y As Integer)
      Me.x = x
      Me.y = y
   End Sub
End Class
[StructLayout(LayoutKind.Sequential)]
public class Point {
   int x, y;
   public void SetXY(int x, int y){ 
      this.x = x;
      this.y = y;
   }
}

Типы значений, используемые при COM-взаимодействии

Форматированные типы могут также быть переданы в вызовы метода COM-взаимодействия. Фактически при экспорте в библиотеку типов типы значений автоматически преобразуются в структуры. Как показано в следующем примере, тип значения Point становится определением типа (typedef) с именем Point. Все ссылки на тип значения Point заменяются по всей библиотеке типов на определение типа Point.

Представление библиотеки типов

typedef struct tagPoint {
   int x;
   int y;
} Point;
interface _Graphics {
   …
   HRESULT SetPoint ([in] Point p)
   HRESULT SetPointRef ([in,out] Point *p)
   HRESULT GetPoint ([out,retval] Point *p)
}

Те же самые правила, которые использовались для маршалинга значений и ссылок при платформозависимых вызовах, используются и при маршалинге через интерфейсы COM. Например, когда экземпляр типа значения Point передается из .NET Framework в COM, Point передается по значению. Если тип значения Point передается по ссылке, указатель на Point передается в стек. Маршалер взаимодействия не поддерживает более высокие уровни косвенных ссылок (Point **) в любом направлении.

ПримечаниеПримечание

Структуры, для которых значение перечисления LayoutKind установлено равным Explicit не могут быть использованы в COM-взаимодействии, так как экспортированная библиотека типов не может предоставлять явное размещение.

Системные типы значений

Пространство имен System содержит ряд типов значений, представляющих собой упакованную форму простых типов среды выполнения. Например, структура типов значения System.Int32 представляет собой упакованную форму ELEMENT_TYPE_I4. Вместо маршалинга этих типов как структур, что делается в случае других форматированных типов, следует выполнять их маршалинг так же, как для соответствующих простых типов. Следовательно, System.Int32 маршалируется как ELEMENT_TYPE_I4, а не как структура, содержащая один член типа long. В следующей таблице приведен список типов значений в пространстве имен System, являющихся упакованными представлениями простых типов.

Системные типы значений

Тип элемента

System.Boolean

ELEMENT_TYPE_BOOLEAN

System.SByte

ELEMENT_TYPE_I1

System.Byte

ELEMENT_TYPE_UI1

System.Char

ELEMENT_TYPE_CHAR

System.Int16

ELEMENT_TYPE_I2

System.UInt16

ELEMENT_TYPE_U2

System.Int32

ELEMENT_TYPE_I4

System.UInt32

ELEMENT_TYPE_U4

System.Int64

ELEMENT_TYPE_I8

System.UInt64

ELEMENT_TYPE_U8

System.Single

ELEMENT_TYPE_R4

System.Double

ELEMENT_TYPE_R8

System.String

ELEMENT_TYPE_STRING

System.IntPtr

ELEMENT_TYPE_I

System.UIntPtr

ELEMENT_TYPE_U

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

Системные типы значений

Тип IDL

System.DateTime

DATE

System.Decimal

DECIMAL

System.Guid

GUID

System.Drawing.Color

OLE_COLOR

В следующем коде показано определение неуправляемых типов DATE, GUID, DECIMAL и OLE_COLOR в библиотеке типов Stdole2.

Представление библиотеки типов

typedef double DATE;
typedef DWORD OLE_COLOR;

typedef struct tagDEC {
    USHORT    wReserved;
    BYTE      scale;
    BYTE      sign;
    ULONG     Hi32;
    ULONGLONG Lo64;
} DECIMAL;

typedef struct tagGUID {
    DWORD Data1;
    WORD  Data2;
    WORD  Data3;
    BYTE  Data4[ 8 ];
} GUID;

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

Public Interface IValueTypes
   Sub M1(d As System.DateTime)
   Sub M2(d As System.Guid)
   Sub M3(d As System.Decimal)
   Sub M4(d As System.Drawing.Color)
End Interface
public interface IValueTypes {
   void M1(System.DateTime d);
   void M2(System.Guid d);
   void M3(System.Decimal d);
   void M4(System.Drawing.Color d);
}

Представление библиотеки типов

[…]
interface IValueTypes : IDispatch {
   HRESULT M1([in] DATE d);
   HRESULT M2([in] GUID d);
   HRESULT M3([in] DECIMAL d);
   HRESULT M4([in] OLE_COLOR d);
};

См. также

Основные понятия

Преобразуемые и непреобразуемые типы

Атрибуты направления

Копирование и закрепление

Другие ресурсы

Поведение маршалинга по умолчанию