Marshalling predefinito per le stringhe

Le classi System.String e System.Text.StringBuilder presentano un comportamento di marshalling simile.

Viene eseguito il marshalling delle stringhe come tipo BSTR di tipo COM oppure come stringa con terminazione null (matrice di caratteri che termina con un carattere null). È possibile eseguire il marshalling dei caratteri all'interno della stringa come Unicode (impostazione predefinita nei sistemi Windows) o ANSI.

Stringhe usate nelle interfacce

La tabella seguente illustra le opzioni di marshalling per il tipo di dati stringa quando il marshalling viene effettuato come argomento di metodo in codice non gestito. L'attributo MarshalAsAttribute fornisce alcuni valori di enumerazione UnmanagedType per il marshalling di stringhe in interfacce COM.

Tipo di enumerazione Descrizione del formato non gestito
UnmanagedType.BStr (predefinito) BSTR di tipo COM con lunghezza fissa e caratteri Unicode.
UnmanagedType.LPStr Puntatore a matrice di caratteri ANSI con terminazione Null.
UnmanagedType.LPWStr Puntatore a una matrice con terminazione Null di caratteri Unicode.

Questa tabella è applicabile alla String. Per StringBuilder, le uniche opzioni consentite sono UnmanagedType.LPStr e UnmanagedType.LPWStr.

L'esempio seguente mostra stringhe dichiarate nell'interfaccia 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'esempio seguente mostra l'interfaccia corrispondente descritta in una libreria dei tipi.

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);
};

Stringhe usate in platform invoke

Quando il set di caratteri è Unicode o un argomento stringa è contrassegnato in modo esplicito come [MarshalAs(UnmanagedType.LPWSTR)] e la stringa viene passata per valore (non ref o out), la stringa verrà bloccata e usata direttamente dal codice nativo. In caso contrario, platform invoke copia gli argomenti stringa, effettuando la conversione dal formato .NET Framework (Unicode) al formato della piattaforma non gestita. Le stringhe non sono modificabili e non vengono copiate di nuovo dalla memoria non gestita alla memoria gestita quando la chiamata restituisce un risultato.

Il codice nativo è responsabile solo del rilascio della memoria quando la stringa viene passata per riferimento e assegna un nuovo valore. In caso contrario, il runtime .NET è proprietario della memoria e la rilascia dopo la chiamata.

La tabella seguente elenca le opzioni di marshalling per le stringhe quando il marshalling viene effettuato come argomento di metodo di una chiamata platform invoke. L'attributo MarshalAsAttribute fornisce alcuni valori di enumerazione UnmanagedType per il marshalling di stringhe.

Tipo di enumerazione Descrizione del formato non gestito
UnmanagedType.AnsiBStr BSTR di tipo COM con lunghezza fissa e caratteri ANSI.
UnmanagedType.BStr BSTR di tipo COM con lunghezza fissa e caratteri Unicode.
UnmanagedType.LPStr (predefinito) Puntatore a matrice di caratteri ANSI con terminazione Null.
UnmanagedType.LPTStr Puntatore a una matrice con terminazione Null di caratteri dipendenti dalla piattaforma.
UnmanagedType.LPUTF8Str Puntatore a una matrice con terminazione Null di caratteri UTF-8 codificati.
UnmanagedType.LPWStr Puntatore a una matrice con terminazione Null di caratteri Unicode.
UnmanagedType.TBStr BSTR di tipo COM con lunghezza fissa e caratteri dipendenti dalla piattaforma.
VBByRefStr Valore che consente a Visual Basic di modificare una stringa in codice non gestito e riflettere i risultati in codice gestito. Questo valore è supportato solo per platform invoke. Si tratta del valore predefinito in Visual Basic per le stringhe ByVal.

Questa tabella è applicabile alla String. Per StringBuilder, le uniche opzioni consentite sono LPStr, LPTStr e LPWStr.

La definizione del tipo seguente mostra l'uso corretto di MarshalAsAttribute per chiamate Platform invoke.

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

Stringhe usate nelle strutture

Le stringhe sono membri validi delle strutture, ma i buffer StringBuilder non sono validi nelle strutture. La tabella seguente illustra le opzioni di marshalling per il tipo di dati String quando viene eseguito il marshalling del tipo come campo. L'attributo MarshalAsAttribute fornisce alcuni valori di enumerazione UnmanagedType per il marshalling di stringhe in un campo.

