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.

Voir aussi