Avoid StringBuilder parameters for P/Invokes
Avoid StringBuilder parameters for P/Invokes
Microsoft docsDescription
Marshalling 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:
- Allocate a native buffer.
- If it is an
Inparameter, copy the contents of theStringBuilderto the native buffer. - If it is an
Outparameter, copy the native buffer into a newly allocated managed array.
By default, StringBuilder is In and Out.
For more information about marshalling strings, see Default marshalling for strings.
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 its severity.
Cause
A P/Invoke has a System.Text.StringBuilder parameter.
How to fix violations
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.
Here is an example for the common scenario of using StringBuilder as an output buffer to be filled by the native function:
For use cases where the buffer is small and unsafe code is acceptable, stackalloc can be used to allocate the buffer on the stack:
For larger buffers, a new array can be allocated as the buffer:
When the P/Invoke is frequently called for larger buffers, System.Buffers.ArrayPool`1 can be used to avoid the repeated allocations and memory pressure that comes with them:
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.
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:
If the parameter is also used as input, the buffers need to be populated with the string data with any null terminator explicitly added.
Example
// 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();
}When to suppress
Suppress a violation of this rule if you're not concerned about the performance impact of marshalling a StringBuilder.