前段时间维护公司一个小程序时,在窗体添加自动下拉滚动条时,出了这个异常。System.AccessViolationException: 尝试读取或写入受保护的内存解决方案。
static class Program
{
private static Form1 M_WinForm;
/// <summary>
/// 应用程序的主入口点。
/// </summary>
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
M_WinForm = new Form1();
M_WinForm.EventWinFormOrder += new Form1.DelegateWinFormOrder(ControllerStart);
Application.Run(M_WinForm);
}
/// <summary>
/// 方法名称:控制器启动方法
/// 方法说明:窗体加载完成后,启动控制器,程序从这里开始运行
/// </summary>
private static void ControllerStart(object sender, Entity.UserEventArgsWinFormOrder e)
{
if (e.WinFormOrderLable == Entity.UserEventLabel.WinFormEvent.FormOpen)
{
Controller controller = new Controller(M_WinForm);
controller.ControllerStart();
}
}
前段时间维护公司一个小程序时,在窗体添加自动下拉滚动条时,出了这个异常。System.AccessViolationException: 尝试读取或写入受保护的内存解决方案。
该异常诡就诡异在,随机出现。循环调用该方法,也许5分钟,也许10分钟,出现程序就卡死,无法Try Catch,绕都绕不过。网上找相关异常资料,普遍给出的建议都是:netsh winsock reset,作用是重置 Winsock 目录。我自己下来试过几次,无果。
异常报错信息:
{System.Reflection.TargetInvocationException: 调用的目标发生了异常。 ---> System.AccessViolationException: 尝试读取或写入受保护的内存。这通常指示其他内存已损坏。
在 System.Windows.Forms.UnsafeNativeMethods.CallWindowProc(IntPtr wndProc, IntPtr hWnd, Int32 msg, IntPtr wParam, IntPtr lParam)
在 System.Windows.Forms.NativeWindow.DefWndProc(Message& m)
在 System.Windows.Forms.Control.DefWndProc(Message& m)
在 System.Windows.Forms.Control.WndProc(Message& m)
在 System.Windows.Forms.TextBoxBase.WndProc(Message& m)
在 System.Windows.Forms.RichTextBox.WndProc(Message& m)
在 System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
在 System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
在 System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
在 System.Windows.Forms.UnsafeNativeMethods.SendMessage(HandleRef hWnd, Int32 msg, Int32 wParam, EDITSTREAM lParam)
在 System.Windows.Forms.RichTextBox.StreamIn(Stream data, Int32 flags)
在 System.Windows.Forms.RichTextBox.StreamIn(String str, Int32 flags)
在 System.Windows.Forms.RichTextBox.set_SelectedText(String value)
在 System.Windows.Forms.TextBoxBase.AppendText(String text)
在 WinForm.Form1.UpdateTxtMethod(String messageIndex, String message) 位置 f:\FRMS\2-23调试\FRMS_DataHandle\WinForm\Form1.cs:行号 72
--- 内部异常堆栈跟踪的结尾 ---
在 System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
在 System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
在 System.Reflection.RuntimeMethodInfo.UnsafeInvoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
在 System.Delegate.DynamicInvokeImpl(Object[] args)
在 System.Windows.Forms.Control.InvokeMarshaledCallbackDo(ThreadMethodEntry tme)
在 System.Windows.Forms.Control.InvokeMarshaledCallbackHelper(Object obj)
在 System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
在 System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
在 System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
在 System.Windows.Forms.Control.InvokeMarshaledCallback(ThreadMethodEntry tme)
在 System.Windows.Forms.Control.InvokeMarshaledCallbacks()
在 System.Windows.Forms.Control.WndProc(Message& m)
在 System.Windows.Forms.ScrollableControl.WndProc(Message& m)
在 System.Windows.Forms.Form.WndProc(Message& m)
在 System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
在 System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
在 System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
在 System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
在 System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr dwComponentID, Int32 reason, Int32 pvLoopData)
在 System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
在 System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
在 System.Windows.Forms.Application.Run(Form mainForm)
在 Controller.Program.Main() 位置 f:\FRMS\2-23调试\FRMS_DataHandle\Controller\Program.cs:行号 26
在 System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
在 System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
在 Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
在 System.Threading.ThreadHelper.ThreadStart_Context(Object state)
在 System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
在 System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
在 System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
在 System.Threading.ThreadHelper.ThreadStart()}
经过仔细排查代码,终于找到问题解决方式了。鉴于目前网上还没有类似案例,记录下来照顾可能会踩到这个坑的同志。
static class Program
{
private static Form1 M_WinForm;
/// <summary>
/// 应用程序的主入口点。
/// </summary>
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
M_WinForm = new Form1();
M_WinForm.EventWinFormOrder += new Form1.DelegateWinFormOrder(ControllerStart);
Application.Run(M_WinForm);
}
/// <summary>
/// 方法名称:控制器启动方法
/// 方法说明:窗体加载完成后,启动控制器,程序从这里开始运行
/// </summary>
private static void ControllerStart(object sender, Entity.UserEventArgsWinFormOrder e)
{
if (e.WinFormOrderLable == Entity.UserEventLabel.WinFormEvent.FormOpen)
{
Controller controller = new Controller(M_WinForm);
controller.ControllerStart();
}
}
大家注意该程序主入口并不是在窗体类下Program进入的,而是在其他入口引入Form窗体,程序其它地方也在随时调取Form实体。
这种设计方式应该极力避免,我想这也是初学winForm的同学经常会犯的错吧。
设计的时候希望遵循一个原则:UI和逻辑层一定要分离。UI和逻辑层一定要分离。UI和逻辑层一定要分离
A与B分别新添加一个类,让A与B分别向该类去注册它们要与外部交互的控件,给这些控件全部以“全球范围内”的唯一ID,当然了,只要在你的方案中是唯一的就可以了。
然后再写一个Adapter,这个转换器仅与A与B的信息注册类进行交互。
这是最安全的做法,你不用更动你分别在A与B中的表示层与逻辑层,注意,Adapter仅用于通信,绝对不可对A与B的内容进行直接操作,这些操作必须由自身来完成,通信仅涉及到操作请求与操作结果的交互。
以上是最安全的做法。
唯一的ID使得转换器知道该向谁发出通信,你根本就不用去知道也绝对不应该去想办法知道对方将会怎么去完成请求,这个工作是由处理对像自身来完成的。这样也根本不会存在你的那种循环引用的问题。
它只是告诉你,你的做法可能会存在潜在的安全问题,哪怕你根本就不存在问题。
最后,我们将程序进行了一次大改,按照正常情况,从窗体开始启动程序,其它地方也不再声明引用窗体类的实体,所有有关窗体间的数据传递通过委托实现。最终解决了该问题。
需要值得大家注意的是,在你不是特别了解窗体之前,尽量按照规范来设计调用窗体。即使暂时程序没有出现问题,其结构也是很脆弱的。