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


Передача структур

Многие неуправляемые функции ожидают, что в качестве параметров будут переданы члены структур (определяемые пользователем типы в Visual Basic) или члены классов, которые определены в управляемом коде. При передаче структур или классов в неуправляемый код с использованием платформы вызова (platform invoke), необходимо предоставить дополнительную информацию для обеспечения сохранения исходного расположения и выравнивания. В этом разделе описывается атрибут StructLayoutAttribute, который используется для определения форматированных типов. Для управляемых структур и классов можно выбрать одно из нескольких предсказуемых поведений размещения, предоставляемых перечислением LayoutKind.

Представленные в этом разделе понятия приводятся с учетом важного различия между структурами и типами классов. Структуры представляют собой типы значений, а классы — ссылочные типы. В классах всегда реализуется как минимум один уровень косвенного обращения к памяти (указатель на значение). Это отличие важно, поскольку неуправляемые функции часто используют непрямое обращение, как видно из сигнатур в первом столбце следующей таблицы. Управляемые структуры и объявления классов в оставшихся столбцах показывают степень, до которой можно настроить уровень косвенности в объявлении. Объявления предоставляются как для Visual Basic, так и для Visual C#.

Неуправляемая сигнатура Управляемое объявление:
без косвенности
Structure MyType
struct MyType;
Управляемая декларация:
один уровень индирекции
Class MyType
class MyType;
DoWork(MyType x);

Требуется ноль уровней косвенного обращения.
DoWork(ByVal x As MyType)
DoWork(MyType x)

Добавляет ноль уровней опосредованности.
Невозможно, поскольку один уровень косвенного обращения уже существует.
DoWork(MyType* x);

Требуется один уровень косвенного обращения.
DoWork(ByRef x As MyType)
DoWork(ref MyType x)

Добавляет один уровень косвенности.
DoWork(ByVal x As MyType)
DoWork(MyType x)

Добавляет нулевой уровень косвенного обращения.
DoWork(MyType** x);

Требуются два уровня косвенного обращения.
Невозможно, поскольку нельзя использовать ByRefByRef или refref. DoWork(ByRef x As MyType)
DoWork(ref MyType x)

Добавляет один уровень косвенности.

Таблица описывает следующие рекомендации по объявлениям вызовов платформы:

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

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

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

Объявление и передача структур

В следующем примере демонстрируется, как определить структуры Point и Rect в управляемом коде и передать типы в качестве параметров в функцию PtInRect в файле User32.dll. Функция PtInRect имеет следующую неуправляемую сигнатуру:

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

Обратите внимание, что структуру Rect необходимо передавать по ссылке, поскольку функция принимает указатель на тип RECT.

Imports System.Runtime.InteropServices  
  
<StructLayout(LayoutKind.Sequential)> Public Structure Point  
    Public x As Integer  
    Public y As Integer  
End Structure  
  
Public Structure <StructLayout(LayoutKind.Explicit)> 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  
  
Friend Class NativeMethods
    Friend Declare Auto Function PtInRect Lib "user32.dll" (
        ByRef r As Rect, p As Point) As Boolean  
End Class  
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;  
}
  
internal static class NativeMethods
{  
    [DllImport("User32.dll")]  
    internal static extern bool PtInRect(ref Rect r, Point p);  
}  

Объявление и передача классов

Члены класса можно передавать в неуправляемую функцию DLL, если класс имеет фиксированное расположение членов. В следующем примере демонстрируется, как передать члены класса MySystemTime, которые определяются последовательно, в функцию GetSystemTime в файле User32.dll. Функция GetSystemTime имеет следующую неуправляемую сигнатуру:

void GetSystemTime(SYSTEMTIME* SystemTime);  

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

Imports System.Runtime.InteropServices  
  
<StructLayout(LayoutKind.Sequential)> Public Class MySystemTime  
    Public wYear As Short  
    Public wMonth As Short  
    Public wDayOfWeek As Short
    Public wDay As Short  
    Public wHour As Short  
    Public wMinute As Short  
    Public wSecond As Short  
    Public wMiliseconds As Short  
End Class  
  
Friend Class NativeMethods  
    Friend Declare Auto Sub GetSystemTime Lib "Kernel32.dll" (
        sysTime As MySystemTime)  
    Friend Declare Auto Function MessageBox Lib "User32.dll" (
        hWnd As IntPtr, lpText As String, lpCaption As String, uType As UInteger) As Integer  
End Class  
  
Public Class TestPlatformInvoke
    Public Shared Sub Main()  
        Dim sysTime As New MySystemTime()  
        NativeMethods.GetSystemTime(sysTime)  
  
        Dim dt As String  
        dt = "System time is:" & ControlChars.CrLf & _  
              "Year: " & sysTime.wYear & _  
              ControlChars.CrLf & "Month: " & sysTime.wMonth & _  
              ControlChars.CrLf & "DayOfWeek: " & sysTime.wDayOfWeek & _  
              ControlChars.CrLf & "Day: " & sysTime.wDay  
        NativeMethods.MessageBox(IntPtr.Zero, dt, "Platform Invoke Sample", 0)
    End Sub  
End Class  
[StructLayout(LayoutKind.Sequential)]  
public class MySystemTime {  
    public ushort wYear;
    public ushort wMonth;  
    public ushort wDayOfWeek;
    public ushort wDay;
    public ushort wHour;
    public ushort wMinute;
    public ushort wSecond;
    public ushort wMilliseconds;
}  
internal static class NativeMethods
{  
    [DllImport("Kernel32.dll")]  
    internal static extern void GetSystemTime(MySystemTime st);  
  
    [DllImport("user32.dll", CharSet = CharSet.Auto)]  
    internal static extern int MessageBox(
        IntPtr hWnd, string lpText, string lpCaption, uint uType);  
}  
  
public class TestPlatformInvoke  
{  
    public static void Main()  
    {  
        MySystemTime sysTime = new MySystemTime();  
        NativeMethods.GetSystemTime(sysTime);  
  
        string dt;  
        dt = "System time is: \n" +  
              "Year: " + sysTime.wYear + "\n" +  
              "Month: " + sysTime.wMonth + "\n" +  
              "DayOfWeek: " + sysTime.wDayOfWeek + "\n" +  
              "Day: " + sysTime.wDay;  
        NativeMethods.MessageBox(IntPtr.Zero, dt, "Platform Invoke Sample", 0);  
    }  
}  

См. также