Freigeben über


Standardmarshalling für Zeichenfolgen

Sowohl die System.String-Klassen als auch die System.Text.StringBuilder-Klassen weisen ein ähnliches Marshallverhalten auf.

Zeichenfolgen werden als BSTR-Typ im COM-Format oder als eine auf NULL endende Zeichenfolge (ein Zeichenarray, das mit dem Zeichen NULL endet) gemarshallt. Die Zeichen innerhalb der Zeichenkette können als Unicode (Standard auf Windows-Systemen) oder ANSI gemarshallt werden.

In Schnittstellen verwendete Strings

In der folgenden Tabelle werden die Marshallingoptionen für den Zeichenfolgen-Datentyp aufgelistet, wenn dieser als Methodenargument für nicht verwalteten Code gemarshallt wird. Das MarshalAsAttribute-Attribut stellt mehrere UnmanagedType-Aufzählungswerte für das Marshallen von Zeichenfolgen an COM-Schnittstellen bereit.

Enumerationstyp Beschreibung des nicht verwalteten Formats
UnmanagedType.BStr (Standardwert) Eine COM-Formatvorlage BSTR mit einer präfixierten Länge und Unicode-Zeichen.
UnmanagedType.LPStr Ein Zeiger auf ein mit NULL endendes Array von ANSI-Zeichen.
UnmanagedType.LPWStr Ein Zeiger auf ein mit Null beendetes Array von Unicode-Zeichen.

Diese Tabelle gilt für String. Für StringBuilder, die einzigen zulässigen Optionen sind UnmanagedType.LPStr und UnmanagedType.LPWStr.

Das folgende Beispiel zeigt Zeichenfolgen, die in der IStringWorker Schnittstelle deklariert sind.

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

Das folgende Beispiel zeigt die entsprechende Schnittstelle, die in einer Typbibliothek beschrieben wird.

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

Im Plattformaufruf verwendete Zeichenfolgen

Wenn der CharSet-Wert Unicode ist oder ein Zeichenfolgenargument explizit als [MarshalAs(UnmanagedType.LPWSTR)] gekennzeichnet ist und die Zeichenfolge als Wert (nicht ref oder out) übergeben wird, wird die Zeichenfolge fixiert und direkt durch nativen Code verwendet. Andernfalls werden Zeichenfolgenargumente durch die Plattform aufgerufen und von dem .NET Framework-Format (Unicode) in das nicht verwaltete Plattformformat konvertiert. Zeichenfolgen sind unveränderlich und werden nicht aus nicht verwaltetem Speicher in verwalteten Speicher kopiert, wenn der Aufruf zurückgegeben wird.

Systemeigener Code ist nur für das Freigeben des Speichers verantwortlich, wenn die Zeichenfolge per Verweis übergeben wird und dabei ein neuer Wert zugewiesen wird. Andernfalls besitzt die .NET Laufzeit den Arbeitsspeicher und gibt sie nach dem Aufruf frei.

In der folgenden Tabelle werden die Marshallingoptionen für Zeichenfolgen aufgelistet, wenn die Zeichenfolgen als Methodenargumente eines Plattformaufrufs gemarshallt werden. Das MarshalAsAttribute-Attribut stellt mehrere UnmanagedType-Enumerationswerte zum Marshallen von Zeichenfolgen bereit.

Enumerationstyp Beschreibung des nicht verwalteten Formats
UnmanagedType.AnsiBStr Eine COM-Formatvorlage BSTR mit einer präfixierten Länge und ANSI-Zeichen.
UnmanagedType.BStr Eine COM-Formatvorlage BSTR mit einer präfixierten Länge und Unicode-Zeichen.
UnmanagedType.LPStr (Standardwert) Ein Zeiger auf ein mit NULL endendes Array von ANSI-Zeichen.
UnmanagedType.LPTStr Ein Zeiger auf ein durch Null beendetes Array von plattformabhängigen Zeichen.
UnmanagedType.LPUTF8Str Ein Zeiger auf ein null-beendetes Array mit UTF-8-codierten Zeichen.
UnmanagedType.LPWStr Ein Zeiger auf ein mit Null beendetes Array von Unicode-Zeichen.
UnmanagedType.TBStr Ein COM-Stil BSTR mit vorgegebener Länge und plattformspezifischen Zeichen.
VBByRefStr Ein Wert, der es Visual Basic ermöglicht, eine Zeichenfolge in nicht verwaltetem Code zu ändern und die Ergebnisse in verwaltetem Code widerzuspiegeln. Dieser Wert wird nur für Plattformaufrufe unterstützt. Dies ist der Standardwert in Visual Basic für ByVal Zeichenfolgen.

