在P/Invoke中,固定的字符串应该传递什么?

5
假设这是一个C语言函数:
void do_something(const char* str)

它会将字符串存储在某个地方,以供以后参考。

此外,我在C#中有这个签名来调用这个函数:

[DllImport("NativeLib")]
static extern void do_something(string str);

现在,当我向该方法传递字符串时,我需要做什么:

  • 我需要固定字符串(使用GCHandle.Alloc()),还是 marshaller 会创建一个副本?
  • 如果我确实需要固定它,我应该传递“原始”的字符串(即我传递给GCHandle.Alloc()的字符串)吗?还是我需要传递GCHandle.AddrOfPinnedObject()的返回值?
  • string是这种情况下正确的数据类型(用于do_something)吗?还是我应该使用IntPtr替代?

当你说“它在某个地方存储字符串”时,你是指它复制了字符串,还是只是存储了指针?这对于回答你的问题有很大的区别。如果你只是存储指针,你需要使用GCHandle.Alloc()。否则,编组器会进行临时复制。 - Matthew Watson
它直接存储点(而不是副本)。 - Sebastian Krysmanski
你应该在Interop签名中将字符串参数替换为IntPtrbyte* - CodesInChaos
2个回答

7
通过互操作边界存储指针是一个非常糟糕的想法,尽一切可能修改C代码,使其复制字符串而不是存储指针。
按照受赞同的答案中建议的方式固定字符串是行不通的,PInvoke marshaller必须将字符串转换为char*,并在进行本地调用后释放转换后的字符串,从而使指针无效。 您必须自己进行字符串编组。将参数声明为IntPtr,并使用Marshal.StringToHGlobalAnsi()创建指针值。
或者使用Marshal.StringToCoTaskMemAnsi(),它从COM堆中分配。这就是您真正的问题所在,没有很好的场景来释放字符串。 C代码不能释放字符串,因为它不知道它是如何分配的。 您的C#代码无法释放字符串,因为它不知道C代码何时完成指针的使用。 这就是为什么复制字符串非常重要的原因。 如果您只进行少量调用,则可以忍受内存泄漏。

0

考虑到非托管代码将存储指针而不是复制字符串,我建议您使用GCHandle.Alloc()手动创建字符串的副本并将其作为IntPtr传递。

然后,您将能够管理传递的内存块的生命周期,只有在完成后释放它(通过GCHandle)。


我不清楚你在建议什么。你是想用GCHandle.Alloc固定字符串吗?那是个坏主意。 - CodesInChaos
抱歉,答案不完整。我原本想使用GCHandle.Alloc()来获取字符串的内存,然后使用Marshal.AllocHGlobal()来分配一些全局内存,并使用Marshal.Copy()将字符串复制到其中。然而,Hans建议使用StringToHGlobalAnsi,这显然更好。 - Matthew Watson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接