C#调用C++原生代码时触发_CrtIsValidHeapPointer断言失败?
我实现了一个通过原生代码模糊图像的简单方法,C#端通过P/Invoke调用C++的GaussianBlur函数。
C#代码
internal override void Apply(BitmapBuffer framebuffer, BitmapBuffer backBuffer, BitmapBuffer frontBuffer, BitmapBufferRepository repository) { var data = framebuffer.Bitmap.LockBits( new System.Drawing.Rectangle(0, 0, framebuffer.Bitmap.Width, framebuffer.Bitmap.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format32bppArgb); ImageProcessing.GaussianBlur(data.Scan0, data.Stride, data.Width, data.Height, Radius); framebuffer.Bitmap.UnlockBits(data); }
[DllImport("Animator.Engine.Native.dll", CallingConvention = CallingConvention.Cdecl)] public static extern void GaussianBlur(IntPtr bitmapData, int stride, int width, int height, int radius);
C++代码
extern "C" void __cdecl GaussianBlur(unsigned char* bitmapData, int stride, int width, int height, int radius) { // Gaussian kernel int diameter = 2 * radius + 1; std::shared_ptr<float[]> kernel = generateGaussKernel(diameter); // Blur auto copy = std::shared_ptr<unsigned char[]>(new unsigned char[height * stride]); for (int y = 0; y < height; y++) memcpy(copy.get() + y * stride, bitmapData + y * stride, width * BYTES_PER_PIXEL); for (int y = 0; y < height; y++) for (int x = 0; x < width; x++) { FloatColor sum; float weight = 0.0f; int count = 0; int xStart = x - radius; int xEnd = x + radius; int yStart = y - radius; int yEnd = y + radius; for (int x1 = xStart; x1 <= xEnd; x1++) for (int y1 = yStart; y1 <= yEnd; y1++) { // Find weight int kernelX = x1 - xStart; int kernelY = y1 - yStart; float kernelValue = kernel[kernelY * diameter + kernelX]; // Premultiply alpha FloatColor color; if (x1 >= 0 && x1 < width && y1 >= 0 && y1 < height) color = getFloatColor(copy.get(), stride, x1, y1); else color = FloatColor(0); sum.R += (float)(color.R * color.A) * kernelValue; sum.G += (float)(color.G * color.A) * kernelValue; sum.B += (float)(color.B * color.A) * kernelValue; sum.A += color.A * kernelValue; weight += kernelValue; count++; } if (count > 0) { FloatColor result; result.A = sum.A / weight; if (result.A > 0) { result.R = ((sum.R / weight) / result.A); result.G = ((sum.G / weight) / result.A); result.B = ((sum.B / weight) / result.A); } setFloatColor(bitmapData, stride, x, y, result); } } }
错误现象
当应用在多线程环境(Parallel.For)中运行时,调用GaussianBlur会触发“Debug Assertion Failed”错误:
Microsoft Visual C++ Runtime Library
Debug Assertion Failed!
Program: ...nimator\Animator\bin\x64\Debug\net6.0-windows7.0\Animator.exe
File: minkernel\crts\ucrt\src\appcrt\heap\debug_heap.cpp
Line: 904Expression: _CrtIsValidHeapPointer(block)
For information on how your program can cause an assertion
failure, see the Visual C++ documentation on asserts.(Press Retry to debug the application)
Przerwij Ponów próbę Ignoruj
经排查,断言在P/Invoke实际进入原生函数前就失败(原生函数第一行的断点从未命中),且该失败有时会在第二个线程尝试调用函数时出现。C++代码仅使用传入参数,未使用外部资源,已实现线程安全。
核心原因
这个断言错误的本质是内存越界污染了调试堆结构,或跨模块CRT堆不兼容,结合你的代码细节,最可能的触发点是内存复制长度错误,加上多线程下的竞争问题。
1. 内存复制长度错误(最可能元凶)
你的C++代码中,memcpy的长度是width * BYTES_PER_PIXEL,但Bitmap的stride是按内存对齐要求计算的实际每行字节数(比如宽度不是4的倍数时,stride会比width*4大)。这会导致:
- 复制的内存长度不足,
copy中每行的末尾对齐字节未被复制,后续访问这些位置时会读取非法内存 - 若
BYTES_PER_PIXEL定义不是4(对应Format32bppArgb),会直接导致读/写越界,破坏堆结构
修复方案:
将memcpy的长度改为stride,确保复制完整的一行数据:
for (int y = 0; y < height; y++) memcpy(copy.get() + y * stride, bitmapData + y * stride, stride);
2. 多线程Bitmap访问竞争
即使C++代码线程安全,若Parallel.For中多个线程同时对同一个Bitmap执行LockBits和写操作,会导致内存竞争,破坏Bitmap的内存结构,触发堆断言。
修复方案:
对Bitmap的访问加锁,确保同一时间只有一个线程操作:
lock(framebuffer.Bitmap) { var data = framebuffer.Bitmap.LockBits( new System.Drawing.Rectangle(0, 0, framebuffer.Bitmap.Width, framebuffer.Bitmap.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format32bppArgb); ImageProcessing.GaussianBlur(data.Scan0, data.Stride, data.Width, data.Height, Radius); framebuffer.Bitmap.UnlockBits(data); }
3. CRT运行时兼容性问题
Debug模式下,VC的调试CRT会使用独立的堆。如果你的C DLL编译时使用了Multi-threaded Debug (/MTd)(静态链接CRT),而.NET进程使用的是动态CRT,会导致跨模块堆操作不兼容,触发堆校验断言。
修复方案:
编译C++ DLL时,选择对应模式的动态CRT:
- Debug模式:
Multi-threaded Debug DLL (/MDd) - Release模式:
Multi-threaded DLL (/MD)
4. 辅助函数的内存越界检查
检查generateGaussKernel、getFloatColor、setFloatColor函数:
- 确认
generateGaussKernel返回的kernel数组长度为diameter*diameter,避免数组越界访问 - 确认
getFloatColor/setFloatColor计算内存偏移时使用正确的公式:y * stride + x * 4(对应32位ARGB像素)
内容的提问来源于stack exchange,提问作者Spook

