C#导出DLL供Delphi调用:Marshal内存释放及字符串填充疑问
嘿,关于你在C#导出DLL给Delphi调用时,Marshal.StringToHGlobalUni和Marshal.FreeHGlobal的使用疑问,我来帮你理清楚核心逻辑——跨语言调用的关键永远是明确内存所有权:谁分配,谁释放。
先拆解你的当前场景
你提到现在是让Delphi传入LPWSTR类型的宽字符串指针,由C#填充内容。这里首先要分两种情况来看:
情况1:Delphi预先分配好缓冲区(推荐方案)
如果这个LPWSTR指针指向的是Delphi已经分配好的足够大的内存缓冲区(比如Delphi里用SetLength给WideString分配了空间,再传指针),那你根本不需要用Marshal.StringToHGlobalUni!
这种场景下,C#只需要把字符串内容复制到Delphi提供的缓冲区里就行,内存所有权完全在Delphi那边,自然由Delphi负责释放,C#这边不用做任何Free操作。举个代码例子:
[DllExport] public static void FillMessage(IntPtr lpBuffer, int bufferSize) { string targetMessage = "你要填充的特定消息"; // 确保不超出缓冲区大小,要给字符串终止符留位置 int maxCharCount = Math.Min(targetMessage.Length, bufferSize - 1); // 把字符串内容复制到Delphi的缓冲区 Marshal.Copy(targetMessage.ToCharArray(), 0, lpBuffer, maxCharCount); // 手动添加宽字符串的终止符(必须加,否则Delphi读取会出问题) Marshal.WriteInt16(lpBuffer, maxCharCount * 2, 0); }
这种方式最安全,完全避免了跨语言内存释放的矛盾,也是跨语言调用里的常规做法。
情况2:你用Marshal.StringToHGlobalUni分配内存给Delphi用
如果你的逻辑是C#分配内存(比如用Marshal.StringToHGlobalUni把托管字符串转成非托管指针),再把这个指针交给Delphi,那就要特别注意:
Marshal.StringToHGlobalUni是在非托管堆上分配的内存,只有调用Marshal.FreeHGlobal才能释放它。- 绝对不能在C#的填充函数里直接调用
FreeHGlobal,否则Delphi拿到的就是野指针,必然崩溃。
这种场景下有两种合规的处理方式:
给Delphi提供专门的释放函数
额外导出一个函数让Delphi在使用完字符串后调用,由C#负责释放内存:[DllExport] public static void FreeAllocatedString(IntPtr strPtr) { if (strPtr != IntPtr.Zero) { Marshal.FreeHGlobal(strPtr); } }Delphi那边拿到指针后,必须在使用完成后调用这个
FreeAllocatedString,否则会造成内存泄漏。用你的IDisposable类管理内存(有风险,需谨慎)
如果你打算在实现IDisposable的类里释放这些内存,一定要注意:必须确保Delphi在C#类实例被Dispose之前,已经不再使用这块内存。
比如在类的Dispose方法里释放所有通过StringToHGlobalUni分配的指针,但如果Delphi在Dispose之后还访问该指针,就会触发访问违规错误。这种方式只适合Delphi的使用周期完全依附于C#类实例生命周期的场景。
最后给你个最佳实践建议
优先选择让Delphi分配缓冲区,C#只负责写入的方案,这种方式内存所有权清晰,不会出现跨语言释放的混乱问题。如果确实需要C#分配内存,一定要和Delphi端明确约定释放规则,避免内存泄漏或野指针问题。
内容的提问来源于stack exchange,提问作者user1808478