Tipo di enumerazione Descrizione del formato non gestito
UnmanagedType.BStr BSTR di tipo COM con lunghezza fissa e caratteri Unicode.
UnmanagedType.LPStr (predefinito) Puntatore a matrice di caratteri ANSI con terminazione Null.
UnmanagedType.LPTStr Puntatore a una matrice con terminazione Null di caratteri dipendenti dalla piattaforma.
UnmanagedType.LPUTF8Str Puntatore a una matrice con terminazione Null di caratteri UTF-8 codificati.
UnmanagedType.LPWStr Puntatore a una matrice con terminazione Null di caratteri Unicode.
UnmanagedType.ByValTStr Matrice di caratteri a lunghezza fissa. Il tipo della matrice viene determinato dal set di carattere della struttura che la contiene.

Il tipo ByValTStr viene usato per matrici di caratteri inline e di lunghezza fissa, disponibili all'interno di una struttura. Altri tipi sono applicabili a riferimenti di stringa contenuti in strutture che includono puntatori ad altre stringhe.

L'argomento CharSet di StructLayoutAttribute applicato alla struttura contenitore determina il formato dei caratteri delle stringhe nelle strutture. Le strutture di esempio seguenti contengono riferimenti a stringhe e stringhe inline, oltre a caratteri ANSI, Unicode e dipendenti dalla piattaforma. La rappresentazione di tali strutture in una libreria dei tipi è illustrata nel codice C++ seguente:

struct StringInfoA
{
    char *  f1;
    char    f2[256];
};

struct StringInfoW
{
    WCHAR * f1;
    WCHAR   f2[256];
    BSTR    f3;
};

struct StringInfoT
{
    TCHAR * f1;
    TCHAR   f2[256];
};

L'esempio seguente illustra come usare MarshalAsAttribute per definire la stessa struttura in formati diversi.

[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

Buffer di stringhe di lunghezza fissa

In alcuni casi, è necessario passare un buffer di caratteri a lunghezza fissa nel codice non gestito da modificare. Il semplice passaggio di una stringa non funziona in questo caso perché il chiamato non può modificare il contenuto del buffer passato. Anche se la stringa viene passata per riferimento, non è possibile inizializzare il buffer su una dimensione specifica.

La soluzione consiste nel passare byte[] o char[], a seconda della codifica prevista, come argomento anziché String. Il chiamato può dereferenziare e modificare la matrice, se contrassegnata con [Out], purché non venga superata la capacità della matrice allocata.

Ad esempio, la funzione API GetWindowTextdi Windows (definita in winuser. h) richiede che il chiamante trasmetta un buffer di caratteri a lunghezza fissa in cui la funzione scrive il testo della finestra. L'argomento lpString punta a un buffer allocato dal chiamante di dimensione nMaxCount. Il chiamante deve allocare il buffer e impostare l'argomento nMaxCount sulla dimensione del buffer allocato. L'esempio seguente illustra la dichiarazione della funzione GetWindowText come definita in winuser.h.

int GetWindowText(
    HWND hWnd,        // Handle to window or control.
    LPTStr lpString,  // Text buffer.
    int nMaxCount     // Maximum number of characters to copy.
);

Il chiamato può dereferenziare e modificare un char[]. L'esempio di codice seguente dimostra come usare ArrayPool<char> per pre-allocare 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

Un'altra soluzione consiste nel passare un StringBuilder come argomento invece di una String. Il chiamato può dereferenziare e modificare il buffer creato quando si effettua il marshalling di StringBuilder, purché non venga superata la capacità di StringBuilder. È anche possibile inizializzare questo oggetto in base a una lunghezza fissa. Ad esempio, se si inizializza un buffer StringBuilder per una capacità pari a N, il gestore di marshalling fornisce un buffer di caratteri di dimensione (N+1). Il valore +1 tiene conto del fatto che, a differenza di StringBuilder, la stringa non gestita ha una terminazione Null.

Nota

In generale, il passaggio di argomenti StringBuilder non è consigliato se si è preoccupati per le prestazioni. Per altre informazioni, vedere Parametri stringa.

Vedi anche