使用 P/Invoke 时设置 StringBuilder.Capacity 的正确方法是啥?

Posted

技术标签:

【中文标题】使用 P/Invoke 时设置 StringBuilder.Capacity 的正确方法是啥?【英文标题】:What is the correct way to set StringBuilder.Capacity when using P/Invoke?使用 P/Invoke 时设置 StringBuilder.Capacity 的正确方法是什么? 【发布时间】:2010-10-30 02:33:46 【问题描述】:

应将StringBuilder.Capacity 设置为最大的 .NET 字符数,而不考虑空终止符,或者在使用 P/Invoke 时必须将其设置为高一以为空终止符保留空间。

自然的反应是它应该设置得更高,但似乎 P/Invoke 应该自动补偿。事实上,这实际上记录在这里:http://msdn.microsoft.com/en-US/library/s9ts558h(v=VS.100).aspx

这个问题的原因是大多数示例与上述文档并不严格一致。它们几乎总是被编码:

StringBuilder sb = new StringBuilder(dotNetChars + 1);
SomeWindowsAPI(sb, sb.Capacity);

代替:

StringBuilder sb = new StringBuilder(dotNetChars);
SomeWindowsAPI(sb, sb.Capacity + 1);

(我意识到某些 API 处理缓冲区大小参数的方式不同。假设 API 以必须通用的方式处理此问题,例如 GetFullPathName:http://msdn.microsoft.com/en-us/library/aa364963(v=VS.85).aspx)

直接在 API 调用中使用带有sb.Capacity 的表达式似乎是避免不匹配的最佳做法。问题是添加+1是否正确。

环顾四周。您可能会发现唯一显示 sb.Capacity + 1 的地方是 MSDN 文档。

当然,谨慎起见,可以分配比绝对必要更大的缓冲区,但我想知道如何做到这一点的共识。

【问题讨论】:

【参考方案1】:

我知道你已经有了五年前的答案,但在我看来,他们并没有真正回答这个问题,他们基本上是在不检查这样做是否正确的情况下挥手解决了潜在的问题。

MSDN 文档保证编组器将确保有足够的空间来存储StringBuilder 的完整Capacity 以及一个额外的空终止符。引用Default Marshaling for Strings:

固定长度的字符串缓冲区

[...]

解决方案是将StringBuilder 缓冲区作为参数而不是字符串传递。 StringBuilder 可以由被调用者取消引用和修改,前提是它不超过 StringBuilder 的容量。它也可以初始化为固定长度。例如,如果您将 StringBuilder 缓冲区初始化为 N 的容量,则封送拆收器将提供大小为 (N+1) 个字符的缓冲区。 +1 说明了非托管字符串有一个空终止符,而 StringBuilder 没有。

[...]

所以您不必担心向容量添加一个,编组器已经为您完成了。

【讨论】:

【参考方案2】:

StringBuilder的构造函数内部,容量是这样使用的:

m_ChunkChars = new char[capacity];

这与m_ChunkLength 字段一起用于确定StringBuilder 的内容。这仅描述实际字符,不包括终止字符。

所以你的答案是+ 1 是没有必要的。

【讨论】:

但是 P/Invoke 对此有何作用?如果 P/Invoke 只是将该数组中第一个字符的地址传递给外部函数,则该数组 1 对于空终止符来说太短了。 字符串被编组以供 pinvoke 和诸如添加 \0 之类的事情由框架自动完成。我相信在这种情况下,您不必考虑到这一点。【参考方案3】:

可能正在进行一定数量的货物崇拜节目。当被问及为字符串分配的内存时,有人养成了回答“长度+1”而不是长度的习惯。与其阅读文档以了解新情况下询问您为字符串分配的内存,开发人员只是“在安全方面犯错”并传递长度 + 1。阅读该代码的其他人推断出相同的规则和实践传播。在人与人之间传播不一定是正确的,只要有合理的可能是正确的,并且不会造成有害的。

【讨论】:

以上是关于使用 P/Invoke 时设置 StringBuilder.Capacity 的正确方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

使用 P/Invoke 调用 dll 时,为啥 LoadLibrary 在某些机器上会失败?

在通过 P/Invoke 获得的 C++ 结构上设置 C# 回调

使用 P/Invoke 会导致系统 AccessViolationException [重复]

如何使用 p/invoke 终止从 C# 调用的 C++ 函数

P/Invoke 是不是执行 DLL 然后将其关闭?

在 C# 中使用 P/Invoke 注册 _set_purecall_handler 函数