Ответ 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
.
На этом этапе вы можете видеть, что количество экземпляров даже не проблема. Тот факт, что он преобразуется в ссылочный тип (в штучной упаковке), означает, что нам лучше сделать его ссылочным типом для начала.