Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Маршалирование взаимодействия работает с правилами, которые определяют поведение данных, связанных с параметрами метода, по мере того как они передаются между управляемой и неуправляемой памятью. Эти встроенные правила управляют такими действиями маршаллинга, как преобразования типа данных, независимо от того, может ли вызывающий объект изменять данные и возвращать эти изменения вызывающему объекту, и при каких обстоятельствах маршаллизатор обеспечивает оптимизацию производительности.
В этом разделе определяются характеристики поведения по умолчанию службы маршалинга взаимодействия. В нём представлены подробные сведения о массивах, маршалировании, типах Boolean, символьных типах, делегатах, классах, объектах, строках и структурах.
Замечание
Маршаллирование универсальных типов не поддерживается. Дополнительные сведения см. в разделе "Взаимодействие с использованием универсальных типов".
Управление памятью с маршализатором взаимодействия
Маршализатор взаимодействия всегда пытается освободить память, выделенную неуправляемым кодом. Это поведение соответствует правилам управления памятью COM, но отличается от правил, регулирующих собственный C++.
Путаница может возникнуть, если вы ожидаете типичное поведение C++ (без освобождения памяти), при использовании механизма Platform Invoke, который автоматически освобождает память для указателей. Например, вызов следующего неуправляемого метода из библиотеки DLL C++ не освобождает память автоматически.
Неуправляемая сигнатура
BSTR MethodOne (BSTR b) {
return b;
}
Однако если вы определите метод как прототип платформенного вызова, замените каждый тип BSTR на тип String и вызовите MethodOne
, то общая языкосвязанная среда выполнения (CLR) попытается освободить b
дважды. Поведение маршаллинга можно изменить, используя IntPtr типы вместо типов String.
Среда выполнения всегда использует метод CoTaskMemFree в Windows и бесплатный метод на других платформах для освобождения памяти. Если память, которую вы используете, не была выделена с использованием метода CoTaskMemAlloc в Windows или метода malloc на других платформах, необходимо использовать IntPtr и освободить ее вручную с помощью соответствующего метода. Аналогичным образом можно избежать автоматического освобождения памяти в ситуациях, когда память никогда не должна быть освобождена, например при использовании функции GetCommandLine из Kernel32.dll, которая возвращает указатель на память ядра. Дополнительные сведения об освобождении памяти вручную см. в примере буферов.
Маршалирование по умолчанию для классов
Классы можно передавать только через COM-взаимодействие и всегда передаются как интерфейсы. В некоторых случаях интерфейс, который используется для преобразования класса, называется интерфейсом класса. Сведения о переопределении интерфейса класса с помощью выбранного интерфейса см. в разделе "Введение в интерфейс класса".
Передача классов в COM
Когда управляемый класс передается в COM, маршализатор взаимодействия автоматически упаковывает класс с помощью COM-прокси и передает интерфейс класса, созданный прокси-сервером, вызову метода COM. Затем прокси-сервер делегирует все вызовы интерфейса класса обратно в управляемый объект. Прокси-сервер также предоставляет другие интерфейсы, которые не реализуются явным образом классом. Прокси-сервер автоматически реализует интерфейсы, такие как IUnknown и IDispatch от имени класса.
Передача классов в код .NET
Coclasses обычно не используются в качестве аргументов метода в COM. Вместо этого интерфейс по умолчанию обычно передается вместо coclass.
Когда интерфейс передается в управляемый код, маршаллизатор отвечает за обертывание интерфейса соответствующей оболочкой и передачу этой оболочки в управляемый метод. Выбор оболочки может быть сложной задачей. Каждый экземпляр COM-объекта имеет одну уникальную оболочку, вне зависимости от того, сколько интерфейсов реализует объект. Например, один COM-объект, реализующий пять отдельных интерфейсов, имеет только одну оболочку. Одна и та же оболочка обеспечивает доступ ко всем пяти интерфейсам. Если создаются два экземпляра COM-объекта, создаются два экземпляра оболочки.
Чтобы оболочка поддерживала тот же тип в течение всего времени существования, маршализатор interop должен определить правильную оболочку при первом прохождении интерфейса, предоставляемого объектом, через маршализатор. Маршаллизатор определяет объект, просматривая один из интерфейсов, который реализует объект.
Например, маршаллизатор определяет, что оболочка класса должна использоваться для упаковки интерфейса, переданного в управляемый код. Когда интерфейс впервые передается через маршрутизатор, он проверяет, поступает ли интерфейс от известного объекта. Эта проверка выполняется в двух ситуациях:
Интерфейс реализуется другим управляемым объектом, переданным в COM в другом месте. Маршализатор может легко идентифицировать интерфейсы, предоставляемые управляемыми объектами, и сопоставлять интерфейс с управляемым объектом, который обеспечивает её реализацию. Затем управляемый объект передается методу и не требуется оболочка.
Объект, который уже был упакован, реализует интерфейс. Чтобы определить, является ли это так, маршаллировщик запрашивает у объекта его интерфейс IUnknown и сравнивает возвращенный интерфейс с интерфейсами других объектов, которые уже обернуты. Если интерфейс совпадает с другим интерфейсом, объекты имеют ту же идентичность, а существующая оболочка передаётся в метод.
Если интерфейс не является из известного источника, маршаллер выполняет следующее:
Маршаллизатор запрашивает объект для интерфейса IProvideClassInfo2 . Если предоставлено, маршализатор использует CLSID, возвращенный из IProvideClassInfo2.GetGUID для идентификации кокласса, предоставляющего интерфейс. С помощью CLSID маршализатор может найти обертку в реестре, если сборка была зарегистрирована ранее.
Маршаллизатор запрашивает интерфейс для интерфейса IProvideClassInfo . Если предоставлено, маршаллизатор использует ITypeInfo, возвращенный из IProvideClassInfo.GetClassInfo, для определения CLSID класса, предоставляющего интерфейс. Маршаллер может использовать CLSID для поиска метаданных для оболочки.
Если маршаллер по-прежнему не может идентифицировать класс, он оборачивает интерфейс универсальным классом оболочки с именем System.__ComObject.
Маршалирование по умолчанию для делегатов
Управляемый делегат управляется как COM-интерфейс или в виде указателя на функцию в зависимости от механизма вызова.
Для вызова платформы делегат преобразуется в неуправляемый указатель на функцию по умолчанию.
Для взаимодействия COM делегат маршалируется как COM-интерфейс типа _Delegate по умолчанию. Интерфейс _Delegate определен в библиотеке типов Mscorlib.tlb и содержит Delegate.DynamicInvoke метод, который позволяет вызывать метод, на который ссылается делегат.
В следующей таблице показаны параметры маршалинга для типа данных управляемого делегата. Атрибут MarshalAsAttribute предоставляет различные значения перечисления UnmanagedType для маршалирования делегатов.
Тип перечисления | Описание неуправляемого формата |
---|---|
UnmanagedType.FunctionPtr | Указатель неуправляемой функции. |
UnmanagedType.Interface (неуправляемый тип: Интерфейс) | Интерфейс типа _Delegate, как определено в mscorlib.tlb. |
Рассмотрим следующий пример кода, в котором методы DelegateTestInterface
экспортируются в библиотеку типов COM. Обратите внимание, что только делегаты, помеченные ключевым словом ref (или ByRef), передаются как входные/выходные параметры.
using System;
using System.Runtime.InteropServices;
public interface DelegateTest {
void m1(Delegate d);
void m2([MarshalAs(UnmanagedType.Interface)] Delegate d);
void m3([MarshalAs(UnmanagedType.Interface)] ref Delegate d);
void m4([MarshalAs(UnmanagedType.FunctionPtr)] Delegate d);
void m5([MarshalAs(UnmanagedType.FunctionPtr)] ref Delegate d);
}
Представление библиотеки типов
importlib("mscorlib.tlb");
interface DelegateTest : IDispatch {
[id(…)] HRESULT m1([in] _Delegate* d);
[id(…)] HRESULT m2([in] _Delegate* d);
[id(…)] HRESULT m3([in, out] _Delegate** d);
[id()] HRESULT m4([in] int d);
[id()] HRESULT m5([in, out] int *d);
};
Указатель функции можно разыменовыть так же, как и любой другой неуправляемый указатель функции.
В этом примере, когда два делегата маршалируются как UnmanagedType.FunctionPtr, результатом являются int
и указатель на int
. Поскольку типы делегатов маршалируются, int
здесь представляет указатель на пустоту (void*
), которая является адресом делегата в памяти. Другими словами, этот результат относится к 32-разрядным системам Windows, так как int
здесь представляет размер указателя функции.
Замечание
Ссылка на указатель функции на управляемый делегат, который удерживается неуправляемым кодом, не мешает среде выполнения общего языка (CLR) выполнять сборку мусора для управляемого объекта.
Например, следующий код является неверным, так как ссылка на cb
объект, переданная SetChangeHandler
методу, не поддерживает cb
в активном состоянии за пределами времени выполнения метода Test
. После того как объект cb
будет собран сборщиком мусора, переданный в SetChangeHandler
указатель функции больше не является допустимым.
public class ExternalAPI {
[DllImport("External.dll")]
public static extern void SetChangeHandler(
[MarshalAs(UnmanagedType.FunctionPtr)]ChangeDelegate d);
}
public delegate bool ChangeDelegate([MarshalAs(UnmanagedType.LPWStr) string S);
public class CallBackClass {
public bool OnChange(string S){ return true;}
}
internal class DelegateTest {
public static void Test() {
CallBackClass cb = new CallBackClass();
// Caution: The following reference on the cb object does not keep the
// object from being garbage collected after the Main method
// executes.
ExternalAPI.SetChangeHandler(new ChangeDelegate(cb.OnChange));
}
}
Чтобы компенсировать непредвиденную сборку мусора, вызывающий код должен убедиться, что cb
объект остается доступным до тех пор, пока используется неуправляемый указатель функции. При необходимости, можно настроить неуправляемый код так, чтобы уведомлять управляемый код о том, когда указатель функции больше не требуется, как показано в следующем примере.
internal class DelegateTest {
CallBackClass cb;
// Called before ever using the callback function.
public static void SetChangeHandler() {
cb = new CallBackClass();
ExternalAPI.SetChangeHandler(new ChangeDelegate(cb.OnChange));
}
// Called after using the callback function for the last time.
public static void RemoveChangeHandler() {
// The cb object can be collected now. The unmanaged code is
// finished with the callback function.
cb = null;
}
}
Маршаллинг по умолчанию для типов значений
Большинство типов значений, таких как целые числа и числа с плавающей запятой, являются блитабельными и не требуют маршаллинга. Другие неблиттабельные типы имеют разнородные представления в управляемой и неуправляемой памяти и требуют маршаллинга. Тем не менее другие типы требуют явного форматирования по границе взаимодействия.
В этом разделе содержатся сведения о следующих типах форматированных значений:
Помимо описания форматированных типов, в этом разделе определяются системные типы значений, обладающие необычным поведением маршалинга.
Форматированный тип — это сложный тип, содержащий информацию, которая явно контролирует расположение его членов в памяти. Сведения о макете элемента предоставляются с помощью атрибута StructLayoutAttribute . Макет может быть одним из следующих LayoutKind значений перечисления:
LayoutKind.Auto
Указывает, что среда 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;
}
При передаче в неуправляемый код эти форматированные типы маршаллизируются как структуры стиля C. Это обеспечивает простой способ вызова неуправляемого API с аргументами структуры. Например, структуры POINT
и RECT
можно передать в функцию Microsoft Windows API PtInRect следующим образом:
BOOL PtInRect(const RECT *lprc, POINT pt);
Вы можете передавать структуры, используя следующее определение вызова платформы:
Friend Class NativeMethods
Friend Declare Auto Function PtInRect Lib "User32.dll" (
ByRef r As Rect, p As Point) As Boolean
End Class
internal static class NativeMethods
{
[DllImport("User32.dll")]
internal static extern bool PtInRect(ref Rect r, Point p);
}
Тип Rect
значения должен передаваться по ссылке, так как неуправляемый API ожидает, что указатель на RECT
будет передан функции.
Point
Тип значения передается по значению, так как неуправляемый API ожидает, что POINT
будет передан в стеке. Это тонкое различие очень важно. Ссылки передаются в неуправляемый код в виде указателей. Значения передаются в неуправляемый код в стеке.
Замечание
Если форматированный тип маршалируется как структура, доступны только поля внутри типа. Если тип содержит методы, свойства или события, они недоступны из неуправляемого кода.
Классы также можно маршалировать в неуправляемый код в виде структур стиля C, если они имеют фиксированное расположение членов. Сведения о макете элемента для класса также предоставляются атрибутом StructLayoutAttribute . Основное различие между типами значений с фиксированным макетом и классами с фиксированным макетом заключается в том, как они маршалируются в неуправляемый код. Типы значений передаются по значению (в стеке) и, следовательно, любые изменения, внесенные в члены типа вызываемого объекта, не отображаются вызывающим элементом. Ссылочные типы передаются по ссылке (ссылка на тип передается в стеке); следовательно, все изменения, внесенные в элементы типа blittable типа вызываемого объекта, отображаются вызывающим элементом.
Замечание
Если у ссылочного типа есть члены неуправляемых типов, преобразование требуется дважды: в первый раз при передаче аргумента на неуправляемую сторону и второй раз при возврате из вызова. Из-за этой добавленной нагрузки параметры In/Out должны быть явно применены к аргументу, если вызывающий объект хочет увидеть изменения, внесенные вызываемым объектом.
В следующем примере класс SystemTime
имеет последовательное расположение членов и может передаваться в функцию Windows API GetSystemTime.
<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 выглядит следующим образом:
Friend Class NativeMethods
Friend Declare Auto Sub GetSystemTime Lib "Kernel32.dll" (
ByVal sysTime As SystemTime)
End Class
internal static class NativeMethods
{
[DllImport("Kernel32.dll", CharSet = CharSet.Auto)]
internal 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
передается через стек. Маршализатор взаимодействия не поддерживает более высокие уровни индирекции (Точка **) в обоих направлениях.
Замечание
Структуры, в которых 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 |
Некоторые другие типы значений в пространстве имен системы обрабатываются иначе. Поскольку в неуправляемом коде уже имеются прочно установленные форматы для этих типов, механизм маршаллинга имеет специальные правила для их обработки. В следующей таблице перечислены специальные типы значений в пространстве имен системы , а также неуправляемый тип, в который они маршаллируются.
Тип системного значения | Тип IDL |
---|---|
System.DateTime | ДАТА |
System.Decimal | ДЕСЯТИЧНАЯ СИСТЕМА |
System.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);
};