You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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: 904

Expression: _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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 20:14:53