Marshalling di classi, strutture e unioni
In .NET Framework classi e strutture sono simili. Entrambe possono avere campi, proprietà ed eventi nonché metodi statici e non statici. Una differenza fondamentale è data dal fatto che le strutture sono tipi di valore e le classi sono tipi di riferimento.
Nella tabella seguente sono elencate le opzioni di marshalling per le classi, le strutture e le unioni con la descrizione dell'utilizzo e un collegamento all'esempio corrispondente di platform invoke.
Tipo | Descrizione | Esempio |
---|---|---|
Classe per valore. | Passa una classe con membri Integer come parametro in/out, come il case gestito. | Esempio di SysTime |
Struttura per valore. | Passa le strutture come parametri in. | Esempio di strutture |
Struttura per riferimento. | Passa le strutture come parametri in/out. | Esempio di OSInfo |
Struttura con strutture annidate (semplificata). | Passa una classe che rappresenta una struttura con strutture annidate nella funzione non gestita. La struttura viene semplificata in una sola grande struttura nel prototipo gestito. | Esempio di FindFile |
Struttura con puntatore a un'altra struttura. | Passa una struttura che contiene un puntatore a una seconda struttura come membro. | Esempio di strutture |
Matrice di strutture con Integer per valore. | Passa una matrice di strutture che contengono solo Integer come un parametro in/out. È possibile modificare i membri della matrice. | Esempio di matrici |
Matrice di strutture con Integer e stringhe per riferimento. | Passa una matrice di strutture che contengono Integer e stringhe come un parametro out. La memoria per la matrice viene allocata dalla funzione chiamata. | Esempio di OutArrayOfStructs |
Unioni con tipi di valore. | Passa le unioni con tipi di valore (Integer e Double). | Esempio di unioni |
Unioni con tipi misti. | Passa unioni con tipi misti (Integer e String). | Esempio di unioni |
Struct con layout specifico della piattaforma. | Passa un tipo con definizioni di compressione nativa. | Esempio di piattaforma |
Valori null nella struttura. | Passa un riferimento null (Nothing in Visual Basic) invece di un riferimento a un tipo di valore. | Esempio di HandleRef |
Structures (esempio)
In questo esempio viene dimostrato come passare una struttura che punta a una seconda struttura, una struttura con una struttura incorporata e una struttura con una matrice incorporata.
Nell'esempio di strutture vengono usate le seguenti funzioni non gestite, illustrate con le dichiarazioni di funzione originali:
TestStructInStruct esportata da PinvokeLib.dll.
int TestStructInStruct(MYPERSON2* pPerson2);
TestStructInStruct3 esportata da PinvokeLib.dll.
void TestStructInStruct3(MYPERSON3 person3);
TestArrayInStruct esportata da PinvokeLib.dll.
void TestArrayInStruct(MYARRAYSTRUCT* pStruct);
PinvokeLib è una libreria non gestita personalizzata contenente implementazioni per le funzioni elencate in precedenza e quattro strutture, ovvero: MYPERSON, MYPERSON2, MYPERSON3 e MYARRAYSTRUCT. Le strutture contengono gli elementi seguenti:
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;
Le strutture gestite MyPerson
,MyPerson2
, MyPerson3
e MyArrayStruct
hanno le caratteristiche seguenti:
MyPerson
contiene solo membri stringa. Le stringhe vengono impostate sul formato ANSI dal campo CharSet, quando viene passato alla funzione non gestita.MyPerson2
contiene un tipo IntPtr alla strutturaMyPerson
. Il tipo IntPtr sostituisce il puntatore originale alla struttura non gestita poiché nelle applicazioni .NET Framework non vengono usati i puntatori a meno che il codice non sia contrassegnato come unsafe.MyPerson3
contieneMyPerson
come struttura incorporata. È possibile semplificare una struttura incorporata in un'altra struttura posizionandone gli elementi direttamente nella struttura principale oppure mantenerla incorporata, come accade in questo esempio.MyArrayStruct
contiene una matrice di Integer. L'attributo MarshalAsAttribute imposta il valore di enumerazione UnmanagedType su ByValArray, che consente di indicare il numero di elementi nella matrice.
Per tutte le strutture di questo esempio, l'attributo StructLayoutAttribute viene applicato in modo che i membri vengano disposti in sequenza nella memoria, nell'ordine in cui appaiono.
La classe NativeMethods
contiene prototipi gestiti per i metodi TestStructInStruct
, TestStructInStruct3
e TestArrayInStruct
chiamati dalla classe App
. Ciascun prototipo dichiara un singolo parametro, come indicato di seguito:
TestStructInStruct
dichiara un riferimento al tipoMyPerson2
come parametro.TestStructInStruct3
dichiara il tipoMyPerson3
come parametro e passa il parametro per valore.TestArrayInStruct
dichiara un riferimento al tipoMyArrayStruct
come parametro.
Come argomenti per i metodi, le strutture vengono passate per valore a meno che il parametro non contenga la parola chiave ref (ByRef in Visual Basic). Ad esempio, il metodo TestStructInStruct
passa un riferimento, ossia il valore di un indirizzo, a un oggetto di tipo MyPerson2
al codice non gestito. Per modificare la struttura alla quale punta MyPerson2
, nell'esempio viene creato un buffer di dimensioni specificate e ne viene restituito l'indirizzo combinando i metodi Marshal.AllocCoTaskMem e Marshal.SizeOf. In seguito, il contenuto della struttura gestita viene copiato nel buffer non gestito. Infine, il metodo Marshal.PtrToStructure viene usato per effettuare il marshalling dei dati dal buffer non gestito in un oggetto gestito, mentre il metodo Marshal.FreeCoTaskMem viene usato per liberare il blocco di memoria non gestito.
Dichiarazione dei prototipi
// 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
Funzioni chiamanti
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 (esempio)
In questo esempio viene illustrato come passare una struttura che contiene una seconda struttura incorporata a una funzione non gestita. Viene inoltre illustrato come usare l'attributo MarshalAsAttribute per dichiarare una matrice a lunghezza fissa all'interno della struttura. Nell'esempio gli elementi della struttura incorporata vengono aggiunti alla struttura padre. Per un esempio di struttura incorporata non appiattita, vedere Esempio di strutture.
Nell'esempio FindFile viene usata la seguente funzione non gestita, illustrata con la relativa dichiarazione di funzione originale:
FindFirstFile esportata da Kernel32.dll.
HANDLE FindFirstFile(LPCTSTR lpFileName, LPWIN32_FIND_DATA lpFindFileData);
La struttura originale passata alla funzione contiene gli elementi seguenti:
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;
In questo esempio, la classe FindData
contiene un membro dati corrispondente per ogni elemento della struttura originale e della struttura incorporata. Al posto di due buffer di caratteri originali, la classe usa delle stringhe. MarshalAsAttribute imposta l'enumerazione UnmanagedType su ByValTStr, che consente di identificare le matrici di caratteri inline a lunghezza fissa visualizzate all'interno delle strutture non gestite.
La classe NativeMethods
contiene un prototipo gestito del metodo FindFirstFile
, che passa la classe FindData
come parametro. Il parametro deve essere dichiarato con gli attributi InAttribute e OutAttribute perché le classi, che sono tipi di riferimento, per impostazione predefinita vengono passate come parametri in.
Dichiarazione dei prototipi
// 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
Funzioni chiamanti
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
unioni (esempio)
In questo esempio viene dimostrato come passare le strutture contenenti solo tipi di valore e le strutture contenenti un tipo di valore e una stringa come parametri a una funzione non gestita per la quale è prevista un'unione. Un'unione rappresenta un percorso di memoria che può essere condiviso da due o più variabili.
Nell'esempio di unioni viene usata la seguente funzione non gestita, illustrata con la dichiarazione di funzione originale:
TestUnion esportata da PinvokeLib.dll.
void TestUnion(MYUNION u, int type);
PinvokeLib.dll è una libreria non gestita personalizzata contenente un'implementazione per la funzione elencata in precedenza e due unioni, MYUNION e MYUNION2. Le unioni contengono i seguenti elementi:
union MYUNION
{
int number;
double d;
}
union MYUNION2
{
int i;
char str[128];
};
Nel codice gestito, le unioni sono definite come strutture. La struttura MyUnion
contiene due tipi di valore come membri, ovvero un Integer e un Double. L'attributo StructLayoutAttribute è impostato in modo da controllare l'esatta posizione di ciascun membro dati. L'attributo FieldOffsetAttribute fornisce la posizione fisica dei campi all'interno della rappresentazione non gestita di un'unione. Poiché entrambi hanno gli stessi valori di offset, i membri possono definire la stessa porzione di memoria.
MyUnion2_1
e MyUnion2_2
contengono rispettivamente un tipo di valore (Integer) e una stringa. Nel codice gestito i tipi di valore e quelli di riferimento non possono sovrapporsi. In questo esempio l'overload dei metodi consente al chiamante di usare entrambi i tipi nella chiamata alla stessa funzione non gestita. Il layout di MyUnion2_1
è esplicito e ha un valore di offset preciso. Al contrario, MyUnion2_2
ha un layout sequenziale, poiché i layout espliciti non sono consentiti con i tipi di riferimento. L'attributo MarshalAsAttribute impostazione l'enumerazione UnmanagedType su ByValTStr, che consente di identificare le matrici di caratteri inline a lunghezza fissa presenti nella rappresentazione non gestita dell'unione.
La classe NativeMethods
contiene i prototipi per i metodi TestUnion
e TestUnion2
. TestUnion2
viene sottoposto a overload allo scopo di dichiarare MyUnion2_1
o MyUnion2_2
come parametri.
Dichiarazione dei prototipi
// 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
Funzioni chiamanti
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
Esempio di piattaforma
In alcuni scenari, i layout struct
e union
possono variare a seconda della piattaforma di destinazione. Ad esempio, si consideri il tipo STRRET
quando viene definito in uno scenario 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>
Lo struct
precedente viene dichiarato con le intestazioni di Windows che influenzano il layout di memoria del tipo. Se definito in un ambiente gestito, questi dettagli di layout sono necessari per interagire correttamente con il codice nativo.
La definizione gestita corretta di questo tipo in un processo a 32 bit è:
[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;
}
In un processo a 64 bit, le dimensioni e gli offset dei campi sono diversi. Il layout corretto è:
[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;
}
Se non si considera correttamente il layout nativo in uno scenario di interoperabilità, possono verificarsi arresti anomali casuali o, peggio ancora, calcoli non corretti.
Per impostazione predefinita, gli assembly .NET possono essere eseguiti in una versione a 32 bit e a 64 bit del runtime .NET. L'app deve attendere fino al runtime per decidere quale delle definizioni precedenti usare.
Il frammento di codice seguente mostra un esempio di come scegliere tra la definizione a 32 bit e quella a 64 bit in fase di esecuzione.
if (IntPtr.Size == 8)
{
// Use the STRRET_64 definition
}
else
{
Debug.Assert(IntPtr.Size == 4);
// Use the STRRET_32 definition
}
SysTime (esempio)
In questo esempio viene dimostrato come passare un puntatore a una classe a una funzione non gestita per la quale è previsto un puntatore a una struttura.
Nell'esempio SysTime viene usata la seguente funzione non gestita, illustrata con la dichiarazione di funzione originale:
GetSystemTime esportata da Kernel32.dll.
VOID GetSystemTime(LPSYSTEMTIME lpSystemTime);
La struttura originale passata alla funzione contiene gli elementi seguenti:
typedef struct _SYSTEMTIME {
WORD wYear;
WORD wMonth;
WORD wDayOfWeek;
WORD wDay;
WORD wHour;
WORD wMinute;
WORD wSecond;
WORD wMilliseconds;
} SYSTEMTIME, *PSYSTEMTIME;
In questo esempio la classe SystemTime
contiene gli elementi della struttura originale rappresentati come membri di classe. L'attributo StructLayoutAttribute è impostato in modo da garantire che i membri vengano inseriti in memoria in sequenza, nell'ordine in cui appaiono.
La classe NativeMethods
contiene un prototipo gestito del metodo GetSystemTime
, che passa la classe SystemTime
come parametro in/out per impostazione predefinita. Il parametro deve essere dichiarato con gli attributi InAttribute e OutAttribute perché le classi, che sono tipi di riferimento, per impostazione predefinita vengono passate come parametri in. Affinché il chiamante riceva i risultati, è necessario applicare questi attributi direzionali in modo esplicito. Con la classe App
si crea una nuova istanza della classe SystemTime
e si accede ai relativi campi di dati.
Esempi di codice
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 (esempio)
In questo esempio viene illustrato come passare una matrice di strutture che contiene Integer e stringhe come parametri out a una funzione non gestita.
L'esempio dimostra come chiamare una funzione nativa usando la classe Marshal e usando codice di tipo unsafe.
Questo esempio usa funzioni wrapper e operazioni platform invoke definite in PinvokeLib.dll, disponibili anche nei file di origine. Vengono usate la funzione TestOutArrayOfStructs
e la struttura MYSTRSTRUCT2
. La struttura contiene gli elementi seguenti:
typedef struct _MYSTRSTRUCT2
{
char* buffer;
UINT size;
} MYSTRSTRUCT2;
La classe MyStruct
contiene un oggetto stringa di caratteri ANSI. Il campo CharSet specifica il formato ANSI. MyUnsafeStruct
è una struttura contenente un tipo IntPtr anziché una stringa.
La classe NativeMethods
contiene il metodo prototipo TestOutArrayOfStructs
di overload. Se un metodo dichiara un puntatore come parametro, la classe deve essere contrassegnata con la parola chiave unsafe
. Poiché Visual Basic non può usare codice unsafe, il metodo di overload, il modificatore unsafe e la struttura MyUnsafeStruct
non sono necessari.
La App
classe implementa il metodo UsingMarshaling
che esegue tutte le attività necessarie per passare la matrice. La matrice è contrassegnata con la parola chiave out
(ByRef
in Visual Basic) per indicare che i dati passano dal chiamato al chiamante. L'implementazione usa i seguenti metodi della classe Marshal:
PtrToStructure per il marshalling dei dati dal buffer non gestito a un oggetto gestito.
DestroyStructure per rilasciare la memoria riservata alle stringhe nella struttura.
FreeCoTaskMem per rilasciare la memoria riservata alla matrice.
Come accennato in precedenza, C# consente il codice unsafe, mentre Visual Basic non lo consente. Nell'esempio C#, UsingUnsafePointer
è un'implementazione alternativa del metodo che usa puntatori anziché la classe Marshal per passare nuovamente la matrice che contiene la struttura MyUnsafeStruct
.
Dichiarazione dei prototipi
// 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
Funzioni chiamanti
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