值
规则 ID
CA1838
类别
“性能”
修复是中断修复还是非中断修复
非中断
原因
P/Invoke 具有一个 StringBuilder 参数。
规则说明
StringBuilder 的封送处理总是会创建一个本机缓冲区副本,这导致一个 P/Invoke 调用出现多次分配。 若要将 StringBuilder 作为 P/Invoke 参数进行封送,运行时将:
分配本机缓冲区。
如果是 In 参数,请将 StringBuilder 的内容复制到本机缓冲区。
如果是 Out 参数,请将本机缓冲区复制到新分配的托管数组中。
默认情况下,StringBuilder 为 In 和 Out。
此规则在默认情况下为禁用状态,因为它可能需要根据具体情况分析冲突是否值得关注,以及是否可能需要进行重大重构来解决冲突。 用户可通过配置其严重性来显式启用此规则。
如何解决冲突
通常情况下,解决冲突涉及到重新处理 P/Invoke 及其调用方以使用缓冲区而不是 StringBuilder。 具体情况取决于 P/Invoke 的用例。
下面是使用 StringBuilder 作为要由本机函数填充的输出缓冲区的常见方案示例:
// 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();
}
对于缓冲区较小且可接受 unsafe 代码的用例,可以使用 stackalloc 在堆栈上分配缓冲区:
[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);
}
}
对于大型缓冲区,可以分配新数组作为缓冲区:
[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);
}
如果频繁调用 P/Invoke 以获取大型缓冲区,可使用 ArrayPool<T> 避免随之而来的重复分配和内存压力:
[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);
}
}
如果缓冲区大小在运行时之前是未知的,则可能需要根据大小以不同的方式创建缓冲区,以避免使用 stackalloc 分配大型缓冲区。
前面的示例使用 2 个字节宽的字符 (CharSet.Unicode)。 如果本机函数使用单字节字符 (CharSet.Ansi),可使用 byte 缓冲区而不是 char 缓冲区。 例如:
[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);
}
}
如果参数还用作输入,则需要使用显示添加了任何 NULL 终止符的字符串数据来填充缓冲区。
何时禁止显示警告
如果你不关心封送 StringBuilder 造成的性能影响,可禁止显示此规则的冲突警告。
另请参阅
性能规则
本机互操作性最佳做法
ArrayPool<T>
stackalloc
字符集
本文系外文翻译,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文系外文翻译,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。