Небезопасный код влияет на безопасный код?

Как я понимаю, маркировка метода как небезопасного отключит некоторые проверки CLR на этом коде, но влияет ли это на остальную систему, которая является безопасной, за исключением того факта, что DLL/EXE может не работать в ненадежной среде.

В частности,

  • Это какие-либо проверки безопасности, которые не будут работать на полной dll, потому что они отмечены как небезопасные?
  • Если DLL помечена как небезопасная, но методы, помеченные как небезопасные, на самом деле не называется, это то же самое, что если бы DLL помечена как безопасно?
  • Получают ли они какие-либо преимущества во время выполнения для небезопасного кода в отдельная DLL?

У меня возникла проблема с перерисовкой вложенных элементов управления в 64-битных окнах в виде подробного здесь и одного из решений (которое, похоже, работает ) подразумевает небезопасный код, и я хотел бы понять, что добавление этого кода в мой проект.

Ответы

Ответ 1

Ответ на ваш вопрос: ключевое слово unsafe не означает "небезопасно", это означает "потенциально опасно". Компилятор и структура не могут работать, чтобы убедиться в его безопасности. Вы должны убедиться, что код не может выполнять небезопасные чтения или записи в память.

Я настоятельно рекомендую вам следовать этому совету, указанному в статье, которую вы указали:

1) Переработайте приложение, чтобы иметь меньше контейнеров и уменьшить количество уровней вложенности.

Если вы используете контейнеры с единственной целью компоновки управления, напишите свой собственный контейнер, который может выполнить все настройки с одним уровнем.

Обновление

Вы можете изменить код в этой статье, чтобы он не использовал указатели (т.е. не требует ключевого слова unsafe). Имейте в виду, что теперь это потребует сортировки, что означает дополнительное копирование. Это, вероятно, хорошо, потому что исходный код передает указатель WINDOWPOS из ОС в 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 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);
    }
}

Примечание. Изменение WINDOWPOS от типа значения до типа ссылки является намеренным. Использование ссылочного типа уменьшает количество копий только до одного (начального маршала) (**).

Обновлено снова Я только заметил, что код изначально сделал объявления p/invoke публичными. Никогда, никогда не выставляйте p/invoke вне класса (*). Напишите управляемые методы, которые вызывают частные объявления p/invoke, если вы намерены предоставить предоставленные возможности; который в этом случае неверен, p/invoke является строго внутренним.

(*) Хорошо, одно исключение. Вы создаете NativeMethods, UnsafeNativeMethods и т.д. Какой из рекомендуемых способов выполнить p/invoke с помощью FxCop.

Обновление

(**) Меня попросили (в другом месте) описать, почему использование ссылочного типа здесь лучше, поэтому я добавил эту информацию здесь. Вопрос, который меня задал, был: "Разве это не увеличивает давление памяти?"

Если WINDOWPOS - тип значения, это будет последовательность событий:

1) Копирование из неуправляемой в управляемую память

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

2) Вторая копия?

BeginInvoke(new ResizeChildDelegate(ResizeChild), wpos);

Подождите! Подпись BeginInvoke равна (Delegate, params object[]). Это означает, Итак, да, здесь происходит вторая копия: операция бокса.

BeginInvoke добавит делегат и объект [] в список вызовов и опубликует зарегистрированное сообщение окна. Когда это сообщение удаляется из очереди насосом сообщения, делегат будет вызываться с параметрами объекта [].

3) Отменить и скопировать вызов ResizeChild.

На этом этапе вы можете видеть, что количество экземпляров даже не проблема. Тот факт, что он преобразуется в ссылочный тип (в штучной упаковке), означает, что нам лучше сделать его ссылочным типом для начала.

Ответ 2

Небезопасный код способен повреждать управляемую кучу. Таким образом, все, что работает в одном и том же процессе, может быть затронуто.

Это включает в себя все остальные библиотеки и потенциально все остальные AppDomains в том же процессе.


UPDATE

Вот пример: http://blogs.msdn.com/b/tess/archive/2006/02/09/net-crash-managed-heap-corruption-calling-unmanaged-code.aspx


ОБНОВЛЕНИЕ 2

Небезопасный код, который написан неудобно плохо?

Нет. т небезопасного кода в самой платформе .NET. Примеры много, но здесь один из 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;
}