我正在开发一个项目,该项目结合使用 C# 和汇编语言 (ASM) 来模拟绿色盲(一种色觉缺陷)。 ASM 代码生成一个处理图像数据的 DLL,我遇到了 System.AccessViolationException 错误,并显示以下消息:
尝试读取或写入受保护的内存。这通常表明其他内存已损坏。
编程语言:C# 用于主应用程序,汇编语言 (ASM) 用于图像处理。 功能:该应用程序允许用户加载图像,处理它(使用asm模块或c#模块)以模拟绿色盲,并保存处理后的图像。
ASM代码(ModuleAsm.asm):
.code
DeuteranopiaAsm proc
SimulateDeuteranopiaASM:
; Save registers
push rbp
mov rbp, rsp
sub rsp, 32
mov r9, rdx ; pixelCount
mov r8d, edi ; threadCount (passed through edi)
mov rdx, rcx ; pointer to original image (originalImage)
mov rcx, rdi ; pointer to processed image (processedImage)
; Calculate the portion size for each thread
xor rax, rax ; Clear rax before division
mov eax, r9d ; Move pixelCount to eax (32-bit register for division)
xor edx, edx ; Clear edx (higher part of rax)
div r8d ; Divide eax by r8d (threadCount in r8d, 32-bit register)
mov r10d, eax ; Store the result (portion size) in r10d
; Parallel processing
mov eax, r10d
mov r11, rdx ; pointer to original image
mov rdx, rcx ; pointer to processed image
ProcessLoop:
cmp r9d, 0
je Done
; Load original pixel data using 8-bit registers
movzx eax, byte ptr [r11] ; Load red channel (R) into eax (extended to 32-bit register)
movzx ecx, byte ptr [r11 + 1] ; Load green channel (G) into ecx (extended to 32-bit register)
movzx edx, byte ptr [r11 + 2] ; Load blue channel (B) into edx (extended to 32-bit register)
; Simulate deuteranopia (green color blindness)
imul eax, eax, 625 ; Transform red channel
imul ecx, ecx, 375 ; Transform green channel
add eax, ecx
shr eax, 10 ; Divide by 1024
mov [rdx + 2], al ; Save transformed red channel to processed image
imul ecx, ecx, 7 ; Further transform green channel
shr ecx, 3 ; Divide by 8
mov [rdx + 1], cl ; Save transformed green channel
imul edx, edx, 8 ; Transform blue channel
shr edx, 3 ; Divide by 8
mov [rdx], dl ; Save transformed blue channel
; Move to the next pixel
add r11, 3 ; Move pointer in original image to the next pixel
add rdx, 3 ; Move pointer in processed image to the next pixel
dec r9d ; Decrement pixel count
jmp ProcessLoop ; Repeat loop
Done:
; Restore registers
add rsp, 32
pop rbp
ret
DeuteranopiaAsm endp
end
C# 代码(MainWindow.xaml.cs):
using System;
using System.Windows;
using System.Windows.Input;
using Microsoft.Win32;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
namespace Projekt
{
public partial class MainWindow : Window
{
// Importing the ASM function (updated with the correct path)
[DllImport(@"C:\Users\kacpe\Desktop\home\Programing\studia\ASM-SEM-5\Projekt\x64\Debug\ModuleAsm.dll")]
public static extern void DeuteranopiaAsm(IntPtr originalImage, IntPtr processedImage, int pixelCount, int stride, int threadCount);
private Bitmap _originalImage;
private Bitmap _processedImage;
public MainWindow()
{
InitializeComponent();
int processorThreads = Environment.ProcessorCount;
threadSlider.Value = processorThreads;
threadCount.Text = $"Selected threads: {processorThreads}";
}
private void Exit_Click(object sender, RoutedEventArgs e)
{
Application.Current.Shutdown();
}
private void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.ButtonState == MouseButtonState.Pressed)
{
this.DragMove();
}
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.WindowStartupLocation = WindowStartupLocation.Manual;
var screenWidth = SystemParameters.PrimaryScreenWidth;
var screenHeight = SystemParameters.PrimaryScreenHeight;
this.Left = (screenWidth - this.Width) / 2;
this.Top = (screenHeight - this.Height) / 2;
}
private void Slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
if (threadCount != null)
{
int threadValue = (int)threadSlider.Value;
threadCount.Text = $"Thread count: {threadValue}";
}
}
private void ChooseImage_Click(object sender, RoutedEventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog
{
Filter = "Image files (*.jpg, *.png)|*.jpg;*.png",
Title = "Choose an image"
};
if (openFileDialog.ShowDialog() == true)
{
imagePathTextBox.Text = openFileDialog.FileName;
_originalImage = new Bitmap(openFileDialog.FileName);
MessageBox.Show("Image loaded successfully.");
}
}
private void ProcessImage_Click(object sender, RoutedEventArgs e)
{
if (_originalImage == null)
{
MessageBox.Show("Please select an image first.");
return;
}
int threadCount = (int)threadSlider.Value;
_processedImage = new Bitmap(_originalImage.Width, _originalImage.Height);
if (asmRadioButton.IsChecked == true)
{
Rectangle rect = new Rectangle(0, 0, _originalImage.Width, _originalImage.Height);
BitmapData originalData = _originalImage.LockBits(rect, ImageLockMode.ReadOnly, _originalImage.PixelFormat);
BitmapData processedData = _processedImage.LockBits(rect, ImageLockMode.WriteOnly, _processedImage.PixelFormat);
int pixelCount = _originalImage.Width * _originalImage.Height;
int stride = originalData.Stride;
IntPtr originalPtr = originalData.Scan0;
IntPtr processedPtr = processedData.Scan0;
// Call to the ASM function
DeuteranopiaAsm(originalPtr, processedPtr, pixelCount, stride, threadCount);
_originalImage.UnlockBits(originalData);
_processedImage.UnlockBits(processedData);
MessageBox.Show("Image processed in ASM.");
}
else if (cSharpRadioButton.IsChecked == true)
{
_processedImage = SimulateDeuteranopia(_originalImage, threadCount);
MessageBox.Show("Image processed in C#.");
}
}
private void SaveImage_Click(object sender, RoutedEventArgs e)
{
if (_processedImage == null)
{
MessageBox.Show("Please process the image first.");
return;
}
SaveFileDialog saveFileDialog = new SaveFileDialog
{
Filter = "Image files (*.jpg, *.png)|*.jpg;*.png",
Title = "Save image"
};
if (saveFileDialog.ShowDialog() == true)
{
_processedImage.Save(saveFileDialog.FileName);
MessageBox.Show("Image saved successfully.");
}
}
public static Bitmap SimulateDeuteranopia(Bitmap original, int threads)
{
int width = original.Width;
int height = original.Height;
Bitmap simulatedImage = new Bitmap(width, height, original.PixelFormat);
Rectangle rect = new Rectangle(0, 0, original.Width, original.Height);
BitmapData originalData = original.LockBits(rect, ImageLockMode.ReadOnly, original.PixelFormat);
BitmapData simulatedData = simulatedImage.LockBits(rect, ImageLockMode.WriteOnly, simulatedImage.PixelFormat);
int bytesPerPixel = Image.GetPixelFormatSize(original.PixelFormat) / 8;
int stride = originalData.Stride;
IntPtr originalScan0 = originalData.Scan0;
IntPtr simulatedScan0 = simulatedData.Scan0;
byte[] originalPixels = new byte[stride * height];
byte[] simulatedPixels = new byte[stride * height];
Marshal.Copy(originalScan0, originalPixels, 0, originalPixels.Length);
Parallel.For(0, threads, threadIndex =>
{
int partitionSize = height / threads;
int start = threadIndex * partitionSize;
int end = (threadIndex == threads - 1) ? height : start + partitionSize;
for (int y = start; y < end; y++)
{
for (int x = 0; x < width; x++)
{
int pixelIndex = y * stride + x * bytesPerPixel;
byte originalB = originalPixels[pixelIndex];
byte originalG = originalPixels[pixelIndex + 1];
byte originalR = originalPixels[pixelIndex + 2];
// Simulate color transformation for deuteranopia
int newR = (int)(originalR * 0.625 + originalG * 0.375);
int newG = (int)(originalG * 0.7);
int newB = (int)(originalB * 0.8);
newR = Clamp(newR, 0, 255);
newG = Clamp(newG, 0, 255);
newB = Clamp(newB, 0, 255);
simulatedPixels[pixelIndex] = (byte)newB;
simulatedPixels[pixelIndex + 1] = (byte)newG;
simulatedPixels[pixelIndex + 2] = (byte)newR;
}
}
});
Marshal.Copy(simulatedPixels, 0, simulatedScan0, simulatedPixels.Length);
original.UnlockBits(originalData);
simulatedImage.UnlockBits(simulatedData);
return simulatedImage;
}
public static int Clamp(int value, int min, int max)
{
if (value < min) return min;
if (value > max) return max;
return value;
}
}
}
当代码尝试访问不允许的内存时,通常会发生 AccessViolationException。我怀疑这可能与 C# 和 ASM 代码之间的指针和内存管理方式有关。我正确锁定了图像的位,但我想知道是否存在任何内存对齐问题或者像素操作是否可能导致问题。
我尝试过重组,但毫无结果...
mov rdx, rcx ; pointer to original image (originalImage) ... xor edx, edx ; Clear edx (higher part of rax) div r8d ; Divide eax by r8d ... mov r11, rdx ; pointer to original image
div r8d
指令不是“eax除以r8d”,而是组合EDX:EAX除以R8D。; Clear edx (higher part of rax)
这显示了对 x86 寄存器的误解。 EDX是RDX寄存器的低32位。 RAX 是一个完全不同的寄存器。
mov [rdx], dl ; Save transformed blue channel
此代码中还有另一个严重错误:由于您选择了 RDX 作为指向已处理图像的指针,因此您不能同时使用 DL 或 EDX 来保存蓝色通道。 DL、DH、DX 和 EDX 都是同一 RDX 寄存器的不同部分。我认为你最好选择例如。使用调用破坏的 R10 作为输出寄存器。
还有另外两个观察结果:
在输入端,您有红色,然后是绿色,然后是蓝色,但在输出端,您颠倒了顺序,所以您有蓝色,然后是绿色,然后是红色。这是故意的吗?
蓝色通道的变换是多余的!
imul edx, edx, 8
乘以 8,但 shr edx, 3
然后除以 8,撤消更改。
mov r9, rdx ; pixelCount mov r8d, edi ; threadCount (passed through edi) mov rdx, rcx ; pointer to original image (originalImage) mov rcx, rdi ; pointer to processed image (processedImage)
你们的论点也存在不匹配和重叠!如果 RDI 持有指向已处理图像的指针,那么 EDI 就不可能持有该 threadCount。 EDI 根本不是一个单独的寄存器; EDI 是我们给 RDI 寄存器的最低 32 位起的名字。
从
DeuteranopiaAsm(originalPtr, processedPtr, pixelCount, stride, threadCount);
,我希望在RCX中找到originalPtr,在RDX中找到processedPtr,在R8中找到pixelCount,在R9中找到stride,以及在32字节影子空间上方的堆栈上找到threadCount .
@九月罗兰 我尝试修复我的代码,这就是结果:
.code
DeuteranopiaAsm proc
SimulateDeuteranopiaASM:
; RCX = originalImage (pointer)
; RDX = processedImage (pointer)
; R8 = pixelCount (number of pixels)
; R9 = stride (bytes per row)
mov r11, rcx ; Save pointer to original image
mov r10, rdx ; Save pointer to processed image
xor rbx, rbx ; Use RBX as pixel counter
PixelLoop:
cmp rbx, r8 ; Check if all pixels are processed
jge EndLoop ; If rbx >= pixelCount, exit
mov rax, rbx ; Copy pixel counter to rax
shl rax, 1 ; rax = rbx * 2 (shift left by 1)
add rax, rbx ; rax = rax + rbx = rbx * 3
; Load color data (B, G, R)
mov al, byte ptr [r11 + rax] ; Load blue channel
mov cl, byte ptr [r11 + rax + 1] ; Load green channel
mov dl, byte ptr [r11 + rax + 2] ; Load red channel
; Simulate deuteranopia (adjust color channels)
; New Red channel
movzx r8d, dl ; Convert red channel to 32-bit
imul r8d, 625 ; R = R * 0.625
movzx r9d, cl ; Convert green channel to 32-bit
imul r9d, 375 ; G = G * 0.375
add r8d, r9d ; New Red = (R * 0.625 + G * 0.375)
shr r8d, 8 ; Divide by 256 to fit in byte range
; Clamp red channel
cmp r8d, 255
jle NoClampRed
mov r8d, 255
NoClampRed:
; Transform green channel
movzx r9d, cl ; Reload green channel
imul r9d, 7 ; G = G * 0.7
shr r9d, 8 ; Divide by 256
; Clamp green channel
cmp r9d, 255
jle NoClampGreen
mov r9d, 255
NoClampGreen:
; Transform blue channel
movzx rax, al ; Convert blue to 32-bit
imul rax, 8 ; B = B * 0.8
shr rax, 8 ; Divide by 256
; Clamp blue channel
cmp rax, 255
jle NoClampBlue
mov rax, 255
NoClampBlue:
; Store processed pixel back into the image
mov byte ptr [r10 + rax], al ; Store blue channel
mov byte ptr [r10 + rax + 1], cl ; Store green channel
mov byte ptr [r10 + rax + 2], dl ; Store red channel
inc rbx ; Increment pixel counter
jmp PixelLoop ; Repeat for next pixel
EndLoop:
ret
DeuteranopiaAsm endp
end
代码可以编译,但输出图像是黑色的,左上角有奇怪的点。 输入照片