Marshaling par défaut des chaînes
Les classes System.String et System.Text.StringBuilder présentent un comportement de marshaling semblable.
Les chaînes sont marshalées en tant que type BSTR
de style COM ou comme une chaîne se terminant par un caractère Null (un tableau de caractères se terminant par un caractère Null). Les caractères de la chaîne peuvent être marshalés en tant que caractères Unicode (valeur par défaut sur les systèmes Windows) ou ANSI.
Chaînes utilisées dans les interfaces
Le tableau suivant montre les options de marshaling pour le type de données String quand il est marshalé comme un argument de méthode du code non managé. L'attribut MarshalAsAttribute fournit plusieurs valeurs d'énumération UnmanagedType pour marshaler des chaînes d'interfaces COM.
Type d'énumération | Description du format non managé |
---|---|
UnmanagedType.BStr (valeur par défaut) |
BSTR de style COM avec une longueur prédéfinie et des caractères Unicode. |
UnmanagedType.LPStr |
Pointeur vers un tableau de caractères ANSI terminé par un caractère Null. |
UnmanagedType.LPWStr |
Pointeur vers un tableau de caractères Unicode terminé par un caractère null. |
Ce tableau s’applique à String. Pour StringBuilder, les seules options autorisées sont UnmanagedType.LPStr
et UnmanagedType.LPWStr
.
L'exemple suivant montre des chaînes déclarées dans l'interface IStringWorker
.
public interface IStringWorker
{
void PassString1(string s);
void PassString2([MarshalAs(UnmanagedType.BStr)] string s);
void PassString3([MarshalAs(UnmanagedType.LPStr)] string s);
void PassString4([MarshalAs(UnmanagedType.LPWStr)] string s);
void PassStringRef1(ref string s);
void PassStringRef2([MarshalAs(UnmanagedType.BStr)] ref string s);
void PassStringRef3([MarshalAs(UnmanagedType.LPStr)] ref string s);
void PassStringRef4([MarshalAs(UnmanagedType.LPWStr)] ref string s);
}
Public Interface IStringWorker
Sub PassString1(s As String)
Sub PassString2(<MarshalAs(UnmanagedType.BStr)> s As String)
Sub PassString3(<MarshalAs(UnmanagedType.LPStr)> s As String)
Sub PassString4(<MarshalAs(UnmanagedType.LPWStr)> s As String)
Sub PassStringRef1(ByRef s As String)
Sub PassStringRef2(<MarshalAs(UnmanagedType.BStr)> ByRef s As String)
Sub PassStringRef3(<MarshalAs(UnmanagedType.LPStr)> ByRef s As String)
Sub PassStringRef4(<MarshalAs(UnmanagedType.LPWStr)> ByRef s As String)
End Interface
L'exemple suivant montre l'interface correspondante décrite dans une bibliothèque de types.
interface IStringWorker : IDispatch
{
HRESULT PassString1([in] BSTR s);
HRESULT PassString2([in] BSTR s);
HRESULT PassString3([in] LPStr s);
HRESULT PassString4([in] LPWStr s);
HRESULT PassStringRef1([in, out] BSTR *s);
HRESULT PassStringRef2([in, out] BSTR *s);
HRESULT PassStringRef3([in, out] LPStr *s);
HRESULT PassStringRef4([in, out] LPWStr *s);
};
Chaînes utilisées dans les appels de code non managé
Lorsque le charset est Unicode ou qu’un argument de chaîne est explicitement marqué comme [MarshalAs(UnmanagedType.LPWSTR)] et que la chaîne est passée en valeur (pas ref
ou out
), la chaîne est épinglée et utilisée directement par le code natif. Sinon, l’appel de code non managé copie des arguments de chaîne, en convertissant du format .NET Framework (Unicode) au format non managé de la plateforme. Les chaînes sont immuables et ne sont pas copiées depuis la mémoire non managée vers la mémoire managée quand l'appel est retourné.
Le code natif n’est responsable de la libération de la mémoire que lorsque la chaîne est passée par référence et qu’il attribue une nouvelle valeur. Sinon, le runtime .NET est propriétaire de la mémoire et la libère après l’appel.
Le tableau suivant répertorie les options de marshaling pour les chaînes quand celles-ci sont marshalées comme un argument de méthode d’un appel de code non managé. L'attribut MarshalAsAttribute fournit plusieurs valeurs d'énumération UnmanagedType pour marshaler les chaînes.
Type d'énumération | Description du format non managé |
---|---|
UnmanagedType.AnsiBStr |
BSTR de style COM avec une longueur prédéfinie et des caractères ANSI. |
UnmanagedType.BStr |
BSTR de style COM avec une longueur prédéfinie et des caractères Unicode. |
UnmanagedType.LPStr (valeur par défaut) |
Pointeur vers un tableau de caractères ANSI terminé par un caractère Null. |
UnmanagedType.LPTStr |
Pointeur vers un tableau de caractères dépendant de la plateforme se terminant par un caractère Null. |
UnmanagedType.LPUTF8Str |
Pointeur vers un tableau de caractères encodés UTF-8 terminé par un caractère null. |
UnmanagedType.LPWStr |
Pointeur vers un tableau de caractères Unicode terminé par un caractère null. |
UnmanagedType.TBStr |
BSTR de style COM de longueur fixe avec des caractères dépendant de la plateforme. |
VBByRefStr |
Valeur qui permet à Visual Basic de changer une chaîne en code non managé et de répercuter les résultats dans du code managé. Cette valeur est prise en charge uniquement pour l'appel de code non managé. Il s’agit de la valeur par défaut dans Visual Basic pour les chaînes ByVal . |
Ce tableau s’applique à String. Pour StringBuilder, les seules options autorisées sont LPStr
, LPTStr
et LPWStr
.
La définition de type suivante montre une utilisation correcte de MarshalAsAttribute
pour les appels de code non managé.
class StringLibAPI
{
[DllImport("StringLib.dll")]
public static extern void PassLPStr([MarshalAs(UnmanagedType.LPStr)] string s);
[DllImport("StringLib.dll")]
public static extern void PassLPWStr([MarshalAs(UnmanagedType.LPWStr)] string s);
[DllImport("StringLib.dll")]
public static extern void PassLPTStr([MarshalAs(UnmanagedType.LPTStr)] string s);
[DllImport("StringLib.dll")]
public static extern void PassLPUTF8Str([MarshalAs(UnmanagedType.LPUTF8Str)] string s);
[DllImport("StringLib.dll")]
public static extern void PassBStr([MarshalAs(UnmanagedType.BStr)] string s);
[DllImport("StringLib.dll")]
public static extern void PassAnsiBStr([MarshalAs(UnmanagedType.AnsiBStr)] string s);
[DllImport("StringLib.dll")]
public static extern void PassTBStr([MarshalAs(UnmanagedType.TBStr)] string s);
}
Class StringLibAPI
Public Declare Auto Sub PassLPStr Lib "StringLib.dll" (
<MarshalAs(UnmanagedType.LPStr)> s As String)
Public Declare Auto Sub PassLPWStr Lib "StringLib.dll" (
<MarshalAs(UnmanagedType.LPWStr)> s As String)
Public Declare Auto Sub PassLPTStr Lib "StringLib.dll" (
<MarshalAs(UnmanagedType.LPTStr)> s As String)
Public Declare Auto Sub PassLPUTF8Str Lib "StringLib.dll" (
<MarshalAs(UnmanagedType.LPUTF8Str)> s As String)
Public Declare Auto Sub PassBStr Lib "StringLib.dll" (
<MarshalAs(UnmanagedType.BStr)> s As String)
Public Declare Auto Sub PassAnsiBStr Lib "StringLib.dll" (
<MarshalAs(UnmanagedType.AnsiBStr)> s As String)
Public Declare Auto Sub PassTBStr Lib "StringLib.dll" (
<MarshalAs(UnmanagedType.TBStr)> s As String)
End Class
Chaînes utilisées dans les structures
Les chaînes sont des membres valides de structures. Toutefois, les mémoires tampons StringBuilder ne sont pas valides dans les structures. Le tableau suivant montre les options de marshaling pour le type de données String lorsque le type est marshalé en tant que champ. L'attribut MarshalAsAttribute fournit plusieurs valeurs d'énumération UnmanagedType pour marshaler les chaînes en tant que champ.
Type d'énumération | Description du format non managé |
---|---|
UnmanagedType.BStr |
BSTR de style COM avec une longueur prédéfinie et des caractères Unicode. |
UnmanagedType.LPStr (valeur par défaut) |
Pointeur vers un tableau de caractères ANSI terminé par un caractère Null. |
UnmanagedType.LPTStr |
Pointeur vers un tableau de caractères dépendant de la plateforme se terminant par un caractère Null. |
UnmanagedType.LPUTF8Str |
Pointeur vers un tableau de caractères encodés UTF-8 terminé par un caractère null. |
UnmanagedType.LPWStr |
Pointeur vers un tableau de caractères Unicode terminé par un caractère null. |
UnmanagedType.ByValTStr |
Tableau de caractères de longueur fixe. Le type du tableau est déterminé par le jeu de caractères de la structure contenante. |
Le type ByValTStr
est utilisé pour les tableaux de caractères inline de longueur fixe qui sont situés dans une structure. D'autres types s'appliquent aux références de chaîne contenues au sein de structures qui contiennent des pointeurs vers des chaînes.
L’argument CharSet
du StructLayoutAttribute qui est appliqué à la structure contenante détermine le format de caractères des chaînes des structures. Les exemples de structures suivants contiennent des références de chaîne et des chaînes inline, ainsi que des caractères ANSI, Unicode et dépendant de la plateforme. La représentation de ces structures dans une bibliothèque de types est illustrée dans l’exemple de code C++ suivant :
struct StringInfoA
{
char * f1;
char f2[256];
};
struct StringInfoW
{
WCHAR * f1;
WCHAR f2[256];
BSTR f3;
};
struct StringInfoT
{
TCHAR * f1;
TCHAR f2[256];
};
L’exemple suivant montre comment utiliser le MarshalAsAttribute pour définir une même structure dans des formats différents.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
struct StringInfoA
{
[MarshalAs(UnmanagedType.LPStr)] public string f1;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] public string f2;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct StringInfoW
{
[MarshalAs(UnmanagedType.LPWStr)] public string f1;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] public string f2;
[MarshalAs(UnmanagedType.BStr)] public string f3;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
struct StringInfoT
{
[MarshalAs(UnmanagedType.LPTStr)] public string f1;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] public string f2;
}
<StructLayout(LayoutKind.Sequential, CharSet := CharSet.Ansi)> _
Structure StringInfoA
<MarshalAs(UnmanagedType.LPStr)> Public f1 As String
<MarshalAs(UnmanagedType.ByValTStr, SizeConst := 256)> _
Public f2 As String
End Structure
<StructLayout(LayoutKind.Sequential, CharSet := CharSet.Unicode)> _
Structure StringInfoW
<MarshalAs(UnmanagedType.LPWStr)> Public f1 As String
<MarshalAs(UnmanagedType.ByValTStr, SizeConst := 256)> _
Public f2 As String
<MarshalAs(UnmanagedType.BStr)> Public f3 As String
End Structure
<StructLayout(LayoutKind.Sequential, CharSet := CharSet.Auto)> _
Structure StringInfoT
<MarshalAs(UnmanagedType.LPTStr)> Public f1 As String
<MarshalAs(UnmanagedType.ByValTStr, SizeConst := 256)> _
Public f2 As String
End Structure
Mémoires tampons de chaînes de longueur fixe
Dans certains cas, une mémoire tampon de caractères de longueur fixe doit être passée à du code non managé pour pouvoir être manipulée. Le fait de passer une chaîne ne fonctionne pas dans ce cas, car l'appelé ne peut pas modifier le contenu de la mémoire tampon passée. Même si la chaîne est passée par référence, il est impossible d'initialiser la mémoire tampon à une taille donnée.
La solution consiste à passer un byte[]
ou char[]
, selon l’encodage attendu, comme argument, plutôt qu’une String. Le tableau, marqué avec [Out]
, peut être déréférencé et modifié par l’appelé à condition qu’il ne dépasse pas la capacité du tableau alloué.
Par exemple, la fonction API Windows GetWindowText
(définie dans winuser.h) requiert que l’appelant passe une mémoire tampon de caractères de longueur fixe dans laquelle la fonction écrit le texte de la fenêtre. L’argument lpString
pointe vers une mémoire tampon allouée par l’appelant de taille nMaxCount
. L'appelant est censé allouer la mémoire tampon et définir l'argument nMaxCount
sur la taille de la mémoire tampon allouée. L’exemple suivant illustre la déclaration de fonction GetWindowText
définie dans winuser.h.
int GetWindowText(
HWND hWnd, // Handle to window or control.
LPTStr lpString, // Text buffer.
int nMaxCount // Maximum number of characters to copy.
);
Un char[]
peut être déréférencé et modifié par l’appelé. L’exemple de code suivant montre comment utiliser ArrayPool<char>
pour préallouer un char[]
.
using System;
using System.Buffers;
using System.Runtime.InteropServices;
internal static class NativeMethods
{
[DllImport("User32.dll", CharSet = CharSet.Unicode)]
public static extern void GetWindowText(IntPtr hWnd, [Out] char[] lpString, int nMaxCount);
}
public class Window
{
internal IntPtr h; // Internal handle to Window.
public string GetText()
{
char[] buffer = ArrayPool<char>.Shared.Rent(256 + 1);
NativeMethods.GetWindowText(h, buffer, buffer.Length);
return new string(buffer);
}
}
Imports System
Imports System.Buffers
Imports System.Runtime.InteropServices
Friend Class NativeMethods
Public Declare Auto Sub GetWindowText Lib "User32.dll" _
(hWnd As IntPtr, <Out> lpString() As Char, nMaxCount As Integer)
End Class
Public Class Window
Friend h As IntPtr ' Friend handle to Window.
Public Function GetText() As String
Dim buffer() As Char = ArrayPool(Of Char).Shared.Rent(256 + 1)
NativeMethods.GetWindowText(h, buffer, buffer.Length)
Return New String(buffer)
End Function
End Class
Une autre solution consiste à passer un StringBuilder comme argument plutôt qu’une String. La mémoire tampon créée lors du marshaling d’un StringBuilder
peut être déréférencée et modifiée par l’appelé, à condition qu’elle ne dépasse pas la capacité du StringBuilder
. Il peut également être initialisé à une longueur fixe. Par exemple, si vous initialisez une mémoire tampon StringBuilder
avec une capacité de N
, le marshaleur fournira une mémoire tampon de (N
+ 1) caractères. Le +1 tient compte du fait que la chaîne non managée possède un terminateur Null, contrairement à StringBuilder
.
Notes
En général, la transmission d’arguments StringBuilder
n’est pas recommandée si vous vous souciez des performances. Pour plus d’informations, consultez Paramètres de chaîne.