前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >CA1838:不要对 P/Invoke 使用 StringBuilder 参数

CA1838:不要对 P/Invoke 使用 StringBuilder 参数

作者头像
用户4268038
发布2022-02-19 18:57:40
4910
发布2022-02-19 18:57:40
举报
文章被收录于专栏:stcnb

规则 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 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档