你的问题的答案是:"
unsafe
"关键字并不意味着“不安全”,而是“潜在不安全”。编译器和框架不能保证其安全性,需要你确保代码不会对内存执行不安全的读写操作。
我强烈建议你遵循链接文章中提供的建议:
1)重新设计应用程序,减少容器数量和嵌套层数。
如果你只是为了控制排列而使用容器,请编写自己的容器来完成所有单层排列。
更新:
你可以修改那篇文章中的代码,使其不使用指针(即不需要
unsafe
关键字)。请注意,这将现在需要进行编组,这意味着额外的复制。这可能是一件好事,因为原始代码正在将一个从操作系统传递到BeginInvoke的WINDOWPOS指针,而该指针并未在操作系统生成它的同一调度事件期间执行。换句话说,那段代码已经有异味了。
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)
{
if ((this.Controls.Count == 1) && (this.Controls[0] is Panel))
{
Panel child = this.Controls[0] as Panel;
SendMessage(new HandleRef(child, child.Handle), WM_SETREDRAW, 0, 0);
SetWindowPos(new HandleRef(child, child.Handle), new HandleRef(null, IntPtr.Zero),
0, 0, wpos.cx, wpos.cy, SWP_NOACTIVATE | SWP_NOZORDER);
SendMessage(new HandleRef(child, child.Handle), WM_SETREDRAW, 1, 0);
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: WINDOWPOS从值类型更改为引用类型是有意的。使用引用类型可以将副本数量减少到仅一个(初始marshal)(**)。
再次更新:我刚刚注意到,代码最初使p/invoke声明为公共的。永远不要在类外部暴露p/invoke (*).编写调用私有p/Invoke声明的托管方法,如果您的意图是公开提供的功能; 在这种情况下不是这样,p/Invoke是严格内部的。
(*) 好吧,有一个例外。您正在创建一个NativeMethods
,UnsafeNativeMethods
等,这是FxCop建议进行p/invoke的方式。
更新
(**) 我被要求(在其他地方)精确描述为什么在此处使用引用类型更好,因此我在此添加了该信息。我被问到的问题是:“这不会增加内存压力吗?”
如果WINDOWPOS
是值类型,则会发生以下事件序列:
1)从非托管内存复制到托管内存
WINDOWPOS wpos = Marshal.PtrToStructure(m.LParam, typeof(WINDOWPOS))
2) 第二份拷贝?
BeginInvoke(new ResizeChildDelegate(ResizeChild), wpos)
等等!
BeginInvoke
的签名是
(Delegate, params object[])
。这意味着wpos将被装箱。因此,在此处发生了第二个副本:装箱操作。
BeginInvoke
会将委托和object[]添加到调用列表中,并发布注册的窗口消息。当消息泵从队列中删除该消息时,将使用object[]参数调用委托。
3)为
ResizeChild
调用进行拆箱和复制。
此时可以看出,复制的数量甚至不是问题。它被转换为引用类型(装箱),这意味着最好一开始就将其作为引用类型处理。