背景:周三同事跟我说在客户现场发生一件很奇怪的事情,我们的程序(C#)在执行调用客户C++代码模块之后,只要循环调用100次,程序必然崩溃,而且无法try-catch异常,Event Log也没什么有效的提示。这个C++方法功能也很简单:就是C#传入一组数据,C++计算,然后返回结果。C++程序之前被其他语言调用过,从未出现过问题。
一开始我没太在意,只是建议检查一下传入/传出参数,和内存释放相关代码应该就可以解决,再不然就添加异常捕获代码,看下真正异常是什么。
第二天(周四),比往常早出门,还想着应该是第一个到公司,谁知刚进门就看到昨天问问题的同事已然在座位上,过去一问,问题还是没有解决。
努力过的方向:
- 由于不是每次都会出现异常,所以添加了log,记录每次执行步骤,然后用于判断出现异常代码行。失败:因为每次异常位置都不一样
- 尝试更改 DllImport 参数。失败,异常依然有
- 尝试使用 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 排版