简体   繁体   English

不安全的代码对安全代码有影响吗?

[英]Does unsafe code have any effect on safe code?

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. 据我所知,将方法标记为不安全将禁用对该代码的一些CLR检查,但除了DLL / EXE无法运行之外,这对系统的其他部分是否安全有任何影响。不受信任的环境。

In particular, 特别是,

  1. Are they are any safety checks that will not work on the complete dll because it is marked as unsafe? 它们是否是任何不能在完整dll上运行的安全检查,因为它被标记为不安全?
  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? 如果DLL被标记为不安全,但标记为不安全的方法实际上没有被调用,这是否与DLL标记为安全相同?
  3. Are they any run-time benefits on keeping the unsafe code in a separate DLL? 将不安全的代码保存在单独的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. 我有详重绘在64位Windows嵌套的控制问题, 在这里和一个解决方案(即似乎工作的一个)涉及不安全的代码,我想明白,添加此代码有我的项目的效果。

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. 这包括同一进程中的所有其他以及可能的所有其他AppDomain


UPDATE UPDATE

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


UPDATE 2 更新2

Is unsafe code that is written diligently bad? 是不是很难写的不安全代码?

No. There are tons of unsafe code in the .NET framework itself. 没有。在.NET框架本身中有大量不安全的代码。 Examples many, but here is one in the System.String : 示例很多,但这里是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;
}

The answer to your question is: The unsafe keyword does not mean "unsafe", it means "potentially unsafe". 您的问题的答案是: 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 . 1)重新设计应用程序以减少容器 数量减少嵌套级别

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 (ie doesn't require the unsafe keyword). 您可以修改该文章中的代码,使其不使用指针(即不需要unsafe关键字)。 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. 这可能是一件好事,因为原始代码将WINDOWPOS指针从OS传递给BeginInvoke,它在操作系统生成指针的同一个调度事件期间不执行。换句话说,该代码已经臭了。

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. 注意 :WINDOWPOS从值类型到引用类型的更改是有意的。 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. 再次更新我刚刚注意到代码最初使p / invoke声明公开。 Never, ever expose p/invoke outside of a class(*). 永远不要在类(*)之外公开​​p / invoke。 Write managed methods that invoke private p/invoke declarations if your intent is to expose the capabilities provided; 编写托管方法,如果您的目的是公开所提供的功能,则调用私有p / invoke声明; which in this case is not true, the p/invoke is strictly internal. 在这种情况下不是真的,p / invoke是严格内部的。

(*) Ok, one exception. (*)好的,一个例外。 You're creating a NativeMethods , UnsafeNativeMethods , etc. Which is the recommended way to do p/invoke by FxCop. 您正在创建NativeMethodsUnsafeNativeMethods等。这是FxCop进行p / invoke的推荐方法。

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: 如果WINDOWPOS是值类型,则这将是事件序列:

1) Copy from unmanaged to managed memory 1)从非托管内存复制到托管内存

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

2) Second copy? 2)第二份?

BeginInvoke(new ResizeChildDelegate(ResizeChild), wpos);

Wait! 等待! The signature of BeginInvoke is (Delegate, params object[]) . BeginInvoke的签名是(Delegate, params object[]) That means wpos is going to get boxed. 这意味着wpos将被装箱。 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. BeginInvoke将委托和对象[]添加到调用列表并发布已注册的窗口消息。 When that message is removed from the queue by the message pump, the delegate will be called with the object[] parameters. 当消息泵从队列中删除该消息时,将使用object []参数调用该委托。

3) Unbox and copy for ResizeChild call. 3)Unbox并复制ResizeChild调用。

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. 它被转换为引用类型(盒装)的事实意味着我们最好将其作为引用类型开始。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM