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