Does unsafe code have any effect on safe code?

a 夏天 提交于 2019-12-08 14:47:39

问题


As I understand it, marking an method as unsafe will disable some of the CLR checks on that code, but does this have any effect on the rest of the system which is safe, other than the fact that the DLL/EXE can not run in a untrusted environment.

In particular,

  1. Are they are any safety checks that will not work on the complete dll because it is marked as unsafe?
  2. If a DLL is marked as unsafe, but the methods marked as unsafe are not actually called, is this the same as if the DLL is marked as safe?
  3. Are they any run-time benefits on keeping the unsafe code in a separate DLL?

I have the problem with redrawing nested controls on 64-bit windows as detailed here and the one the solutions (the one that appears to work) involves unsafe code and I would like to understand the effect that adding this code has to my project.


回答1:


The answer to your question is: The unsafe keyword does not mean "unsafe", it means "potentially unsafe". The compiler and framework cannot work to make certain that it's safe. It is up to you to make certain that the code cannot perform unsafe reads or writes to memory.

I would strongly encourage you to follow this advice given in the article you linked:

1) Redesign the application to have less containers and reduce the number of nesting levels.

If you're using containers for the sole purpose of control arrangement, write your own container that can do all the arrangement with one level.

Updated

You can modify the code in that article so that it doesn't use pointers (i.e. doesn't require the unsafe keyword). Keep in mind that this will now require marshalling which means extra copying. This is probably a good thing because the original code is passing a WINDOWPOS pointer from the OS to BeginInvoke which does not execute during the same dispatch event that the OS generated the pointer in. In other words, that code was smelly already.

internal class MyTabPage : TabPage
{
    private const int WM_WINDOWPOSCHANGING = 70;
    private const int WM_SETREDRAW = 0xB;
    private const int SWP_NOACTIVATE = 0x0010;
    private const int SWP_NOZORDER = 0x0004;
    private const int SWP_NOSIZE = 0x0001;
    private const int SWP_NOMOVE = 0x0002;

    [DllImport("User32.dll", CharSet = CharSet.Auto)]
    extern static int SendMessage(HandleRef hWnd, int msg, int wParam, int lParam);

    [DllImport("User32.dll", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Auto)]
    extern static bool SetWindowPos(HandleRef hWnd, HandleRef hWndInsertAfter,
    int x, int y, int cx, int cy, int flags);

    [StructLayout(LayoutKind.Sequential)]
    private class WINDOWPOS
    {
        public IntPtr hwnd;
        public IntPtr hwndInsertAfter;
        public int x;
        public int y;
        public int cx;
        public int cy;
        public int flags;
    };

    private delegate void ResizeChildDelegate(WINDOWPOS wpos);

    private void ResizeChild(WINDOWPOS wpos)
    {
        // verify if it's the right instance of MyPanel if needed
        if ((this.Controls.Count == 1) && (this.Controls[0] is Panel))
        {
            Panel child = this.Controls[0] as Panel;

            // stop window redraw to avoid flicker
            SendMessage(new HandleRef(child, child.Handle), WM_SETREDRAW, 0, 0);

            // start a new stack of SetWindowPos calls
            SetWindowPos(new HandleRef(child, child.Handle), new HandleRef(null, IntPtr.Zero),
            0, 0, wpos.cx, wpos.cy, SWP_NOACTIVATE | SWP_NOZORDER);

            // turn window repainting back on 
            SendMessage(new HandleRef(child, child.Handle), WM_SETREDRAW, 1, 0);

            // send repaint message to this control and its children
            this.Invalidate(true);
        }
    }

    protected override void WndProc(ref Message m)
    {
        if (m.Msg == WM_WINDOWPOSCHANGING)
        {
            WINDOWPOS wpos = new WINDOWPOS();
            Marshal.PtrToStructure(m.LParam, wpos);

            Debug.WriteLine("WM_WINDOWPOSCHANGING received by " + this.Name + " flags " + wpos.flags);

            if (((wpos.flags & (SWP_NOZORDER | SWP_NOACTIVATE)) == (SWP_NOZORDER | SWP_NOACTIVATE)) &&
            ((wpos.flags & ~(SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE)) == 0))
            {
                if ((wpos.cx != this.Width) || (wpos.cy != this.Height))
                {
                    BeginInvoke(new ResizeChildDelegate(ResizeChild), wpos);
                    return;
                }
            }
        }

        base.WndProc(ref m);
    }
}

Note: The change in WINDOWPOS from value type to reference type is intentional. Using a reference type reduces the number of copies to just one (the initial marshal)(**).

Updated Again I just noticed that the code originally made the p/invoke declarations public. Never, ever expose p/invoke outside of a class(*). Write managed methods that invoke private p/invoke declarations if your intent is to expose the capabilities provided; which in this case is not true, the p/invoke is strictly internal.

(*) Ok, one exception. You're creating a NativeMethods, UnsafeNativeMethods, etc. Which is the recommended way to do p/invoke by FxCop.

Updated

(**) I was asked (elsewhere) to describe precicely why using a reference type here is better, so I've added that info here. The question I was asked was, "Doesn't this add memory pressure?"

If WINDOWPOS was a value type, this would be the sequence of events:

1) Copy from unmanaged to managed memory

WINDOWPOS wpos = Marshal.PtrToStructure(m.LParam, typeof(WINDOWPOS));

2) Second copy?

BeginInvoke(new ResizeChildDelegate(ResizeChild), wpos);

Wait! The signature of BeginInvoke is (Delegate, params object[]). That means wpos is going to get boxed. So yes, a second copy occurs here: The boxing operation.

BeginInvoke will add the delegate and object[] to an invocation list and post a registered window message. When that message is removed from the queue by the message pump, the delegate will be called with the object[] parameters.

3) Unbox and copy for ResizeChild call.

At this point you can see that the number of copies isn't even the issue. The fact that it gets converted to a reference type (boxed) means that we are better off making it a reference type to begin with.




回答2:


An unsafe code is capable of corrupting the managed heap. As such, anything that runs in the same process can be affected.

This includes all other libraries and potentially all other AppDomains in the same process.


UPDATE

Here is an example: http://blogs.msdn.com/b/tess/archive/2006/02/09/net-crash-managed-heap-corruption-calling-unmanaged-code.aspx


UPDATE 2

Is unsafe code that is written diligently bad?

No. There are tons of unsafe code in the .NET framework itself. Examples many, but here is one in the System.String:

public static unsafe string Copy(string str)
{
    if (str == null)
    {
        throw new ArgumentNullException("str");
    }
    int length = str.Length;
    string str2 = FastAllocateString(length);
    fixed (char* chRef = &str2.m_firstChar)
    {
        fixed (char* chRef2 = &str.m_firstChar)
        {
            wstrcpyPtrAligned(chRef, chRef2, length);
        }
    }
    return str2;
}


来源:https://stackoverflow.com/questions/5677346/does-unsafe-code-have-any-effect-on-safe-code

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!