Klasy, struktury i związki marshallingowe
Klasy i struktury są podobne w programie .NET Framework. Oba mogą mieć pola, właściwości i zdarzenia. Mogą również mieć metody statyczne i niestatyczne. Jedną z godnych uwagi różnicą jest to, że struktury są typami wartości i klasami referencyjnymi.
W poniższej tabeli wymieniono opcje marshalingu dla klas, struktur i związków; opisuje ich użycie; i zawiera link do odpowiedniej platformy wywołać przykład.
Type | Opis | Przykład |
---|---|---|
Klasa według wartości. | Przekazuje klasę z elementami członkowskimi całkowitymi jako parametr in/out, taki jak przypadek zarządzany. | Przykład SysTime |
Struktura według wartości. | Przekazuje struktury jako W parametrach. | Próbka struktur |
Struktura według odwołania. | Przekazuje struktury jako parametry wejścia/wyjścia. | Przykład OSInfo |
Struktura z zagnieżdżonych struktur (spłaszczone). | Przekazuje klasę reprezentującą strukturę ze strukturami zagnieżdżonym w funkcji niezarządzanej. Struktura jest spłaszczona do jednej dużej struktury w zarządzanym prototypie. | Przykład FindFile |
Struktura ze wskaźnikiem do innej struktury. | Przekazuje strukturę zawierającą wskaźnik do drugiej struktury jako elementu członkowskiego. | Przykład struktury |
Tablica struktur z liczbami całkowitymi według wartości. | Przekazuje tablicę struktur, które zawierają tylko liczby całkowite jako parametr in/out. Elementy członkowskie tablicy można zmienić. | Przykład tablic |
Tablica struktur z liczbami całkowitymi i ciągami według odwołania. | Przekazuje tablicę struktur, które zawierają liczby całkowite i ciągi jako parametr Out. Wywołana funkcja przydziela pamięć dla tablicy. | Przykład OutArrayOfStructs |
Związki z typami wartości. | Przekazuje związki z typami wartości (liczba całkowita i podwójna). | Przykład unii |
Związki z typami mieszanymi. | Przekazuje związki z mieszanymi typami (liczba całkowita i ciąg). | Przykład unii |
Struktura z układem specyficznym dla platformy. | Przekazuje typ z definicjami pakowania natywnego. | Przykład platformy |
Wartości null w strukturze. | Przekazuje odwołanie o wartości null (Nic w Visual Basic) zamiast odwołania do typu wartości. | Przykład HandleRef |
Próbka struktur
W tym przykładzie pokazano, jak przekazać strukturę wskazującą drugą strukturę, przekazać strukturę z osadzoną strukturą i przekazać strukturę z tablicą osadzoną.
W przykładzie Struktury używane są następujące funkcje niezarządzane pokazane przy użyciu oryginalnej deklaracji funkcji:
TestStructInStruct wyeksportowany z PinvokeLib.dll.
int TestStructInStruct(MYPERSON2* pPerson2);
TestStructInStruct3 wyeksportowany z PinvokeLib.dll.
void TestStructInStruct3(MYPERSON3 person3);
TestArrayInStruct wyeksportowany z PinvokeLib.dll.
void TestArrayInStruct(MYARRAYSTRUCT* pStruct);
PinvokeLib.dll to niestandardowa niezarządzana biblioteka zawierająca implementacje poprzednio wymienionych funkcji i cztery struktury: MYPERSON, MYPERSON2, MYPERSON3 i MYARRAYSTRUCT. Te struktury zawierają następujące elementy:
typedef struct _MYPERSON
{
char* first;
char* last;
} MYPERSON, *LP_MYPERSON;
typedef struct _MYPERSON2
{
MYPERSON* person;
int age;
} MYPERSON2, *LP_MYPERSON2;
typedef struct _MYPERSON3
{
MYPERSON person;
int age;
} MYPERSON3;
typedef struct _MYARRAYSTRUCT
{
bool flag;
int vals[ 3 ];
} MYARRAYSTRUCT;
Zarządzane MyPerson
struktury , MyPerson2
, MyPerson3
i MyArrayStruct
mają następującą charakterystykę:
MyPerson
zawiera tylko elementy członkowskie ciągu. Pole CharSet ustawia ciągi na format ANSI po przekazaniu do funkcji niezarządzanej.MyPerson2
zawiera element IntPtr doMyPerson
struktury. Typ IntPtr zastępuje oryginalny wskaźnik strukturą niezarządzaną, ponieważ aplikacje .NET Framework nie używają wskaźników, chyba że kod jest oznaczony jako niebezpieczny.MyPerson3
zawieraMyPerson
jako osadzoną strukturę. Struktura osadzona w innej strukturze może zostać spłaszczone przez umieszczenie elementów osadzonej struktury bezpośrednio w strukturze głównej lub pozostawienie jej jako osadzonej struktury, tak jak w tym przykładzie.MyArrayStruct
zawiera tablicę liczb całkowitych. Atrybut MarshalAsAttribute ustawia UnmanagedType wartość wyliczenia na ByValArray, która służy do wskazywania liczby elementów w tablicy.
W przypadku wszystkich struktur w tym przykładzie atrybut jest stosowany w celu upewnienia się, StructLayoutAttribute że składowe są rozmieszczone sekwencyjnie w pamięci, w kolejności, w której są wyświetlane.
Klasa NativeMethods
zawiera zarządzane prototypy dla TestStructInStruct
metod , TestStructInStruct3
i TestArrayInStruct
wywoływanych przez klasę App
. Każdy prototyp deklaruje pojedynczy parametr w następujący sposób:
TestStructInStruct
deklaruje odwołanie do typuMyPerson2
jako jego parametru.TestStructInStruct3
deklaruje typMyPerson3
jako parametr i przekazuje parametr według wartości.TestArrayInStruct
deklaruje odwołanie do typuMyArrayStruct
jako jego parametru.
Struktury jako argumenty metod są przekazywane przez wartość, chyba że parametr zawiera słowo kluczowe ref (ByRef w Visual Basic). Na przykład TestStructInStruct
metoda przekazuje odwołanie (wartość adresu) do obiektu typu MyPerson2
do niezarządzanego kodu. Aby manipulować strukturą wskazującąMyPerson2
, przykład tworzy bufor o określonym rozmiarze i zwraca jego adres, łącząc Marshal.AllocCoTaskMem metody i .Marshal.SizeOf Następnie przykład kopiuje zawartość zarządzanej struktury do buforu niezarządzanego. Na koniec przykład używa Marshal.PtrToStructure metody do marshalingu danych z buforu niezarządzanego do zarządzanego obiektu i Marshal.FreeCoTaskMem metody w celu zwolnienia niezarządzanego bloku pamięci.
Deklarowanie prototypów
// Declares a managed structure for each unmanaged structure.
[StructLayout(LayoutKind::Sequential, CharSet = CharSet::Ansi)]
public value struct MyPerson
{
public:
String^ first;
String^ last;
};
[StructLayout(LayoutKind::Sequential)]
public value struct MyPerson2
{
public:
IntPtr person;
int age;
};
[StructLayout(LayoutKind::Sequential)]
public value struct MyPerson3
{
public:
MyPerson person;
int age;
};
[StructLayout(LayoutKind::Sequential)]
public value struct MyArrayStruct
{
public:
bool flag;
[MarshalAs(UnmanagedType::ByValArray, SizeConst = 3)]
array<int>^ vals;
};
private ref class NativeMethods
{
public:
// Declares a managed prototype for unmanaged function.
[DllImport("..\\LIB\\PinvokeLib.dll")]
static int TestStructInStruct(MyPerson2% person2);
[DllImport("..\\LIB\\PinvokeLib.dll")]
static int TestStructInStruct3(MyPerson3 person3);
[DllImport("..\\LIB\\PinvokeLib.dll")]
static int TestArrayInStruct(MyArrayStruct% myStruct);
};
// Declares a managed structure for each unmanaged structure.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct MyPerson
{
public string first;
public string last;
}
[StructLayout(LayoutKind.Sequential)]
public struct MyPerson2
{
public IntPtr person;
public int age;
}
[StructLayout(LayoutKind.Sequential)]
public struct MyPerson3
{
public MyPerson person;
public int age;
}
[StructLayout(LayoutKind.Sequential)]
public struct MyArrayStruct
{
public bool flag;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public int[] vals;
}
internal static class NativeMethods
{
// Declares a managed prototype for unmanaged function.
[DllImport("..\\LIB\\PinvokeLib.dll", CallingConvention = CallingConvention.Cdecl)]
internal static extern int TestStructInStruct(ref MyPerson2 person2);
[DllImport("..\\LIB\\PinvokeLib.dll", CallingConvention = CallingConvention.Cdecl)]
internal static extern int TestStructInStruct3(MyPerson3 person3);
[DllImport("..\\LIB\\PinvokeLib.dll", CallingConvention = CallingConvention.Cdecl)]
internal static extern int TestArrayInStruct(ref MyArrayStruct myStruct);
}
' Declares a managed structure for each unmanaged structure.
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)>
Public Structure MyPerson
Public first As String
Public last As String
End Structure
<StructLayout(LayoutKind.Sequential)>
Public Structure MyPerson2
Public person As IntPtr
Public age As Integer
End Structure
<StructLayout(LayoutKind.Sequential)>
Public Structure MyPerson3
Public person As MyPerson
Public age As Integer
End Structure
<StructLayout(LayoutKind.Sequential)>
Public Structure MyArrayStruct
Public flag As Boolean
<MarshalAs(UnmanagedType.ByValArray, SizeConst:=3)>
Public vals As Integer()
End Structure
Friend Class NativeMethods
' Declares managed prototypes for unmanaged functions.
<DllImport("..\LIB\PinvokeLib.dll", CallingConvention:=CallingConvention.Cdecl)>
Friend Shared Function TestStructInStruct(
ByRef person2 As MyPerson2) As Integer
End Function
<DllImport("..\LIB\PinvokeLib.dll", CallingConvention:=CallingConvention.Cdecl)>
Friend Shared Function TestStructInStruct3(
ByVal person3 As MyPerson3) As Integer
End Function
<DllImport("..\LIB\PinvokeLib.dll", CallingConvention:=CallingConvention.Cdecl)>
Friend Shared Function TestArrayInStruct(
ByRef myStruct As MyArrayStruct) As Integer
End Function
End Class
Wywoływanie funkcji
public ref class App
{
public:
static void Main()
{
// Structure with a pointer to another structure.
MyPerson personName;
personName.first = "Mark";
personName.last = "Lee";
MyPerson2 personAll;
personAll.age = 30;
IntPtr buffer = Marshal::AllocCoTaskMem(Marshal::SizeOf(personName));
Marshal::StructureToPtr(personName, buffer, false);
personAll.person = buffer;
Console::WriteLine("\nPerson before call:");
Console::WriteLine("first = {0}, last = {1}, age = {2}",
personName.first, personName.last, personAll.age);
int res = NativeMethods::TestStructInStruct(personAll);
MyPerson personRes =
(MyPerson)Marshal::PtrToStructure(personAll.person,
MyPerson::typeid);
Marshal::FreeCoTaskMem(buffer);
Console::WriteLine("Person after call:");
Console::WriteLine("first = {0}, last = {1}, age = {2}",
personRes.first, personRes.last, personAll.age);
// Structure with an embedded structure.
MyPerson3 person3;// = gcnew MyPerson3();
person3.person.first = "John";
person3.person.last = "Evans";
person3.age = 27;
NativeMethods::TestStructInStruct3(person3);
// Structure with an embedded array.
MyArrayStruct myStruct;// = new MyArrayStruct();
myStruct.flag = false;
myStruct.vals = gcnew array<int>(3);
myStruct.vals[0] = 1;
myStruct.vals[1] = 4;
myStruct.vals[2] = 9;
Console::WriteLine("\nStructure with array before call:");
Console::WriteLine(myStruct.flag);
Console::WriteLine("{0} {1} {2}", myStruct.vals[0],
myStruct.vals[1], myStruct.vals[2]);
NativeMethods::TestArrayInStruct(myStruct);
Console::WriteLine("\nStructure with array after call:");
Console::WriteLine(myStruct.flag);
Console::WriteLine("{0} {1} {2}", myStruct.vals[0],
myStruct.vals[1], myStruct.vals[2]);
}
};
public class App
{
public static void Main()
{
// Structure with a pointer to another structure.
MyPerson personName;
personName.first = "Mark";
personName.last = "Lee";
MyPerson2 personAll;
personAll.age = 30;
IntPtr buffer = Marshal.AllocCoTaskMem(Marshal.SizeOf(personName));
Marshal.StructureToPtr(personName, buffer, false);
personAll.person = buffer;
Console.WriteLine("\nPerson before call:");
Console.WriteLine("first = {0}, last = {1}, age = {2}",
personName.first, personName.last, personAll.age);
int res = NativeMethods.TestStructInStruct(ref personAll);
MyPerson personRes =
(MyPerson)Marshal.PtrToStructure(personAll.person,
typeof(MyPerson));
Marshal.FreeCoTaskMem(buffer);
Console.WriteLine("Person after call:");
Console.WriteLine("first = {0}, last = {1}, age = {2}",
personRes.first, personRes.last, personAll.age);
// Structure with an embedded structure.
MyPerson3 person3 = new MyPerson3();
person3.person.first = "John";
person3.person.last = "Evans";
person3.age = 27;
NativeMethods.TestStructInStruct3(person3);
// Structure with an embedded array.
MyArrayStruct myStruct = new MyArrayStruct();
myStruct.flag = false;
myStruct.vals = new int[3];
myStruct.vals[0] = 1;
myStruct.vals[1] = 4;
myStruct.vals[2] = 9;
Console.WriteLine("\nStructure with array before call:");
Console.WriteLine(myStruct.flag);
Console.WriteLine("{0} {1} {2}", myStruct.vals[0],
myStruct.vals[1], myStruct.vals[2]);
NativeMethods.TestArrayInStruct(ref myStruct);
Console.WriteLine("\nStructure with array after call:");
Console.WriteLine(myStruct.flag);
Console.WriteLine("{0} {1} {2}", myStruct.vals[0],
myStruct.vals[1], myStruct.vals[2]);
}
}
Public Class App
Public Shared Sub Main()
' Structure with a pointer to another structure.
Dim personName As MyPerson
personName.first = "Mark"
personName.last = "Lee"
Dim personAll As MyPerson2
personAll.age = 30
Dim buffer As IntPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(
personName))
Marshal.StructureToPtr(personName, buffer, False)
personAll.person = buffer
Console.WriteLine(ControlChars.CrLf & "Person before call:")
Console.WriteLine("first = {0}, last = {1}, age = {2}",
personName.first, personName.last, personAll.age)
Dim res As Integer = NativeMethods.TestStructInStruct(personAll)
Dim personRes As MyPerson =
CType(Marshal.PtrToStructure(personAll.person,
GetType(MyPerson)), MyPerson)
Marshal.FreeCoTaskMem(buffer)
Console.WriteLine("Person after call:")
Console.WriteLine("first = {0}, last = {1}, age = {2}",
personRes.first,
personRes.last, personAll.age)
' Structure with an embedded structure.
Dim person3 As New MyPerson3()
person3.person.first = "John"
person3.person.last = "Evans"
person3.age = 27
NativeMethods.TestStructInStruct3(person3)
' Structure with an embedded array.
Dim myStruct As New MyArrayStruct()
myStruct.flag = False
Dim array(2) As Integer
myStruct.vals = array
myStruct.vals(0) = 1
myStruct.vals(1) = 4
myStruct.vals(2) = 9
Console.WriteLine(vbNewLine + "Structure with array before call:")
Console.WriteLine(myStruct.flag)
Console.WriteLine("{0} {1} {2}", myStruct.vals(0),
myStruct.vals(1), myStruct.vals(2))
NativeMethods.TestArrayInStruct(myStruct)
Console.WriteLine(vbNewLine + "Structure with array after call:")
Console.WriteLine(myStruct.flag)
Console.WriteLine("{0} {1} {2}", myStruct.vals(0),
myStruct.vals(1), myStruct.vals(2))
End Sub
End Class
FindFile — przykład
W tym przykładzie pokazano, jak przekazać strukturę zawierającą drugą, osadzoną strukturę do funkcji niezarządzanej. Pokazuje również, jak używać atrybutu MarshalAsAttribute do deklarowania tablicy o stałej długości w strukturze. W tym przykładzie osadzone elementy struktury są dodawane do struktury nadrzędnej. Aby zapoznać się z przykładem osadzonej struktury, która nie jest spłaszczone, zobacz Przykład struktury.
W przykładzie FindFile użyto następującej niezarządzanej funkcji pokazanej przy użyciu oryginalnej deklaracji funkcji:
FindFirstFile wyeksportowany z Kernel32.dll.
HANDLE FindFirstFile(LPCTSTR lpFileName, LPWIN32_FIND_DATA lpFindFileData);
Oryginalna struktura przekazana do funkcji zawiera następujące elementy:
typedef struct _WIN32_FIND_DATA
{
DWORD dwFileAttributes;
FILETIME ftCreationTime;
FILETIME ftLastAccessTime;
FILETIME ftLastWriteTime;
DWORD nFileSizeHigh;
DWORD nFileSizeLow;
DWORD dwReserved0;
DWORD dwReserved1;
TCHAR cFileName[ MAX_PATH ];
TCHAR cAlternateFileName[ 14 ];
} WIN32_FIND_DATA, *PWIN32_FIND_DATA;
W tym przykładzie FindData
klasa zawiera odpowiedni element członkowski danych dla każdego elementu oryginalnej struktury i osadzonej struktury. Zamiast dwóch oryginalnych buforów znaków klasa zastępuje ciągi. MarshalAsAttribute ustawia wyliczenie na ByValTStr, który służy do identyfikowania wbudowanych tablic znaków o stałej długości, które pojawiają się w strukturach niezarządzanychUnmanagedType.
Klasa NativeMethods
zawiera zarządzany prototyp FindFirstFile
metody, która przekazuje FindData
klasę jako parametr. Parametr musi być zadeklarowany za pomocą InAttribute atrybutów i OutAttribute , ponieważ klasy, które są typami referencyjnymi, są domyślnie przekazywane jako W parametrach.
Deklarowanie prototypów
// Declares a class member for each structure element.
[StructLayout(LayoutKind::Sequential, CharSet = CharSet::Auto)]
public ref class FindData
{
public:
int fileAttributes;
// creationTime was an embedded FILETIME structure.
int creationTime_lowDateTime;
int creationTime_highDateTime;
// lastAccessTime was an embedded FILETIME structure.
int lastAccessTime_lowDateTime;
int lastAccessTime_highDateTime;
// lastWriteTime was an embedded FILETIME structure.
int lastWriteTime_lowDateTime;
int lastWriteTime_highDateTime;
int nFileSizeHigh;
int nFileSizeLow;
int dwReserved0;
int dwReserved1;
[MarshalAs(UnmanagedType::ByValTStr, SizeConst = 260)]
String^ fileName;
[MarshalAs(UnmanagedType::ByValTStr, SizeConst = 14)]
String^ alternateFileName;
};
private ref class NativeMethods
{
public:
// Declares a managed prototype for the unmanaged function.
[DllImport("Kernel32.dll", CharSet = CharSet::Auto)]
static IntPtr FindFirstFile(String^ fileName, [In, Out]
FindData^ findFileData);
};
// Declares a class member for each structure element.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public class FindData
{
public int fileAttributes = 0;
// creationTime was an embedded FILETIME structure.
public int creationTime_lowDateTime = 0;
public int creationTime_highDateTime = 0;
// lastAccessTime was an embedded FILETIME structure.
public int lastAccessTime_lowDateTime = 0;
public int lastAccessTime_highDateTime = 0;
// lastWriteTime was an embedded FILETIME structure.
public int lastWriteTime_lowDateTime = 0;
public int lastWriteTime_highDateTime = 0;
public int nFileSizeHigh = 0;
public int nFileSizeLow = 0;
public int dwReserved0 = 0;
public int dwReserved1 = 0;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string fileName = null;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
public string alternateFileName = null;
}
internal static class NativeMethods
{
// Declares a managed prototype for the unmanaged function.
[DllImport("Kernel32.dll", CharSet = CharSet.Auto)]
internal static extern IntPtr FindFirstFile(
string fileName, [In, Out] FindData findFileData);
}
' Declares a class member for each structure element.
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)>
Public Class FindData
Public fileAttributes As Integer = 0
' creationTime was a by-value FILETIME structure.
Public creationTime_lowDateTime As Integer = 0
Public creationTime_highDateTime As Integer = 0
' lastAccessTime was a by-value FILETIME structure.
Public lastAccessTime_lowDateTime As Integer = 0
Public lastAccessTime_highDateTime As Integer = 0
' lastWriteTime was a by-value FILETIME structure.
Public lastWriteTime_lowDateTime As Integer = 0
Public lastWriteTime_highDateTime As Integer = 0
Public nFileSizeHigh As Integer = 0
Public nFileSizeLow As Integer = 0
Public dwReserved0 As Integer = 0
Public dwReserved1 As Integer = 0
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=260)>
Public fileName As String = Nothing
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=14)>
Public alternateFileName As String = Nothing
End Class
Friend Class NativeMethods
' Declares a managed prototype for the unmanaged function.
Friend Declare Auto Function FindFirstFile Lib "Kernel32.dll" (
ByVal fileName As String, <[In], Out> ByVal findFileData As _
FindData) As IntPtr
End Class
Wywoływanie funkcji
public ref class App
{
public:
static void Main()
{
FindData^ fd = gcnew FindData();
IntPtr handle = NativeMethods::FindFirstFile("C:\\*.*", fd);
Console::WriteLine("The first file: {0}", fd->fileName);
}
};
public class App
{
public static void Main()
{
FindData fd = new FindData();
IntPtr handle = NativeMethods.FindFirstFile("C:\\*.*", fd);
Console.WriteLine($"The first file: {fd.fileName}");
}
}
Public Class App
Public Shared Sub Main()
Dim fd As New FindData()
Dim handle As IntPtr = NativeMethods.FindFirstFile("C:\*.*", fd)
Console.WriteLine($"The first file: {fd.fileName}")
End Sub
End Class
przykład unii
W tym przykładzie pokazano, jak przekazywać struktury zawierające tylko typy wartości i struktury zawierające typ wartości i ciąg jako parametry funkcji niezarządzanej oczekującej unii. Związek reprezentuje lokalizację pamięci, którą można współużytkować przez co najmniej dwie zmienne.
W przykładzie Unions użyto następującej niezarządzanej funkcji pokazanej z oryginalną deklaracją funkcji:
TestUnion wyeksportowany z PinvokeLib.dll.
void TestUnion(MYUNION u, int type);
PinvokeLib.dll to niestandardowa niezarządzana biblioteka, która zawiera implementację dla wcześniej wymienionej funkcji i dwóch związków, MYUNION i MYUNION2. Związki zawierają następujące elementy:
union MYUNION
{
int number;
double d;
}
union MYUNION2
{
int i;
char str[128];
};
W kodzie zarządzanym związki są definiowane jako struktury. Struktura MyUnion
zawiera dwa typy wartości jako elementy członkowskie: liczbę całkowitą i podwójną. Atrybut StructLayoutAttribute jest ustawiony tak, aby kontrolować dokładną pozycję każdego elementu członkowskiego danych. Atrybut FieldOffsetAttribute zapewnia fizyczne położenie pól w niezarządzanej reprezentacji unii. Zwróć uwagę, że oba elementy członkowskie mają te same wartości przesunięcia, więc składowe mogą definiować ten sam fragment pamięci.
MyUnion2_1
i MyUnion2_2
zawierają odpowiednio typ wartości (liczba całkowita) i ciąg. W kodzie zarządzanym typy wartości i typy referencyjne nie mogą się nakładać. W tym przykładzie użyto przeciążenia metody, aby umożliwić obiektowi wywołującym używanie obu typów podczas wywoływania tej samej funkcji niezarządzanej. Układ elementu jest jawny MyUnion2_1
i ma dokładną wartość przesunięcia. Natomiast ma układ sekwencyjny, MyUnion2_2
ponieważ jawne układy nie są dozwolone z typami odwołań. Atrybut MarshalAsAttribute ustawia wyliczenie na UnmanagedTypeByValTStr, który służy do identyfikowania wbudowanych tablic znaków o stałej długości, które pojawiają się w niezarządzanej reprezentacji unii.
Klasa NativeMethods
zawiera prototypy metod TestUnion
i TestUnion2
. TestUnion2
parametr jest przeciążony do deklarowania MyUnion2_1
lub MyUnion2_2
jako parametrów.
Deklarowanie prototypów
// Declares managed structures instead of unions.
[StructLayout(LayoutKind::Explicit)]
public value struct MyUnion
{
public:
[FieldOffset(0)]
int i;
[FieldOffset(0)]
double d;
};
[StructLayout(LayoutKind::Explicit, Size = 128)]
public value struct MyUnion2_1
{
public:
[FieldOffset(0)]
int i;
};
[StructLayout(LayoutKind::Sequential)]
public value struct MyUnion2_2
{
public:
[MarshalAs(UnmanagedType::ByValTStr, SizeConst = 128)]
String^ str;
};
private ref class NativeMethods
{
public:
// Declares managed prototypes for unmanaged function.
[DllImport("..\\LIB\\PInvokeLib.dll")]
static void TestUnion(MyUnion u, int type);
[DllImport("..\\LIB\\PInvokeLib.dll")]
static void TestUnion2(MyUnion2_1 u, int type);
[DllImport("..\\LIB\\PInvokeLib.dll")]
static void TestUnion2(MyUnion2_2 u, int type);
};
// Declares managed structures instead of unions.
[StructLayout(LayoutKind.Explicit)]
public struct MyUnion
{
[FieldOffset(0)]
public int i;
[FieldOffset(0)]
public double d;
}
[StructLayout(LayoutKind.Explicit, Size = 128)]
public struct MyUnion2_1
{
[FieldOffset(0)]
public int i;
}
[StructLayout(LayoutKind.Sequential)]
public struct MyUnion2_2
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string str;
}
internal static class NativeMethods
{
// Declares managed prototypes for unmanaged function.
[DllImport("..\\LIB\\PInvokeLib.dll")]
internal static extern void TestUnion(MyUnion u, int type);
[DllImport("..\\LIB\\PInvokeLib.dll")]
internal static extern void TestUnion2(MyUnion2_1 u, int type);
[DllImport("..\\LIB\\PInvokeLib.dll")]
internal static extern void TestUnion2(MyUnion2_2 u, int type);
}
' Declares managed structures instead of unions.
<StructLayout(LayoutKind.Explicit)>
Public Structure MyUnion
<FieldOffset(0)> Public i As Integer
<FieldOffset(0)> Public d As Double
End Structure
<StructLayout(LayoutKind.Explicit, Size:=128)>
Public Structure MyUnion2_1
<FieldOffset(0)> Public i As Integer
End Structure
<StructLayout(LayoutKind.Sequential)>
Public Structure MyUnion2_2
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=128)>
Public str As String
End Structure
Friend Class NativeMethods
' Declares managed prototypes for unmanaged function.
<DllImport("..\LIB\PinvokeLib.dll", CallingConvention:=CallingConvention.Cdecl)>
Friend Shared Sub TestUnion(
ByVal u As MyUnion, ByVal type As Integer)
End Sub
<DllImport("..\LIB\PinvokeLib.dll", CallingConvention:=CallingConvention.Cdecl)>
Friend Overloads Shared Sub TestUnion2(
ByVal u As MyUnion2_1, ByVal type As Integer)
End Sub
<DllImport("..\LIB\PinvokeLib.dll", CallingConvention:=CallingConvention.Cdecl)>
Friend Overloads Shared Sub TestUnion2(
ByVal u As MyUnion2_2, ByVal type As Integer)
End Sub
End Class
Wywoływanie funkcji
public ref class App
{
public:
static void Main()
{
MyUnion mu;// = new MyUnion();
mu.i = 99;
NativeMethods::TestUnion(mu, 1);
mu.d = 99.99;
NativeMethods::TestUnion(mu, 2);
MyUnion2_1 mu2_1;// = new MyUnion2_1();
mu2_1.i = 99;
NativeMethods::TestUnion2(mu2_1, 1);
MyUnion2_2 mu2_2;// = new MyUnion2_2();
mu2_2.str = "*** string ***";
NativeMethods::TestUnion2(mu2_2, 2);
}
};
public class App
{
public static void Main()
{
MyUnion mu = new MyUnion();
mu.i = 99;
NativeMethods.TestUnion(mu, 1);
mu.d = 99.99;
NativeMethods.TestUnion(mu, 2);
MyUnion2_1 mu2_1 = new MyUnion2_1();
mu2_1.i = 99;
NativeMethods.TestUnion2(mu2_1, 1);
MyUnion2_2 mu2_2 = new MyUnion2_2();
mu2_2.str = "*** string ***";
NativeMethods.TestUnion2(mu2_2, 2);
}
}
Public Class App
Public Shared Sub Main()
Dim mu As New MyUnion()
mu.i = 99
NativeMethods.TestUnion(mu, 1)
mu.d = 99.99
NativeMethods.TestUnion(mu, 2)
Dim mu2_1 As New MyUnion2_1()
mu2_1.i = 99
NativeMethods.TestUnion2(mu2_1, 1)
Dim mu2_2 As New MyUnion2_2()
mu2_2.str = "*** string ***"
NativeMethods.TestUnion2(mu2_2, 2)
End Sub
End Class
Przykład platformy
W niektórych scenariuszach układy struct
mogą union
się różnić w zależności od platformy docelowej. Rozważmy na przykład STRRET
typ zdefiniowany w scenariuszu COM:
#include <pshpack8.h> /* Defines the packing of the struct */
typedef struct _STRRET
{
UINT uType;
/* [switch_is][switch_type] */ union
{
/* [case()][string] */ LPWSTR pOleStr;
/* [case()] */ UINT uOffset;
/* [case()] */ char cStr[ 260 ];
} DUMMYUNIONNAME;
} STRRET;
#include <poppack.h>
Powyższe struct
polecenie jest deklarowane przy użyciu nagłówków systemu Windows mających wpływ na układ pamięci typu. Po zdefiniowaniu w środowisku zarządzanym te szczegóły układu są potrzebne do prawidłowego współdziałania z kodem natywnym.
Prawidłowa definicja zarządzana tego typu w procesie 32-bitowym to:
[StructLayout(LayoutKind.Explicit, Size = 264)]
public struct STRRET_32
{
[FieldOffset(0)]
public uint uType;
[FieldOffset(4)]
public IntPtr pOleStr;
[FieldOffset(4)]
public uint uOffset;
[FieldOffset(4)]
public IntPtr cStr;
}
W procesie 64-bitowym rozmiar i przesunięcia pola są różne. Prawidłowy układ to:
[StructLayout(LayoutKind.Explicit, Size = 272)]
public struct STRRET_64
{
[FieldOffset(0)]
public uint uType;
[FieldOffset(8)]
public IntPtr pOleStr;
[FieldOffset(8)]
public uint uOffset;
[FieldOffset(8)]
public IntPtr cStr;
}
Niepowodzenie prawidłowego rozważenia układu natywnego w scenariuszu międzyoperacyjnym może spowodować losowe awarie lub gorzej, nieprawidłowe obliczenia.
Domyślnie zestawy .NET mogą działać zarówno w 32-bitowej, jak i 64-bitowej wersji środowiska uruchomieniowego platformy .NET. Aplikacja musi poczekać na czas wykonywania, aby zdecydować, które z poprzednich definicji mają być używane.
Poniższy fragment kodu przedstawia przykład wyboru między definicją 32-bitową a 64-bitową w czasie wykonywania.
if (IntPtr.Size == 8)
{
// Use the STRRET_64 definition
}
else
{
Debug.Assert(IntPtr.Size == 4);
// Use the STRRET_32 definition
}
SysTime — przykład
W tym przykładzie pokazano, jak przekazać wskaźnik do klasy do niezarządzanej funkcji, która oczekuje wskaźnika do struktury.
W przykładzie SysTime użyto następującej niezarządzanej funkcji pokazanej z oryginalną deklaracją funkcji:
Polecenie GetSystemTime wyeksportowane z Kernel32.dll.
VOID GetSystemTime(LPSYSTEMTIME lpSystemTime);
Oryginalna struktura przekazana do funkcji zawiera następujące elementy:
typedef struct _SYSTEMTIME {
WORD wYear;
WORD wMonth;
WORD wDayOfWeek;
WORD wDay;
WORD wHour;
WORD wMinute;
WORD wSecond;
WORD wMilliseconds;
} SYSTEMTIME, *PSYSTEMTIME;
W tym przykładzie SystemTime
klasa zawiera elementy oryginalnej struktury reprezentowanej jako składowe klasy. Atrybut StructLayoutAttribute jest ustawiony tak, aby upewnić się, że elementy członkowskie są rozmieszczone sekwencyjnie w pamięci, w kolejności, w jakiej się pojawiają.
Klasa NativeMethods
zawiera zarządzany prototyp GetSystemTime
metody, która domyślnie przekazuje SystemTime
klasę jako parametr in/out. Parametr musi być zadeklarowany za pomocą InAttribute atrybutów i OutAttribute , ponieważ klasy, które są typami referencyjnymi, są domyślnie przekazywane jako W parametrach. Aby obiekt wywołujący otrzymał wyniki, należy jawnie zastosować te atrybuty kierunkowe. Klasa App
tworzy nowe wystąpienie SystemTime
klasy i uzyskuje dostęp do pól danych.
Przykłady kodu
using namespace System;
using namespace System::Runtime::InteropServices; // For StructLayout, DllImport
[StructLayout(LayoutKind::Sequential)]
public ref class SystemTime
{
public:
unsigned short year;
unsigned short month;
unsigned short weekday;
unsigned short day;
unsigned short hour;
unsigned short minute;
unsigned short second;
unsigned short millisecond;
};
public class NativeMethods
{
public:
// Declares a managed prototype for the unmanaged function using Platform Invoke.
[DllImport("Kernel32.dll")]
static void GetSystemTime([In, Out] SystemTime^ st);
};
public class App
{
public:
static void Main()
{
Console::WriteLine("C++/CLI SysTime Sample using Platform Invoke");
SystemTime^ st = gcnew SystemTime();
NativeMethods::GetSystemTime(st);
Console::Write("The Date is: ");
Console::Write("{0} {1} {2}", st->month, st->day, st->year);
}
};
int main()
{
App::Main();
}
// The program produces output similar to the following:
//
// C++/CLI SysTime Sample using Platform Invoke
// The Date is: 3 21 2010
using System;
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential)]
public class SystemTime
{
public ushort year;
public ushort month;
public ushort weekday;
public ushort day;
public ushort hour;
public ushort minute;
public ushort second;
public ushort millisecond;
}
internal static class NativeMethods
{
// Declares a managed prototype for the unmanaged function using Platform Invoke.
[DllImport("Kernel32.dll")]
internal static extern void GetSystemTime([In, Out] SystemTime st);
}
public class App
{
public static void Main()
{
Console.WriteLine("C# SysTime Sample using Platform Invoke");
SystemTime st = new SystemTime();
NativeMethods.GetSystemTime(st);
Console.Write("The Date is: ");
Console.Write($"{st.month} {st.day} {st.year}");
}
}
// The program produces output similar to the following:
//
// C# SysTime Sample using Platform Invoke
// The Date is: 3 21 2010
Imports System.Runtime.InteropServices
' Declares a class member for each structure element.
<StructLayout(LayoutKind.Sequential)>
Public Class SystemTime
Public year As Short
Public month As Short
Public weekday As Short
Public day As Short
Public hour As Short
Public minute As Short
Public second As Short
Public millisecond As Short
End Class
Friend Class NativeMethods
' Declares a managed prototype for the unmanaged function.
Friend Declare Sub GetSystemTime Lib "Kernel32.dll" (
<[In](), Out()> ByVal st As SystemTime)
End Class
Public Class App
Public Shared Sub Main()
Console.WriteLine("VB .NET SysTime Sample using Platform Invoke")
Dim st As New SystemTime()
NativeMethods.GetSystemTime(st)
Console.Write($"The Date is: {st.month} {st.day} {st.year}")
End Sub
End Class
' The program produces output similar to the following:
'
' VB .NET SysTime Sample using Platform Invoke
' The Date is: 3 21 2010
OutArrayOfStructs — przykład
W tym przykładzie pokazano, jak przekazać tablicę struktur, która zawiera liczby całkowite i ciągi jako parametry out do funkcji niezarządzanej.
W tym przykładzie pokazano, jak wywołać funkcję natywną przy użyciu klasy i przy użyciu niebezpiecznego Marshal kodu.
W tym przykładzie użyto funkcji otoki i wywołań platformy zdefiniowanych w PinvokeLib.dll, które są również udostępniane w plikach źródłowych. Używa TestOutArrayOfStructs
funkcji i MYSTRSTRUCT2
struktury. Struktura zawiera następujące elementy:
typedef struct _MYSTRSTRUCT2
{
char* buffer;
UINT size;
} MYSTRSTRUCT2;
Klasa MyStruct
zawiera obiekt ciągu znaków ANSI. Pole CharSet określa format ANSI. MyUnsafeStruct
, jest strukturą zawierającą IntPtr typ zamiast ciągu.
Klasa NativeMethods
zawiera przeciążonej TestOutArrayOfStructs
metody prototypu. Jeśli metoda deklaruje wskaźnik jako parametr, klasa powinna być oznaczona unsafe
słowem kluczowym. Ponieważ język Visual Basic nie może używać niebezpiecznego kodu, przeciążona metoda, niebezpieczny modyfikator i MyUnsafeStruct
struktura są niepotrzebne.
Klasa App
implementuje metodę UsingMarshaling
, która wykonuje wszystkie zadania niezbędne do przekazania tablicy. Tablica jest oznaczona za pomocą słowa kluczowego out
(ByRef
w Visual Basic), aby wskazać, że dane przechodzą z wywoływania do obiektu wywołującego. Implementacja używa następujących Marshal metod klasy:
PtrToStructure do marshalingu danych z buforu niezarządzanego do obiektu zarządzanego.
DestroyStructure aby zwolnić pamięć zarezerwowaną dla ciągów w strukturze.
FreeCoTaskMem aby zwolnić pamięć zarezerwowaną dla tablicy.
Jak wspomniano wcześniej, język C# zezwala na niebezpieczny kod, a język Visual Basic nie. W przykładzie języka C# jest implementacją metody alternatywnej, UsingUnsafePointer
która używa wskaźników zamiast Marshal klasy do przekazywania z powrotem tablicy zawierającej MyUnsafeStruct
strukturę.
Deklarowanie prototypów
// Declares a class member for each structure element.
[StructLayout(LayoutKind::Sequential, CharSet = CharSet::Ansi)]
public ref class MyStruct
{
public:
String^ buffer;
int size;
};
// Declares a structure with a pointer.
[StructLayout(LayoutKind::Sequential)]
public value struct MyUnsafeStruct
{
public:
IntPtr buffer;
int size;
};
private ref class NativeMethods
{
public:
// Declares managed prototypes for the unmanaged function.
[DllImport("..\\LIB\\PInvokeLib.dll")]
static void TestOutArrayOfStructs(int% size, IntPtr% outArray);
[DllImport("..\\LIB\\PInvokeLib.dll")]
static void TestOutArrayOfStructs(int% size, MyUnsafeStruct** outArray);
};
// Declares a class member for each structure element.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public class MyStruct
{
public string buffer;
public int size;
}
// Declares a structure with a pointer.
[StructLayout(LayoutKind.Sequential)]
public struct MyUnsafeStruct
{
public IntPtr buffer;
public int size;
}
internal static unsafe class NativeMethods
{
// Declares managed prototypes for the unmanaged function.
[DllImport("..\\LIB\\PInvokeLib.dll")]
internal static extern void TestOutArrayOfStructs(
out int size, out IntPtr outArray);
[DllImport("..\\LIB\\PInvokeLib.dll")]
internal static extern void TestOutArrayOfStructs(
out int size, MyUnsafeStruct** outArray);
}
' Declares a class member for each structure element.
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)>
Public Class MyStruct
Public buffer As String
Public someSize As Integer
End Class
Friend Class NativeMethods
' Declares a managed prototype for the unmanaged function.
<DllImport("..\LIB\PinvokeLib.dll", CallingConvention:=CallingConvention.Cdecl)>
Friend Shared Sub TestOutArrayOfStructs(
ByRef arrSize As Integer, ByRef outArray As IntPtr)
End Sub
End Class
Wywoływanie funkcji
public ref class App
{
public:
static void Main()
{
Console::WriteLine("\nUsing marshal class\n");
UsingMarshaling();
Console::WriteLine("\nUsing unsafe code\n");
UsingUnsafePointer();
}
static void UsingMarshaling()
{
int size;
IntPtr outArray;
NativeMethods::TestOutArrayOfStructs(size, outArray);
array<MyStruct^>^ manArray = gcnew array<MyStruct^>(size);
IntPtr current = outArray;
for (int i = 0; i < size; i++)
{
manArray[i] = gcnew MyStruct();
Marshal::PtrToStructure(current, manArray[i]);
Marshal::DestroyStructure(current, MyStruct::typeid);
//current = (IntPtr)((long)current + Marshal::SizeOf(manArray[i]));
current = current + Marshal::SizeOf(manArray[i]);
Console::WriteLine("Element {0}: {1} {2}", i, manArray[i]->buffer,
manArray[i]->size);
}
Marshal::FreeCoTaskMem(outArray);
}
static void UsingUnsafePointer()
{
int size;
MyUnsafeStruct* pResult;
NativeMethods::TestOutArrayOfStructs(size, &pResult);
MyUnsafeStruct* pCurrent = pResult;
for (int i = 0; i < size; i++, pCurrent++)
{
Console::WriteLine("Element {0}: {1} {2}", i,
Marshal::PtrToStringAnsi(pCurrent->buffer), pCurrent->size);
Marshal::FreeCoTaskMem(pCurrent->buffer);
}
Marshal::FreeCoTaskMem((IntPtr)pResult);
}
};
public class App
{
public static void Main()
{
Console.WriteLine("\nUsing marshal class\n");
UsingMarshaling();
Console.WriteLine("\nUsing unsafe code\n");
UsingUnsafePointer();
}
public static void UsingMarshaling()
{
int size;
IntPtr outArray;
NativeMethods.TestOutArrayOfStructs(out size, out outArray);
MyStruct[] manArray = new MyStruct[size];
IntPtr current = outArray;
for (int i = 0; i < size; i++)
{
manArray[i] = new MyStruct();
Marshal.PtrToStructure(current, manArray[i]);
//Marshal.FreeCoTaskMem((IntPtr)Marshal.ReadInt32(current));
Marshal.DestroyStructure(current, typeof(MyStruct));
current = (IntPtr)((long)current + Marshal.SizeOf(manArray[i]));
Console.WriteLine("Element {0}: {1} {2}", i, manArray[i].buffer,
manArray[i].size);
}
Marshal.FreeCoTaskMem(outArray);
}
public static unsafe void UsingUnsafePointer()
{
int size;
MyUnsafeStruct* pResult;
NativeMethods.TestOutArrayOfStructs(out size, &pResult);
MyUnsafeStruct* pCurrent = pResult;
for (int i = 0; i < size; i++, pCurrent++)
{
Console.WriteLine("Element {0}: {1} {2}", i,
Marshal.PtrToStringAnsi(pCurrent->buffer), pCurrent->size);
Marshal.FreeCoTaskMem(pCurrent->buffer);
}
Marshal.FreeCoTaskMem((IntPtr)pResult);
}
}
Public Class App
Public Shared Sub Main()
Console.WriteLine(vbNewLine + "Using marshal class" + vbNewLine)
UsingMarshaling()
'Visual Basic 2005 cannot use unsafe code.
End Sub
Public Shared Sub UsingMarshaling()
Dim arrSize As Integer
Dim outArray As IntPtr
NativeMethods.TestOutArrayOfStructs(arrSize, outArray)
Dim manArray(arrSize - 1) As MyStruct
Dim current As IntPtr = outArray
Dim i As Integer
For i = 0 To arrSize - 1
manArray(i) = New MyStruct()
Marshal.PtrToStructure(current, manArray(i))
Marshal.DestroyStructure(current, GetType(MyStruct))
current = IntPtr.op_Explicit(current.ToInt64() _
+ Marshal.SizeOf(manArray(i)))
Console.WriteLine("Element {0}: {1} {2}", i, manArray(i).
buffer, manArray(i).someSize)
Next i
Marshal.FreeCoTaskMem(outArray)
End Sub
End Class