Влияет ли небезопасный код на безопасный код? - PullRequest
15 голосов
/ 15 апреля 2011

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

В частности,

  1. Это какие-то проверки безопасности, которые не будут работать на полной DLL, потому что она помечена как небезопасная?
  2. Если DLL помечена как небезопасная, но методы помечены как небезопасные на самом деле не вызывается, это так же, как если бы DLL помечена как безопасно?
  3. Есть ли у них какие-либо преимущества при хранении небезопасного кода в отдельная DLL?

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

Ответы [ 2 ]

8 голосов
/ 15 апреля 2011

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

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


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;
}
3 голосов
/ 15 апреля 2011

Ответ на ваш вопрос: ключевое слово 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'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);
    }
}

Примечание : изменение в 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[]). Это означает, что wpos будет упакован. Так что да, здесь происходит вторая копия: операция бокса.

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

3) Распакуйте и скопируйте для ResizeChild звонка.

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

...