Share via


CA1838: Parameters voor P/Invokes vermijden StringBuilder

Eigenschappen Weergegeven als
Regel-id CA1838
Titel Parameters voor P/Invokes vermijden StringBuilder
Categorie Prestaties
Oplossing is brekend of niet-brekend Niet-brekend
Standaard ingeschakeld in .NET 8 Nee

Oorzaak

Een P/Invoke heeft een StringBuilder parameter.

Beschrijving van regel

Marshalling van StringBuilder maakt altijd een systeemeigen bufferkopie, wat resulteert in meerdere toewijzingen voor één P/Invoke-aanroep. De runtime doet het volgende om een marshal a StringBuilder as a P/Invoke-parameter te gebruiken:

  • Wijs een systeemeigen buffer toe.
  • Als het een In parameter is, kopieert u de inhoud van de StringBuilder naar de systeemeigen buffer.
  • Als het een Out parameter is, kopieert u de systeemeigen buffer naar een zojuist toegewezen beheerde matrix.

StringBuilder Standaard is In en Out.

Zie Standaard marshalling voor tekenreeksen voor meer informatie over marshallreeksen.

Deze regel is standaard uitgeschakeld, omdat hiervoor een case-by-case-analyse kan worden vereist van het feit of de schending van belang is en mogelijk niet-triviale herstructurering om de schending aan te pakken. Gebruikers kunnen deze regel expliciet inschakelen door de ernst ervan te configureren.

Schendingen oplossen

Over het algemeen omvat het aanpakken van een schending het opnieuw bewerken van de P/Invoke en de bellers om een buffer te gebruiken in plaats van StringBuilder. De details zijn afhankelijk van de gebruiksvoorbeelden voor de P/Invoke.

Hier volgt een voorbeeld voor het algemene scenario van het gebruik StringBuilder als uitvoerbuffer die moet worden gevuld door de systeemeigen functie:

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

Voor gebruiksscenario's waarbij de buffer klein is en unsafe code acceptabel is, kan stackalloc worden gebruikt om de buffer toe te wijzen aan de stack:

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

Voor grotere buffers kan een nieuwe matrix als buffer worden toegewezen:

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

Wanneer de P/Invoke vaak wordt aangeroepen voor grotere buffers, ArrayPool<T> kan worden gebruikt om de herhaalde toewijzingen en geheugendruk te voorkomen die bij deze buffers hoort:

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

Als de buffergrootte pas bekend is tijdens de runtime, moet de buffer mogelijk anders worden gemaakt op basis van de grootte om te voorkomen dat grote buffers worden toe te wijzen.stackalloc

In de voorgaande voorbeelden worden 2 bytebrede tekens (CharSet.Unicode) gebruikt. Als de systeemeigen functie 1-bytetekens (CharSet.Ansi) gebruikt, kan een byte buffer worden gebruikt in plaats van een char buffer. Bijvoorbeeld:

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

Als de parameter ook wordt gebruikt als invoer, moeten de buffers worden gevuld met de tekenreeksgegevens met een null-afsluiter die expliciet is toegevoegd.

Wanneer waarschuwingen onderdrukken

Onderdrukt een schending van deze regel als u zich geen zorgen maakt over de invloed op de prestaties van het marshallen van een StringBuilder.

Een waarschuwing onderdrukken

Als u slechts één schending wilt onderdrukken, voegt u preprocessorrichtlijnen toe aan uw bronbestand om de regel uit te schakelen en vervolgens opnieuw in te schakelen.

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

Als u de regel voor een bestand, map of project wilt uitschakelen, stelt u de ernst none ervan in op het configuratiebestand.

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

Zie Codeanalysewaarschuwingen onderdrukken voor meer informatie.

Zie ook