最近接手了一个同事的代码,主要是实现对C++ SDK的封装,从而提供给Unity客户调用,我们姑且称之为C# SDK。
该C# SDK提供了一个示例demo,大概就是从摄像头获取数据,然后在其上绘制人体关键点,并渲染出来。该demo分为Unity和Winform两个版本,功能相同,细节略有不同。
问题来了,同样的代码,在Winform中表现正常,在Unity中很容易出现下面这个问题:
经过一番努力之后,大概定位到这部分代码:
//获取图像,通过new unsigned char[buf_sz]
IntPtr pRgb = NativeMethods.Raw16ToRgb24(fg.foregroundData, nw, nh);
...
//在RGB图像上绘制小矩形
NativeMethods.Rgb24DrawRect(pRgb, nw, nh, rect.x, rect.y, 5, 5, 255, 255, 255, 0);
...
//释放图像,通过delete [] p
NativeMethods.DeleteMemPointer(pRgb);
该代码调用C++ SDK中的一个绘制函数,在RGB图像上画一个5x5像素的矩形。
起初走了弯路,因为Winform中表现OK,所以认为是Unity平台的特性导致,感觉很头大,无从下手。
后来检索搜索引擎,看到这样一个类似的问题:
What does “CRT detected that the application wrote to memory after end of heap buffer” mean?
注意到其中的一个答案:
这似乎解释了上面的两种不同表现,并且类似的是同样在释放内存出现的错误。带着这两个假设,决定跳过C#的部分,直接看C++代码,写了这样一个测试snippet:
void debug_heap_corrupt(){
const auto w = 320;
const auto h = 240;
unsigned char buf[] = { 0, 0, 0 };
for (int i = 0; i < w; i++) {
for (int j = 0; j < h; j++)
{
unsigned char* pRgb = new unsigned char[w * h * 3];
Rgb24DrawRect_(pRgb, w, h, i, j, 5, 5, buf, 0);
delete[] pRgb;
}
}
}
幸运的是,在VS2017/x64中,得到了相同的现象:
并且在VS的Debug模式帮助下,得到了此时传递给绘制函数的值:i=316, j=235
。这里有一部分值组合可以触发。
下面就是解决这个问题了,此时断定就是内存越界修改。看Rgb24DrawRect_
函数,注意到其中对内存的修改,有这么两个循环。每个循环都是将特定位置的像素,修改为指定的值:
int Rgb24DrawRect_(unsigned char* pRgb, int nw, int nh, int nx, int ny, int nWRect, int nHRect, unsigned char* pColorBuf, int nAlign){
//loop1
for (j = 0, k = 0; j < m; j++, k += 3)
{
memcpy(pPos + k, pColorBuf, 3);
}
//loop2
for (i = 1; i < n; i++)
{
memcpy(pCur, pPos, k);
pCur += cbLine;
}
}
我这里为了快速解决,就采用简单的方式处理了,在修改内存之前,对目的地址进行判断,确保其落在整个RGB图像unsigned char
数组范围内,下面是修改后的代码:
int Rgb24DrawRect_(unsigned char* pRgb, int nw, int nh, int nx, int ny, int nWRect, int nHRect, unsigned char* pColorBuf, int nAlign){
#ifdef PTR_SAFETY_CHECK
//计算RGB图像的安全范围
const auto * const pRgbEnd = pRgb + nw * nh * 3;
#endif
//loop1
for (j = 0, k = 0; j < m; j++, k += 3)
{
#ifdef PTR_SAFETY_CHECK
if(pPos + k + 3 <= pRgbEnd )
#endif
memcpy(pPos + k, pColorBuf, 3);
}
//loop2
for (i = 1; i < n; i++)
{
#ifdef PTR_SAFETY_CHECK
if (pCur + k <= pRgbEnd)
#endif
memcpy(pCur, pPos, k);
pCur += cbLine;
}
}
Unity(Windows) | Winform | VS2017(C++) | g++ 7.4.0 | NDK r11c |
---|---|---|---|---|
报错 | 无报错 | 报错 | 无报错 | 无报错 |