|
| 1 | +--- |
| 2 | +title: "CA1838: Avoid StringBuilder parameters for P/Invokes" |
| 3 | +ms.date: 08/03/2020 |
| 4 | +ms.topic: reference |
| 5 | +f1_keywords: |
| 6 | + - "AvoidStringBuilderPInvokeParameters" |
| 7 | + - "CA1838" |
| 8 | +helpviewer_keywords: |
| 9 | + - "AvoidStringBuilderPInvokeParameters" |
| 10 | + - "CA1838" |
| 11 | +author: elinor-fung |
| 12 | +ms.author: elfung |
| 13 | +manager: jeffschw |
| 14 | +ms.workload: |
| 15 | + - "multiple" |
| 16 | +--- |
| 17 | + |
| 18 | +# CA1838: Avoid `StringBuilder` parameters for P/Invokes |
| 19 | + |
| 20 | +|Item|Value| |
| 21 | +|-|-| |
| 22 | +|CheckId|CA1838| |
| 23 | +|Category|Microsoft.Performance| |
| 24 | +|Breaking change|Non-breaking| |
| 25 | + |
| 26 | +## Cause |
| 27 | + |
| 28 | +A [P/Invoke](/dotnet/standard/native-interop/pinvoke) has a <xref:System.Text.StringBuilder> parameter. |
| 29 | + |
| 30 | +## Rule description |
| 31 | + |
| 32 | +Marshaling of `StringBuilder` always creates a native buffer copy, resulting in multiple allocations for one P/Invoke call. To marshal a `StringBuilder` as a P/Invoke parameter, the runtime will: |
| 33 | +- Allocate a native buffer |
| 34 | +- If it is an `In` parameter, copy the contents of the `StringBuilder` to the native buffer |
| 35 | +- If it is an `Out` parameter, copy the native buffer into a newly allocated managed array |
| 36 | + |
| 37 | +By default, `StringBuilder` is `In` and `Out`. |
| 38 | + |
| 39 | +This rule is disabled by default, because it can require case-by-case analysis of whether the violation is of interest and potentially non-trivial refactoring to address the violation. Users can explicitly enable this rule by configuring the [severity of analyzer rules](use-roslyn-analyzers.md#rule-severity). |
| 40 | + |
| 41 | +## How to fix violations |
| 42 | + |
| 43 | +In general, addressing a violation involves reworking the P/Invoke and its callers to use a buffer instead of `StringBuilder`. The specifics would depend on the use cases for the P/Invoke. |
| 44 | + |
| 45 | +Here is an example for the common scenario of using `StringBuilder` as an output buffer to be filled by the native function: |
| 46 | + |
| 47 | +```csharp |
| 48 | +// Violation |
| 49 | +[DllImport("MyLibrary", CharSet = CharSet.Unicode)] |
| 50 | +private static extern void Foo(StringBuilder sb, ref int length); |
| 51 | + |
| 52 | +public void Bar() |
| 53 | +{ |
| 54 | + int BufferSize = ... |
| 55 | + StringBuilder sb = new StringBuilder(BufferSize); |
| 56 | + int len = sb.Capacity; |
| 57 | + Foo(sb, ref len); |
| 58 | + string result = sb.ToString(); |
| 59 | +} |
| 60 | +``` |
| 61 | + |
| 62 | +For use cases where the buffer is small and `unsafe` code is acceptable, [stackalloc](/dotnet/csharp/language-reference/operators/stackalloc) can be used to allocate the buffer on the stack: |
| 63 | + |
| 64 | +```csharp |
| 65 | +[DllImport("MyLibrary", CharSet = CharSet.Unicode)] |
| 66 | +private static extern unsafe void Foo(char* buffer, ref int length); |
| 67 | + |
| 68 | +public void Bar() |
| 69 | +{ |
| 70 | + int BufferSize = ... |
| 71 | + unsafe |
| 72 | + { |
| 73 | + char* buffer = stackalloc char[BufferSize]; |
| 74 | + int len = BufferSize; |
| 75 | + Foo(buffer, ref len); |
| 76 | + string result = new string(buffer); |
| 77 | + } |
| 78 | +} |
| 79 | +``` |
| 80 | + |
| 81 | +For larger buffers, a new array can be allocated as the buffer: |
| 82 | + |
| 83 | +```csharp |
| 84 | +[DllImport("MyLibrary", CharSet = CharSet.Unicode)] |
| 85 | +private static extern void Foo([Out] char[] buffer, ref int length); |
| 86 | + |
| 87 | +public void Bar() |
| 88 | +{ |
| 89 | + int BufferSize = ... |
| 90 | + char[] buffer = new char[BufferSize]; |
| 91 | + int len = buffer.Length; |
| 92 | + Foo(buffer, ref len); |
| 93 | + string result = new string(buffer); |
| 94 | +} |
| 95 | +``` |
| 96 | + |
| 97 | +When the P/Invoke is frequently called for larger buffers, <xref:System.Buffers.ArrayPool%601> can be used to avoid the repeated allocations and memory pressure that comes with them: |
| 98 | + |
| 99 | +```csharp |
| 100 | +[DllImport("MyLibrary", CharSet = CharSet.Unicode)] |
| 101 | +private static extern unsafe void Foo([Out] char[] buffer, ref int length); |
| 102 | + |
| 103 | +public void Bar() |
| 104 | +{ |
| 105 | + int BufferSize = ... |
| 106 | + char[] buffer = ArrayPool<char>.Shared.Rent(BufferSize); |
| 107 | + try |
| 108 | + { |
| 109 | + int len = buffer.Length; |
| 110 | + Foo(buffer, ref len); |
| 111 | + string result = new string(buffer); |
| 112 | + } |
| 113 | + finally |
| 114 | + { |
| 115 | + ArrayPool<char>.Shared.Return(buffer); |
| 116 | + } |
| 117 | +} |
| 118 | +``` |
| 119 | + |
| 120 | +If the buffer size is not known until runtime, the buffer may need to be created differently based on the size to avoid allocating large buffers with `stackalloc`. |
| 121 | + |
| 122 | +The preceding examples use 2-byte wide characters (`CharSet.Unicode`). If the native function uses 1-byte characters (`CharSet.Ansi`), a `byte` buffer can be used instead of a `char` buffer. For example: |
| 123 | + |
| 124 | +```csharp |
| 125 | +[DllImport("MyLibrary", CharSet = CharSet.Ansi)] |
| 126 | +private static extern unsafe void Foo(byte* buffer, ref int length); |
| 127 | + |
| 128 | +public void Bar() |
| 129 | +{ |
| 130 | + int BufferSize = ... |
| 131 | + unsafe |
| 132 | + { |
| 133 | + byte* buffer = stackalloc byte[BufferSize]; |
| 134 | + int len = BufferSize; |
| 135 | + Foo(buffer, ref len); |
| 136 | + string result = Marshal.PtrToStringAnsi((IntPtr)buffer); |
| 137 | + } |
| 138 | +} |
| 139 | +``` |
| 140 | + |
| 141 | +If the parameter is also used as input, the buffers need to be populated with the string data with any null terminator explicitly added. |
| 142 | + |
| 143 | +## When to suppress warnings |
| 144 | + |
| 145 | +Suppress a violation of this rule if you're not concerned about the performance impact of marshaling a `StringBuilder`. |
| 146 | + |
| 147 | +## See also |
| 148 | + |
| 149 | +- [Performance warnings](../code-quality/performance-warnings.md) |
| 150 | +- [Native interoperability best practices](/dotnet/standard/native-interop/best-practices) |
| 151 | +- <xref:System.Buffers.ArrayPool%601> |
| 152 | +- [stackalloc](/dotnet/csharp/language-reference/operators/stackalloc) |
| 153 | +- [Charsets](/dotnet/standard/native-interop/charset) |
0 commit comments