我最近阅读了image processing in C#
上的一篇文章那里有一些我不喜欢的代码,因为它不安全,我想知道它是否可以安全:
public static bool Invert(Bitmap b)
{
// GDI+ still lies to us - the return format is BGR, NOT RGB.
BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height),
ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
int stride = bmData.Stride;
System.IntPtr Scan0 = bmData.Scan0;
unsafe
{
byte * p = (byte *)(void *)Scan0;
int nOffset = stride - b.Width*3;
int nWidth = b.Width * 3;
for(int y=0;y < b.Height;++y)
{
for(int x=0; x < nWidth; ++x )
{
p[0] = (byte)(255-p[0]);
++p;
}
p += nOffset;
}
}
b.UnlockBits(bmData);
return true;
}
线byte* p = (byte*)(void*)Scan0;
看起来像是罪魁祸首,但我不得不说我不明白它在做什么,或者如何安全。
有人可以对此有所了解吗?
答案 0 :(得分:5)
主要是出于性能原因使用不安全代码。基本的想法是你在图像数据上逐字节地移动,并手动翻转每个字节(虽然有更有效和简单的方法来处理同样的事情)。
底层映像由GDI +处理,这是非托管代码。因此,当您直接使用图像字节时,必须操纵非托管内存。实际上,安全性或不安全性确实令人惊讶地难以确定 - 它在很大程度上取决于最初如何分配非托管内存。鉴于您正在使用托管代码,并且您可能从文件或某些流中加载了位图,实际上它很可能不是非常不安全,实际上 - 例如,您无法意外覆盖托管内存。 unsafe
关键字的名称并非来自本质上的危险 - 它来自允许你做非常不安全的事情。例如,如果您在自己的托管堆栈上为位图分配了内存,那么您可能会把事情搞得一团糟。
总的来说,如果您能够真正证明它是值得花费的话,那么只使用不安全的代码是一个好习惯。在图像处理中,这通常是一个很好的权衡 - 你正在使用大量的简单数据,例如在边界检查可能很重要,即使只验证它们一次很容易,而不是在循环的每次迭代中。
如果您想摆脱这种不安全的代码,一种方法是分配您自己的byte[]
(托管),使用Marshal.Copy
将图片数据复制到此byte[]
,在托管数组中进行修改,然后再次使用Marshal.Copy
复制结果。问题是,这意味着分配与原始图像一样大的byte[]
,然后将其复制两次(在这种情况下,边界检查可以忽略不计 - .NET JIT编译器将对其进行优化)。最后,使用Marshal.Copy
时出现错误仍然可能会给你带来与unsafe
相同的问题(不完全是,但是将会进行更长时间的讨论。)
对我而言,将unsafe
作为关键字的最有价值的部分是,它允许您本地化您正在做的不安全的事情。虽然典型的非托管应用程序一直不安全,但C#只允许您在代码的特定标记部分中不安全。虽然这些仍然可以影响代码的其余部分(这是您只能在FullTrust环境中使用unsafe
的原因之一),但它使调试和控制更容易。这是一种权衡,一如既往。
但是,代码实际上是以不同的方式不安全的 - 如果代码中间有异常,则UnlockBits
调用可能永远不会发生。您应该使用finally
子句来确保推进者清理非托管资源。
最后一点,如果你想要“真正的”性能,安全或不安全,你可能无论如何都不会在CPU上进行图像处理。今天,通常可以安全地假设您运行的计算机具有GPU,可以更快,更轻松地完成工作,并完全隔离计算机本身实际运行的代码。