C#调用C++ DLL输出char*时内存泄漏问题及正确封送方案咨询
问题根因
- 缓冲区溢出(核心崩溃原因):你在C#中初始化
StringBuilder时没有指定预分配容量,默认初始容量仅为16。C++侧的sprintf不会校验目标缓冲区长度,只要返回的属性值长度超过16,就会直接越界写入,破坏托管堆内存结构,高频调用下很快会触发内存访问错误导致崩溃。 - C++函数存在安全缺陷:现有C++函数没有提供缓冲区长度入参,无法感知外部传入的
oAttValue可用空间大小,天生存在越界写入风险。 - 补充:C侧
MEM_free是C风格内存释放接口,不会抛出C异常,包裹的try-catch逻辑完全无效,属于冗余代码。
临时修复方案(不改动C++ DLL)
先确认业务中返回的属性值最大长度,初始化StringBuilder时指定足够大的容量即可,示例修改后的C#代码如下:
[DllImport("tc_custom_caller.dll", EntryPoint = "get_value_of_field", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] private static extern int get_value_of_field(int iObjectTag, [In, MarshalAs(UnmanagedType.LPStr)]string iAttName, StringBuilder oAttValue); public static void Get_Value_Of_Field(int iObjectTag, string iAttName, out string oAttValue) { oAttValue = ""; // 按业务实际最大长度设置,比如确认最长不超过2048就设2048,不确定可以设为4096 StringBuilder sbValue = new StringBuilder(4096); int retcode = get_value_of_field(iObjectTag, iAttName, sbValue); if (retcode != 0) { throw new Exception("Error in get_value_of_field: " + Get_Error_Text(retcode)); } oAttValue = sbValue.ToString(); }
更合理的封送优化方案(建议改动C++接口)
方案1:增加缓冲区长度校验(最小改动)
修改C++导出函数,新增缓冲区长度入参,改用snprintf做边界检查:
// 修改后的C++函数 int get_value_of_field ( const int iObjectTag, const char iAttName[], char *oAttValue, const int iBufLen) // 新增:传入缓冲区可用长度 { int retcode = 0; char *AttValue = NULL; ITK (tc_custom_methods::get_value_of_field (iObjectTag, iAttName, &AttValue)); if (retcode == 0 && oAttValue != NULL && iBufLen > 0) { // 最多写入iBufLen-1个字符,避免溢出 snprintf(oAttValue, iBufLen, "%s", AttValue); } if (AttValue != NULL) MEM_free(AttValue); return retcode; }
对应修改C#的DllImport定义,传StringBuilder的容量进去即可。
方案2:直接返回BSTR(最省心,无需手动处理内存)
如果允许调整接口返回值,C++侧直接返回BSTR类型,C#侧可以直接接收string,由.NET运行时自动释放内存,完全避免缓冲区溢出问题:
// 修改后的C++导出函数 BSTR __stdcall get_value_of_field (const int iObjectTag, const char iAttName[], int* oRetCode) { *oRetCode = 0; char *AttValue = NULL; ITK (tc_custom_methods::get_value_of_field (iObjectTag, iAttName, &AttValue)); BSTR res = NULL; if (*oRetCode == 0 && AttValue != NULL) { res = SysAllocStringByteLen(AttValue, strlen(AttValue)); } if (AttValue != NULL) MEM_free(AttValue); return res; }
对应C#导入逻辑:
[DllImport("tc_custom_caller.dll", EntryPoint = "get_value_of_field", CharSet = CharSet.Ansi)] [return: MarshalAs(UnmanagedType.BStr)] private static extern string get_value_of_field(int iObjectTag, string iAttName, out int oRetCode); public static string Get_Value_Of_Field(int iObjectTag, string iAttName) { int retCode = 0; string res = get_value_of_field(iObjectTag, iAttName, out retCode); if (retCode != 0) { throw new Exception("Error in get_value_of_field: " + Get_Error_Text(retCode)); } return res; }
内容的提问来源于stack exchange,提问作者kor6k
相关产品推荐
相关产品推荐

