CA1838: StringBuilder-Parameter for P/Invokes vermeiden

Eigenschaft Wert
Regel-ID CA1838
Titel CA1838: StringBuilder-Parameter for P/Invokes vermeiden
Kategorie Leistung
Fix führt oder führt nicht zur Unterbrechung Nicht unterbrechend
Standardmäßig in .NET 8 aktiviert Nein

Ursache

Ein P/Invoke weist einen StringBuilder-Parameter auf.

Regelbeschreibung

Beim Marshalling von StringBuilder wird immer eine native Pufferkopie erstellt, sodass mehrere Zuordnungen für einen P/Invoke-Aufruf vorhanden sind. Um ein StringBuilder als P/Aufruf-Parameter zu marshallen, führt Runtime Folgendes aus:

  • Einen nativen Puffer hinzufügen.
  • Wenn es sich um einen In-Parameter handelt, kopieren Sie den Inhalt des StringBuilder in den nativen Puffer.
  • Wenn es sich um einen Out/Parameter handelt, kopieren Sie den nativen Puffer in ein neu zugeordnetes verwaltetes Array.

StringBuilder ist standardmäßig In und Out.

Weitere Informationen zum Marshalling von Zeichenfolgen finden Sie unter Standardmäßiges Marshalling für Zeichenfolgen.

Diese Regel ist standardmäßig deaktiviert, da Sie eine fallweise Analyse verlangen kann, ob der Verstoß von Interesse ist, und potenziell eine nicht triviale Neufaktorierung, um den Verstoß zu beheben. Benutzer können diese Regel durch Konfigurieren des Schweregradsexplizit aktivieren.

Behandeln von Verstößen

Im allgemeinen umfasst die Behebung einer Verletzung das erneute Arbeiten von P/Invoke und seinen Aufrufern, um einen Puffer anstelle von StringBuilder zu verwenden. Die Besonderheiten sind abhängig von den Anwendungsfällen für P/Invoke.

Im folgenden finden Sie ein Beispiel für das gängige Szenario der Verwendung von StringBuilder als Ausgabepuffer, das von der nativen Funktion aufgefüllt wird:

// Violation
[DllImport("MyLibrary", CharSet = CharSet.Unicode)]
private static extern void Foo(StringBuilder sb, ref int length);

public void Bar()
{
    int BufferSize = ...
    StringBuilder sb = new StringBuilder(BufferSize);
    int len = sb.Capacity;
    Foo(sb, ref len);
    string result = sb.ToString();
}

Für Anwendungsfälle, in denen der Puffer klein ist und unsafe-Code akzeptabel ist, kann stackalloc verwendet werden, um den Puffer im Stapel zuzuordnen:

[DllImport("MyLibrary", CharSet = CharSet.Unicode)]
private static extern unsafe void Foo(char* buffer, ref int length);

public void Bar()
{
    int BufferSize = ...
    unsafe
    {
        char* buffer = stackalloc char[BufferSize];
        int len = BufferSize;
        Foo(buffer, ref len);
        string result = new string(buffer);
    }
}

Bei größeren Puffern kann ein neues Array als Puffer zugeordnet werden:

[DllImport("MyLibrary", CharSet = CharSet.Unicode)]
private static extern void Foo([Out] char[] buffer, ref int length);

public void Bar()
{
    int BufferSize = ...
    char[] buffer = new char[BufferSize];
    int len = buffer.Length;
    Foo(buffer, ref len);
    string result = new string(buffer);
}

Wenn P/Invoke häufig für größere Puffer aufgerufen wird, kann ArrayPool<T> verwendet werden, um die wiederholten Zuordnungen und die Arbeitsspeicherauslastung zu vermeiden, die in den folgenden Fällen entstehen:

[DllImport("MyLibrary", CharSet = CharSet.Unicode)]
private static extern unsafe void Foo([Out] char[] buffer, ref int length);

public void Bar()
{
    int BufferSize = ...
    char[] buffer = ArrayPool<char>.Shared.Rent(BufferSize);
    try
    {
        int len = buffer.Length;
        Foo(buffer, ref len);
        string result = new string(buffer);
    }
    finally
    {
        ArrayPool<char>.Shared.Return(buffer);
    }
}

Wenn die Puffergröße bis zur Runtime nicht bekannt ist, muss der Puffer möglicherweise basierend auf der Größe unterschiedlich erstellt werden, um die Zuordnung großer Puffer mit stackalloc zu vermeiden.

In den vorangehenden Beispielen werden 2-Byte breit eZeichen (CharSet.Unicode) verwendet. Wenn die native Funktion 1-Byte-Zeichen (CharSet.Ansi) verwendet, kann ein byte-Puffer anstelle eines char-Puffers verwendet werden. Beispiel:

[DllImport("MyLibrary", CharSet = CharSet.Ansi)]
private static extern unsafe void Foo(byte* buffer, ref int length);

public void Bar()
{
    int BufferSize = ...
    unsafe
    {
        byte* buffer = stackalloc byte[BufferSize];
        int len = BufferSize;
        Foo(buffer, ref len);
        string result = Marshal.PtrToStringAnsi((IntPtr)buffer);
    }
}

Wenn der Parameter auch als Eingabe verwendet wird, müssen die Puffer mit den Zeichenfolgen-Daten aufgefüllt werden, wobei ein beliebiger Null-Terminator explizit hinzugefügt wird.

Wann sollten Warnungen unterdrückt werden?

Unterdrücken Sie einen Verstoß gegen diese Regel, wenn Leistungseinbußen durch das Marshalling von StringBuilder keine Rolle spielen.

Unterdrücken einer Warnung

Um nur eine einzelne Verletzung zu unterdrücken, fügen Sie der Quelldatei Präprozessoranweisungen hinzu, um die Regel zu deaktivieren und dann wieder zu aktivieren.

#pragma warning disable CA1838
// The code that's violating the rule is on this line.
#pragma warning restore CA1838

Um die Regel für eine Datei, einen Ordner oder ein Projekt zu deaktivieren, legen Sie den Schweregrad in der Konfigurationsdatei auf none fest.

[*.{cs,vb}]
dotnet_diagnostic.CA1838.severity = none

Weitere Informationen finden Sie unter Vorgehensweise: Unterdrücken von Codeanalyse-Warnungen.

Siehe auch