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 排版

相关推荐
小码编匠3 小时前
一款 C# 编写的神经网络计算图框架
后端·神经网络·c#
Envyᥫᩣ6 小时前
C#语言:从入门到精通
开发语言·c#
IT技术分享社区12 小时前
C#实战:使用腾讯云识别服务轻松提取火车票信息
开发语言·c#·云计算·腾讯云·共识算法
△曉風殘月〆19 小时前
WPF MVVM入门系列教程(二、依赖属性)
c#·wpf·mvvm
逐·風21 小时前
unity关于自定义渲染、内存管理、性能调优、复杂物理模拟、并行计算以及插件开发
前端·unity·c#
m0_656974741 天前
C#中的集合类及其使用
开发语言·c#
九鼎科技-Leo1 天前
了解 .NET 运行时与 .NET 框架:基础概念与相互关系
windows·c#·.net
九鼎科技-Leo1 天前
什么是 ASP.NET Core?与 ASP.NET MVC 有什么区别?
windows·后端·c#·asp.net·mvc·.net
.net开发1 天前
WPF怎么通过RestSharp向后端发请求
前端·c#·.net·wpf