由于前面的建议在本质上是相当通用的,我认为通过特定的代码示例发布我自己与此异常的斗争,我为导致此异常发生而实施的背景更改以及我如何解决它可能会有所帮助。
首先,TL;DR 版本:我使用的是用 C++(非托管)编写的内部 dll。我从我的 .NET 可执行文件中传入了一个特定大小的数组。非托管代码尝试写入未由托管代码分配的数组位置。这导致内存损坏,后来被设置为垃圾收集。当垃圾收集器准备收集内存时,它首先检查内存的状态(和边界)。当它发现腐败时,BOOM。
现在是详细版本:
我正在使用内部开发的非托管 dll,用 C++ 编写。我自己的 GUI 开发使用 C# .Net 4.0。我正在调用各种非托管方法。该 dll 有效地充当我的数据源。来自 dll 的外部定义示例:
[DllImport(@"C:\Program Files\MyCompany\dataSource.dll",
EntryPoint = "get_sel_list",
CallingConvention = CallingConvention.Winapi)]
private static extern int ExternGetSelectionList(
uint parameterNumber,
uint[] list,
uint[] limits,
ref int size);
然后我将这些方法包装在我自己的界面中,以便在我的整个项目中使用:
/// <summary>
/// Get the data for a ComboBox (Drop down selection).
/// </summary>
/// <param name="parameterNumber"> The parameter number</param>
/// <param name="messageList"> Message number </param>
/// <param name="valueLimits"> The limits </param>
/// <param name="size"> The maximum size of the memory buffer to
/// allocate for the data </param>
/// <returns> 0 - If successful, something else otherwise. </returns>
public int GetSelectionList(uint parameterNumber,
ref uint[] messageList,
ref uint[] valueLimits,
int size)
{
int returnValue = -1;
returnValue = ExternGetSelectionList(parameterNumber,
messageList,
valueLimits,
ref size);
return returnValue;
}
此方法的示例调用:
uint[] messageList = new uint[3];
uint[] valueLimits = new uint[3];
int dataReferenceParameter = 1;
// BUFFERSIZE = 255.
MainNavigationWindow.MainNavigationProperty.DataSourceWrapper.GetSelectionList(
dataReferenceParameter,
ref messageList,
ref valueLimits,
BUFFERSIZE);
在 GUI 中,人们浏览包含各种图形和用户输入的不同页面。以前的方法允许我获取要填充的数据ComboBoxes
。在此异常之前的时间我的导航设置和调用的示例:
在我的主机窗口中,我设置了一个属性:
/// <summary>
/// Gets or sets the User interface page
/// </summary>
internal UserInterfacePage UserInterfacePageProperty
{
get
{
if (this.userInterfacePage == null)
{
this.userInterfacePage = new UserInterfacePage();
}
return this.userInterfacePage;
}
set { this.userInterfacePage = value; }
}
然后,在需要时,我导航到该页面:
MainNavigationWindow.MainNavigationProperty.Navigate(
MainNavigation.MainNavigationProperty.UserInterfacePageProperty);
一切都运作良好,虽然我确实有一些严重的爬行问题。使用对象(NavigationService.Navigate Method (Object))进行导航时,该IsKeepAlive
属性的默认设置为true
。但问题远不止于此。即使您IsKeepAlive
将该页面的构造函数中的值专门设置false
为true
. 现在对于我的许多页面来说,这没什么大不了的。他们的内存占用很小,没有那么多事情发生。但这些页面中的许多其他页面上都有一些非常详细的大型图形,用于说明目的。没过多久,我们设备的操作员正常使用这个接口导致了巨大的内存分配,这些内存从未被清除并最终阻塞了机器上的所有进程。在最初的开发热潮从海啸平息到更多的潮汐之后,我终于决定一劳永逸地解决内存泄漏问题。我不会详细介绍我为清理内存而实施的所有技巧(WeakReference到图像,在 Unload() 上取消挂钩事件处理程序,使用实现IWeakEventListener的自定义计时器界面等...)。我所做的关键更改是使用 Uri 而不是对象( NavigationService.Navigate Method (Uri) )导航到页面。使用这种类型的导航有两个重要的区别:
IsKeepAlive
默认设置为false
。
- 垃圾收集器现在将尝试清理导航对象,就像
IsKeepAlive
设置为false
.
所以现在我的导航看起来像:
MainNavigation.MainNavigationProperty.Navigate(
new Uri("/Pages/UserInterfacePage.xaml", UriKind.Relative));
这里还有一点需要注意:这不仅会影响垃圾收集器清理对象的方式,还会影响它们最初在内存中的分配方式,我很快就会发现。
一切似乎都很好。当我浏览图形密集型页面时,我的内存会很快被清理到接近我的初始状态,直到我通过对 dataSource dll 的特定调用来填充一些组合框来点击这个特定页面。然后我得到了这个讨厌的FatalEngineExecutionError
。经过几天的研究并找到了不适合我的模糊建议或高度具体的解决方案,以及释放了我个人编程库中几乎所有的调试武器,我终于决定了我真正要解决这个问题的唯一方法down 是重建这个特定页面的精确副本的极端措施,逐个元素,逐个方法,逐行,直到我最终遇到引发此异常的代码。这就像我暗示的那样乏味和痛苦,但我终于找到了它。
事实证明,非托管 dll 分配内存以将数据写入我发送用于填充的数组中的方式。该特定方法实际上会查看参数编号,并根据该信息,根据它预期写入我发送的数组的数据量分配特定大小的数组。崩溃的代码:
uint[] messageList = new uint[2];
uint[] valueLimits = new uint[2];
int dataReferenceParameter = 1;
// BUFFERSIZE = 255.
MainNavigationWindow.MainNavigationProperty.DataSourceWrapper.GetSelectionList(
dataReferenceParameter,
ref messageList,
ref valueLimits,
BUFFERSIZE);
这段代码可能看起来与上面的示例相同,但有一点不同。我分配的数组大小是2而不是3。我这样做是因为我知道这个特定的 ComboBox 只有两个选择项,而页面上的其他 ComboBoxes 都具有三个选择项。然而,非托管代码并没有像我看到的那样看待事物。它得到了我提交的数组,并试图将一个大小为3的数组写入我的大小为2的分配中,就是这样。*砰!* *崩溃!* 我将分配大小更改为 3,错误消失了。
现在这个特定的代码已经运行了至少一年而没有出现这个错误。Uri
但是通过 a而不是a 导航到此页面的简单行为Object
导致崩溃出现。这意味着由于我使用的导航方法,必须以不同方式分配初始对象。由于使用我的旧导航方法,记忆只是堆积在适当的位置并按照我认为适合永恒的方式进行处理,因此它是否在一两个小位置有点损坏似乎并不重要。一旦垃圾收集器必须对该内存进行实际操作(例如清理它),它就会检测到内存损坏并抛出异常。具有讽刺意味的是,我的主要内存泄漏是掩盖了一个致命的内存错误!
显然,我们将审查这个界面,以避免这种简单的假设在未来导致此类崩溃。希望这有助于指导其他人找出他们自己的代码中发生了什么。