Diese Tabelle gilt für String. Für StringBuilder, die einzigen zulässigen Optionen sind LPStr, LPTStrund LPWStr.

Die folgende Typdefinition zeigt die richtige Verwendung von MarshalAsAttribute für Plattformaufrufaufrufe.

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

In Strukturen verwendete Zeichenfolgen

Zeichenfolgen sind gültige Elemente von Strukturen; StringBuilder Puffer sind jedoch in Strukturen ungültig. In der folgenden Tabelle werden die Marshalling-Optionen für den String Datentyp angezeigt, wenn der Typ als Feld gemarshallt wird. Das MarshalAsAttribute Attribut stellt mehrere UnmanagedType Aufzählungswerte zum Überführen von Zeichenfolgen zu einem Feld bereit.

Enumerationstyp Beschreibung des nicht verwalteten Formats
UnmanagedType.BStr Eine COM-Formatvorlage BSTR mit einer präfixierten Länge und Unicode-Zeichen.
UnmanagedType.LPStr (Standardwert) Ein Zeiger auf ein mit NULL endendes Array von ANSI-Zeichen.
UnmanagedType.LPTStr Ein Zeiger auf ein durch Null beendetes Array von plattformabhängigen Zeichen.
UnmanagedType.LPUTF8Str Ein Zeiger auf ein null-beendetes Array mit UTF-8-codierten Zeichen.
UnmanagedType.LPWStr Ein Zeiger auf ein mit Null beendetes Array von Unicode-Zeichen.
UnmanagedType.ByValTStr Ein Zeichen-Array mit fester Länge; der Typ des Arrays wird durch den Zeichensatz der Struktur, die es enthält, bestimmt.

Der ByValTStr Typ wird für Inline-Zeichenarrays mit fester Länge verwendet, die in einer Struktur angezeigt werden. Andere Typen gelten für Zeichenfolgenverweise, die in Strukturen enthalten sind, die Zeiger auf Zeichenfolgen enthalten.

Das CharSet Argument des StructLayoutAttribute, das auf die enthaltene Struktur angewendet wird, bestimmt die Zeichenformatierung von Zeichenfolgen in den Strukturen. Die folgenden Beispielstrukturen enthalten Zeichenfolgenverweise und Inlinezeichenfolgen sowie ANSI-, Unicode- und plattformabhängige Zeichen. Die Darstellung dieser Strukturen in einer Typbibliothek wird im folgenden C++-Code gezeigt:

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

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

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

Das folgende Beispiel zeigt, wie man die Struktur mit MarshalAsAttribute in verschiedenen Formaten definiert.

