在C#中使不安全的代码安全

时间:2015-05-29 13:33:31

标签: c# .net pointers gdi+ unsafe

我最近阅读了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;看起来像是罪魁祸首,但我不得不说我不明白它在做什么,或者如何安全。

有人可以对此有所了解吗?

1 个答案:

答案 0 :(得分:5)

主要是出于性能原因使用不安全代码。基本的想法是你在图像数据上逐字节地移动,并手动翻转每个字节(虽然有更有效和简单的方法来处理同样的事情)。

底层映像由GDI +处理,这是非托管代码。因此,当您直接使用图像字节时,必须操纵非托管内存。实际上,安全性或不安全性确实令人惊讶地难以确定 - 它在很大程度上取决于最初如何分配非托管内存。鉴于您正在使用托管代码,并且您可能从文件或某些流中加载了位图,实际上它很可能不是非常不安全,实际上 - 例如,您无法意外覆盖托管内存。 unsafe关键字的名称并非来自本质上的危险 - 它来自允许你做非常不安全的事情。例如,如果您在自己的托管堆栈上为位图分配了内存,那么您可能会把事情搞得一团糟。

总的来说,如果您能够真正证明它是值得花费的话,那么只使用不安全的代码是一个好习惯。在图像处理中,这通常是一个很好的权衡 - 你正在使用大量的简单数据,例如在边界检查可能很重要,即使只验证它们一次很容易,而不是在循环的每次迭代中。

如果您想摆脱这种不安全的代码,一种方法是分配您自己的byte[](托管),使用Marshal.Copy将图片数据复制到此byte[],在托管数组中进行修改,然后再次使用Marshal.Copy复制结果。问题是,这意味着分配与原始图像一样大的byte[],然后将其复制两次(在这种情况下,边界检查可以忽略不计 - .NET JIT编译器将对其进行优化)。最后,使用Marshal.Copy时出现错误仍然可能会给你带来与unsafe相同的问题(不完全是,但是将会进行更长时间的讨论。)

对我而言,将unsafe作为关键字的最有价值的部分是,它允许您本地化您正在做的不安全的事情。虽然典型的非托管应用程序一直不安全,但C#只允许您在代码的特定标记部分中不安全。虽然这些仍然可以影响代码的其余部分(这是您只能在FullTrust环境中使用unsafe的原因之一),但它使调试和控制更容易。这是一种权衡,一如既往。

但是,代码实际上是以不同的方式不安全的 - 如果代码中间有异常,则UnlockBits调用可能永远不会发生。您应该使用finally子句来确保推进者清理非托管资源。

最后一点,如果你想要“真正的”性能,安全或不安全,你可能无论如何都不会在CPU上进行图像处理。今天,通常可以安全地假设您运行的计算机具有GPU,可以更快,更轻松地完成工作,并完全隔离计算机本身实际运行的代码。