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

ser*_*0ne 2 .net c# gdi+ pointers unsafe

我最近在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;
}
Run Code Online (Sandbox Code Playgroud)

这条线byte* p = (byte*)(void*)Scan0;看起来像罪魁祸首,但我不得不说我真的不明白它在做什么,或者它是如何安全的.

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

Lua*_*aan 5

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

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

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

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

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

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

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