[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

Zeichenfolgenpuffer mit fester Länge

Unter bestimmten Umständen muss ein Zeichenpuffer mit fester Länge an nicht verwalteten Code übergeben werden, um bearbeitet zu werden. Die einfache Übergabe einer Zeichenfolge funktioniert in diesem Fall nicht, da der Angerufene den Inhalt des übergebenen Puffers nicht ändern kann. Auch wenn die Zeichenfolge per Verweis übergeben wird, gibt es keine Möglichkeit, den Puffer mit einer bestimmten Größe zu initialisieren.

Die Lösung besteht darin, byte[] oder char[] je nach erwarteter Codierung zu übergeben, und zwar als Argument anstelle eines String. Das Array kann, wenn es mit [Out] gekennzeichnet ist, vom Angerufenen dereferenziert und geändert werden, vorausgesetzt, es überschreitet nicht die Kapazität des zugewiesenen Arrays.

Die Windows GetWindowText-API-Funktion (definiert in winuser.h) erfordert beispielsweise, dass der Aufrufer einen Puffer mit fester Länge übergibt, in den die Funktion den Text des Fensters schreibt. Das lpString Argument verweist auf einen vom Aufrufer zugewiesenen Puffer der Größe nMaxCount. Der Aufrufer sollte den Puffer zuweisen und das nMaxCount Argument auf die Größe des zugewiesenen Puffers festlegen. Das folgende Beispiel zeigt die GetWindowText Funktionsdeklaration gemäß der Definition in winuser.h.

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

char[] kann vom Aufgerufenen dereferenziert und geändert werden. Der empfohlene Ansatz besteht darin, ArrayPool<T> zu verwenden, um char[] zu mieten, was wiederholte Heap-Zuordnungen vermeidet. Im folgenden Codebeispiel wird dieses Muster veranschaulicht.

using System;
using System.Buffers;
using System.Runtime.InteropServices;

internal static class NativeMethods
{
    [DllImport("User32.dll", CharSet = CharSet.Unicode)]
    public static extern int 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);
        try
        {
            int length = NativeMethods.GetWindowText(h, buffer, buffer.Length);
            return new string(buffer, 0, length);
        }
        finally
        {
            ArrayPool<char>.Shared.Return(buffer);
        }
    }
}
Imports System
Imports System.Buffers
Imports System.Runtime.InteropServices

Friend Class NativeMethods
    Public Declare Auto Function GetWindowText Lib "User32.dll" _
        (hWnd As IntPtr, <Out> lpString() As Char, nMaxCount As Integer) 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)
        Try
            Dim length As Integer = NativeMethods.GetWindowText(h, buffer, buffer.Length)
            Return New String(buffer, 0, length)
        Finally
            ArrayPool(Of Char).Shared.Return(buffer)
        End Try
    End Function
End Class

Sie können auch erwägen, anstelle eines String ein StringBuilder zu verwenden. Der Puffer, der beim StringBuilder marshalling erstellt wird, kann vom Angerufenen dereferenziert und geändert werden, sofern er die Kapazität des StringBuilder nicht überschreitet. Sie kann auch auf eine feste Länge initialisiert werden. Wenn Sie z. B. einen StringBuilder Puffer auf eine Kapazität von N initialisieren, stellt der Marshaller einen Puffer mit der Größe von (N+1) Zeichen bereit. Mit +1 wird berücksichtigt, dass die nicht verwaltete Zeichenfolge einen Null-Terminator aufweist, während StringBuilder dies nicht tut.

Vorsicht

Vermeiden Sie StringBuilder Parameter, wenn die Leistung wichtig ist. Durch Marshalling wird StringBuilder eine systemeigene Pufferkopie erstellt. Ein typischer Aufruf zum Abrufen einer Zeichenfolge aus systemeigenem Code kann zu vier Zuordnungen führen:

  1. Ein verwalteter StringBuilder Puffer.
  2. Ein systemeigener Puffer, der während des Marshallings zugewiesen wird.
  3. Wenn [Out] der native Pufferinhalt in ein neu zugewiesenes verwaltetes Array kopiert wird.
  4. string wird von ToString() zugeordnet.

Durch die Wiederverwendung desselben StringBuilder für Anrufe wird nur eine Zuordnung gespeichert. Die Verwendung eines vom ArrayPool<char> gemieteten Zeichenpuffers ist viel effizienter – sie reduziert nachfolgende Aufrufe auf lediglich die Zuordnung für ToString().

Darüber hinaus enthält die StringBuilder Kapazität keinen ausgeblendeten Null-Terminator, den die Interoperabilität immer berücksichtigt. Dies ist ein häufiger Fehler, da die meisten APIs die Größe des Puffers einschließlich null wünschen. Dies kann zu vergeudeten oder unnötigen Zuweisungen führen und verhindert, dass die Laufzeit StringBuilder das Marshalling optimiert, um Kopien zu minimieren.

Weitere Informationen finden Sie unter Zeichenfolgenparameter und CA1838: Vermeidung von StringBuilder Parametern für P/Invokes.

Siehe auch