Unable to read memory? C++内存怎么不安全的

背景:周三同事跟我说在客户现场发生一件很奇怪的事情,我们的程序(C#)在执行调用客户C++代码模块之后,只要循环调用100次,程序必然崩溃,而且无法try-catch异常,Event Log也没什么有效的提示。这个C++方法功能也很简单:就是C#传入一组数据,C++计算,然后返回结果。C++程序之前被其他语言调用过,从未出现过问题。

一开始我没太在意,只是建议检查一下传入/传出参数,和内存释放相关代码应该就可以解决,再不然就添加异常捕获代码,看下真正异常是什么。

第二天(周四),比往常早出门,还想着应该是第一个到公司,谁知刚进门就看到昨天问问题的同事已然在座位上,过去一问,问题还是没有解决。

努力过的方向:

  1. 由于不是每次都会出现异常,所以添加了log,记录每次执行步骤,然后用于判断出现异常代码行。失败:因为每次异常位置都不一样
  2. 尝试更改 DllImport 参数。失败,异常依然有
  3. 尝试使用 HandleProcessCorruptedStateExceptionsAttribute 捕获异常,期待异常信息能给一点提示。失败:并没什么异常信息

C# 代码:

csharp 复制代码
// 输入:input_1,input_2
// 输出:output/err_1/err_2
[DllImport("C_plus_plus.dll", EntryPoint = "2a64cf1c-a94c-43b0-942d-870fa679bfe7")]
public extern static byte Calc_By_C_plus_plus(IntPtr output, IntPtr err_1, IntPtr err_2, IntPtr input_1, IntPtr input_2);    

// 调用
var gc_output = GCHandle.Alloc(new double[10], GCHandleType.Pinned);
var output_ptr = gc_output.AddrOfPinnedObject();     
// 输出数组,长度==1?? 
var gc_err_1 = GCHandle.Alloc(new double[1], GCHandleType.Pinned);
var err_1_ptr = gc_err_1.AddrOfPinnedObject();  
var gc_err_2 = GCHandle.Alloc(new double[1], GCHandleType.Pinned);
var err_2_ptr = gc_err_2.AddrOfPinnedObject();  
// ...
var gc_input_1 = GCHandle.Alloc(input_1, GCHandleType.Pinned);
var input_1_ptr = gc_output.AddrOfPinnedObject();  
try
{
 int result = Calc_By_C_plus_plus(output_ptr, err_1_ptr, err_2_ptr, input_1_ptr, input_1_p);
 var s_1_o = new int[10]
 for (int i = 0; i < 10; i++)
 {
  s_1_o[i] = Marshal.ReadInt32(step_1_out_ptr, 4 * i);
 }
}
finally
{
 gc_output.Free();
 gc_err_1.Free();
 gc_err_2.Free();
 // ...
 gc_input_1.Free();
}

C++Header

C++ 复制代码
CALC_API signed char Calc_Step_1(int *ouput, double *err1, double *err2, double *input_1, double *input_2);

C#代码看上去并没什么问题,输入两个double类型数组(input_1,intput_2),计算之后返回一个 int数组(output)和两个double类型数组(err_1,err_2),看到输出err数组长度都是1,感觉比较奇怪,但客户说其他语言调用就是这么定义的没什么问题。只能写个Cnosole程序Debug一下了。

Console程序运行起来之后执行一次调用,没出现异常,计算结果也正确。于是添加for 100次循环,直接运行,竟然出现了 IndexOutOfRangeException!

怀疑是内存释放问题,但是 IntPtr没办法提供更多内存信息。于是改了一下 C# 代码,使用数组直接传参

csharp 复制代码
// [In]/[Out]
[DllImport("C_plus_plus.dll", EntryPoint = "2a64cf1c-a94c-43b0-942d-870fa679bfe7")]
public extern static int Calc_By_C_plus_plus([Out] int[] result, [Out] double[] err_1, [Out] double[] err_2, [In] double[] src_1, [In] double[] src_2)       
       
 // 调用
 public static int Calc(int[] output, double[] input_1, double[] input_2)
 {
     var calc_output = new int[10];
     var err1 = new double[1];
     var err2 = new double[1];

     var result = Calc_By_C_plus_plus(calc_output, err1, err2, input_1, input_2);
     Array.Copy(step1out, step_1_out, step_1_out.Length);
     return result;
 } 

这次异常信息变成了:ExecutionEngineException,多次执行,每次出现的位置都不一样,奇了怪了,更奇怪的是异常还会出现在数组定义的地方,肯定是内存问题没错了!

开始单步调试,发现执行C++代码之后,鼠标移到 err2 数组时竟然没法预览,添加到 watch 窗口之后在 value 列显示 'Unable to read memory'

开始有点意思了,为什么是 err2,而不是其他变量呢,再次检查代码,发现输入数组(input_1,input_2),输出数组(output)的长度(Array.Length)都是10,而 err1/err2 数组长度却是 1,难道是传入数组 err1 数组长度不够,C++代码写到 err2 数组里面了,导致 err2 数组没法读取?

将 err1/err2 数组长度改为10,单步调试。果然,这次 err2 可以正常读取,之前判断是对的!对C++内存不安全有了切身的体会。

将循环次数改为一百万次,五分钟之后程序正常结束,问题就此解决!

本文使用 markdown.com.cn 排版

相关推荐
“抚琴”的人18 小时前
【机械视觉】C#+VisionPro联合编程———【六、visionPro连接工业相机设备】
c#·工业相机·visionpro·机械视觉
FAREWELL0007520 小时前
C#核心学习(七)面向对象--封装(6)C#中的拓展方法与运算符重载: 让代码更“聪明”的魔法
学习·c#·面向对象·运算符重载·oop·拓展方法
CodeCraft Studio20 小时前
Excel处理控件Spire.XLS系列教程:C# 合并、或取消合并 Excel 单元格
前端·c#·excel
勘察加熊人1 天前
forms实现连连看
c#
hvinsion1 天前
PPT助手:一款集计时、远程控制与多屏切换于一身的PPT辅助工具
c#·powerpoint·ppt·ppt助手·ppt翻页
weixin_307779131 天前
使用C#实现从Hive的CREATE TABLE语句中提取分区字段名和数据类型
开发语言·数据仓库·hive·c#
时光追逐者1 天前
在 Blazor 中使用 Chart.js 快速创建数据可视化图表
开发语言·javascript·信息可视化·c#·.net·blazor
与火星的孩子对话1 天前
Unity3D开发AI桌面精灵/宠物系列 【三】 语音识别 ASR 技术、语音转文本多平台 - 支持科大讯飞、百度等 C# 开发
人工智能·unity·c#·游戏引擎·语音识别·宠物
response_L1 天前
国产系统统信uos和麒麟v10在线打开word给表格赋值
java·c#·word·信创·在线编辑
MasterNeverDown1 天前
Swagger2Md:让WebAPI文档生成变得轻松高效
c#