C#调用C++ DLL:相同代码在Main与独立方法中结果不一致问题
问题根源与解决方案:C#调用C++ DLL返回栈内存导致的异常
核心问题分析
你的C++ fullname函数返回的是栈上的局部数组result——栈内存会在函数执行完毕后被操作系统回收或复用。
- Main方法中第一次读取时,这段内存刚好还没被覆盖,所以能正常获取结果;
- 调用
StringFromNative方法时,函数调用会修改栈空间,原有的内存数据已经被破坏,因此读取失败。这属于C++中的未定义行为,偶然能运行只是巧合,完全不可靠。
解决方案1:C++返回堆内存(需手动释放)
修改C++代码,使用malloc在堆上分配内存(堆内存不会随函数返回自动释放):
#include <cstdlib> char* fullname(char* firstname, char* lastname) { char* result = (char*)malloc(1000); if (result == nullptr) return nullptr; strcpy_s(result, 1000, firstname); strcat_s(result, 1000, " "); strcat_s(result, 1000, lastname); return result; }
C#端使用后必须手动释放堆内存,避免泄漏:
static void Main(string[] args) { string fn = "Foo"; string ln = "United"; IntPtr nameResult = fullname(Encoding.Default.GetBytes(fn), Encoding.Default.GetBytes(ln)); try { string res = StringFromNative(nameResult); Console.WriteLine(res); } finally { Marshal.FreeHGlobal(nameResult); // 释放C++堆内存 } Console.ReadLine(); } public static string StringFromNative(IntPtr native) { int len = 0; while (Marshal.ReadByte(native, len) != 0) ++len; byte[] buffer = new byte[len]; Marshal.Copy(native, buffer, 0, buffer.Length); return Encoding.Default.GetString(buffer); }
解决方案2:由C#分配内存传入C++(更安全)
让C#负责内存分配,将缓冲区传给C函数,避免跨语言内存管理的混乱:
修改C函数,接收外部传入的缓冲区:
void fullname(char* firstname, char* lastname, char* outputBuffer, size_t bufferSize) { strcpy_s(outputBuffer, bufferSize, firstname); strcat_s(outputBuffer, bufferSize, " "); strcat_s(outputBuffer, bufferSize, lastname); }
C#端的调用代码:
[DllImport("ActivatorDll.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)] public static extern void fullname(byte[] firstname, byte[] lastname, IntPtr outputBuffer, int bufferSize); static void Main(string[] args) { string fn = "Foo"; string ln = "United"; const int bufferSize = 1000; IntPtr outputBuffer = Marshal.AllocHGlobal(bufferSize); try { fullname(Encoding.Default.GetBytes(fn), Encoding.Default.GetBytes(ln), outputBuffer, bufferSize); // 直接用Marshal方法转换,无需手动计算长度 string res = Marshal.PtrToStringAnsi(outputBuffer); Console.WriteLine(res); } finally { Marshal.FreeHGlobal(outputBuffer); // 确保释放内存 } Console.ReadLine(); }
关键注意事项
- 永远不要返回C++函数栈上的局部变量指针,这是严格的未定义行为;
- 跨语言交互时,内存管理必须明确:要么由C分配后C#释放,要么由C#分配后传给C使用;
- 使用
Marshal.PtrToStringAnsi可以替代手动计算字符串长度的代码,简化逻辑。
内容的提问来源于stack exchange,提问作者MatthieuCross
相关产品推荐
相关产品推荐

