据我所知,将方法标记为不安全将禁用对该代码的一些CLR检查,但除了DLL / EXE可以的事实之外,这对系统的其他部分是否安全有任何影响不在不受信任的环境中运行。
特别是
我有问题在64位窗口上重绘嵌套控件详细here和解决方案(看起来工作的那个)涉及不安全的代码,我想了解添加这个的效果代码有我的项目。
答案 0 :(得分:8)
损坏托管堆的能力是不安全的代码。因此,在相同过程中运行的任何内容都会受到影响。
这包括同一过程中的所有其他库以及所有其他 AppDomains 。
是否写入了不安全的代码 孜孜不倦?
没有。 .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;
}
答案 1 :(得分:3)
您的问题的答案是:unsafe
关键字并不意味着“不安全”,它意味着“可能不安全”。编译器和框架无法确保它是安全的。您可以确保代码无法对内存执行不安全的读取或写入操作。
我强烈建议您遵循您链接的文章中提供的建议:
1)重新设计应用程序以使更少的容器和减少嵌套级别数。
如果您使用容器仅用于控制安排,请编写您自己的容器,以便在一个级别上完成所有安排。
<强>更新强>
您可以修改该文章中的代码,使其不使用指针(即不需要unsafe关键字)。请记住,这将需要编组,这意味着额外的复制。这可能是一件好事,因为原始代码将一个WINDOWPOS指针从OS传递给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
等。这是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 []参数调用该委托。
3)对ResizeChild
电话进行取消装箱和复制。
此时您可以看到副本数量甚至不是问题。它被转换为引用类型(盒装)这一事实意味着我们最好将它作为一个引用类型开始。