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

C#与ASM绿色盲模拟项目触发System.AccessViolationException问题排查

解决C#与ASM互操作中的System.AccessViolationException异常

问题概述

开发C#与ASM结合的绿色盲模拟项目时,调用ASM生成的DLL处理图像数据时触发System.AccessViolationException,错误提示:

尝试读取或写入受保护的内存。这通常指示其他内存已损坏。

项目细节:

  • 主应用:C#(.NET Framework x64)
  • 图像处理模块:x64汇编
  • 功能:加载图像,通过ASM/C#模拟绿色盲效果并保存

错误根源分析

从提供的ASM和C#代码来看,异常由以下多个问题共同导致:

1. 调用约定与参数映射错误

C#的[DllImport]默认使用StdCall调用约定,但x64汇编遵循微软FastCall约定,且你的ASM代码完全搞混了参数传递的寄存器顺序:

  • 微软x64约定中,前4个参数依次存入rcx、rdx、r8、r9,剩余参数入栈
  • 你错误地使用rdi等非参数寄存器接收参数,直接导致后续逻辑使用错误的内存地址

2. 寄存器操作逻辑混乱

ASM代码中存在寄存器覆盖、参数值丢失的问题:

  • 初始化阶段就覆盖了传入的参数寄存器,导致后续计算使用无效值
  • 处理像素时错误覆盖rdx寄存器(原本指向处理后图像的指针),引发写入地址错误

3. 忽略图像Stride导致内存越界

C#传递了stride参数,但ASM完全未使用:

  • 图像内存并非严格按宽度*3字节排列,而是按stride(对齐后的行字节数)存储
  • 直接按+3递增指针会跳过行对齐的填充字节,最终访问到受保护内存区域

4. 像素格式与通道顺序错误

ASM默认按RGB顺序处理3字节像素,但C#的Bitmap默认是BGR通道顺序,且未处理32位RGBA格式的情况,直接导致内存访问偏移错误

5. 并行处理逻辑未实现

ASM代码仅写了并行计算的初始化代码,但实际还是单线程循环,线程数参数未被正确使用

修复方案

步骤1:修正ASM的调用约定与参数映射

修改ASM代码,严格遵循微软x64调用约定,同时修正通道顺序和计算逻辑:

.code
DeuteranopiaAsm proc
; 微软x64调用约定参数顺序:
; rcx = originalImage (原始图像指针)
; rdx = processedImage (处理后图像指针)
; r8d = pixelCount (总像素数)
; r9d = stride (图像行字节数)
; [rbp+32] = threadCount (第5个参数,栈上)

    push rbp
    mov rbp, rsp
    sub rsp, 40 ; 分配栈空间,保持16字节对齐

    ; 保存非易失性寄存器(x64约定需保存rbx, rsi, rdi, r12-r15)
    push rbx
    push rsi
    push rdi
    push r12
    push r13
    push r14
    push r15

    ; 读取参数
    mov rsi, rcx        ; rsi = 原始图像指针
    mov rdi, rdx        ; rdi = 处理后图像指针
    mov r12d, r8d       ; r12d = 总像素数
    mov r13d, r9d       ; r13d = stride
    mov r14d, [rbp+32]  ; r14d = threadCount

    ; 计算单线程处理像素数(先实现单线程,再扩展并行)
    xor rax, rax
    mov eax, r12d
    xor edx, edx
    div r14d
    mov r15d, eax       ; r15d = 每个线程处理像素数

    ; 单线程处理逻辑
    mov rbx, 0          ; rbx = 当前处理像素索引

ProcessLoop:
    cmp rbx, r12
    jge Done

    ; 计算当前像素的内存偏移:行偏移 + 像素偏移
    mov eax, ebx
    xor edx, edx
    div r13d            ; eax = 行索引,edx = 行内字节偏移(像素索引*3)
    imul rax, rax, r13  ; rax = 行起始偏移
    add rax, rdx        ; rax = 总偏移

    ; 读取原始像素(BGR顺序,对应C# Bitmap默认格式)
    movzx edx, byte ptr [rsi+rax]        ; B
    movzx ecx, byte ptr [rsi+rax+1]      ; G
    movzx eax, byte ptr [rsi+rax+2]      ; R

    ; 绿色盲转换计算(与C#逻辑一致)
    ; 处理R通道:R*0.625 + G*0.375
    imul eax, eax, 625
    imul r8d, ecx, 375
    add eax, r8d
    shr eax, 10
    ; 钳位到0-255
    cmp eax, 255
    cmovg eax, dword ptr [rel max_val]
    cmp eax, 0
    cmovl eax, dword ptr [rel min_val]

    ; 处理G通道:G*0.7
    imul ecx, ecx, 7
    shr ecx, 3
    cmp ecx, 255
    cmovg ecx, dword ptr [rel max_val]
    cmp ecx, 0
    cmovl ecx, dword ptr [rel min_val]

    ; 处理B通道:B*0.8
    imul edx, edx, 4
    mov r9d, edx
    shr r9d, 2
    sub edx, r9d        ; B*4/5 ≈ B*0.8
    cmp edx, 255
    cmovg edx, dword ptr [rel max_val]
    cmp edx, 0
    cmovl edx, dword ptr [rel min_val]

    ; 写入处理后图像(保持BGR顺序)
    mov byte ptr [rdi+rax], dl      ; B通道
    mov byte ptr [rdi+rax+1], cl    ; G通道
    mov byte ptr [rdi+rax+2], al    ; R通道

    inc rbx             ; 处理下一个像素
    jmp ProcessLoop

Done:
    ; 恢复非易失性寄存器
    pop r15
    pop r14
    pop r13
    pop r12
    pop rdi
    pop rsi
    pop rbx

    add rsp, 40
    pop rbp
    ret

; 钳位常量
max_val dword 255
min_val dword 0
DeuteranopiaAsm endp
end

步骤2:修正C#的DllImport调用约定

显式指定调用约定,避免匹配错误:

[DllImport(@"C:\Users\kacpe\Desktop\home\Programing\studia\ASM-SEM-5\Projekt\x64\Debug\ModuleAsm.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void DeuteranopiaAsm(IntPtr originalImage, IntPtr processedImage, int pixelCount, int stride, int threadCount);

步骤3:添加像素格式检查与转换

在C#调用ASM前,统一图像格式为24位BGR,避免格式不匹配:

if (_originalImage.PixelFormat != PixelFormat.Format24bppRgb)
{
    _originalImage = _originalImage.Clone(new Rectangle(0,0,_originalImage.Width,_originalImage.Height), PixelFormat.Format24bppRgb);
}
_processedImage = new Bitmap(_originalImage.Width, _originalImage.Height, PixelFormat.Format24bppRgb);

步骤4:实现并行处理(可选)

若需多线程处理,建议在C#中拆分图像区域,再调用ASM处理子区域,避免在ASM中编写复杂的线程管理逻辑:

// 示例:C#拆分区域后调用ASM
Parallel.For(0, threadCount, threadIndex =>
{
    int startPixel = threadIndex * pixelCount / threadCount;
    int endPixel = (threadIndex == threadCount-1) ? pixelCount : (threadIndex+1)*pixelCount/threadCount;
    // 调用ASM处理startPixel到endPixel的区域(需修改ASM支持区域处理)
});

调试建议

  • 使用Visual Studio混合模式调试,同时调试C#和ASM,设置断点观察寄存器值与内存访问情况
  • 检查stride值是否正确,验证行偏移计算逻辑
  • 打印像素格式信息,确认通道顺序与ASM处理逻辑匹配

内容的提问来源于stack exchange,提问作者Kacper Baryłowicz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 00:14:51