Поскольку предыдущие предложения носят довольно общий характер, я подумал, что было бы полезно опубликовать свое собственное сражение с этим исключением с конкретными примерами кода, изменениями фона, которые я реализовал, чтобы вызвать это исключение, и тем, как я его решил .
Во-первых, короткая версия: я использовал собственную dll, написанную на C ++ (неуправляемую). Я передал массив определенного размера из моего исполняемого файла .NET. Неуправляемый код пытался записать в расположение массива, которое не было выделено управляемым кодом. Это вызвало повреждение памяти, которое позже было установлено как сборщик мусора. Когда сборщик мусора готовится собирать память, он сначала проверяет состояние памяти (и границы). Когда он находит коррупцию, BOOM .
Ниже приведена версия TL; DR:
Я использую неуправляемую 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);
В графическом интерфейсе можно перемещаться по разным страницам, содержащим различные графические элементы и пользовательский ввод. Предыдущий метод позволил мне получить данные для заполнения 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 (Object) ) по умолчанию для свойства IsKeepAlive
установлено true
. Но проблема более гнусна, чем эта. Даже если вы установите значение IsKeepAlive
в конструкторе этой страницы специально для false
, оно все равно останется сборщиком мусора, как если бы оно было true
. Теперь для многих моих страниц это было не страшно. У них были небольшие следы памяти, и не так много всего происходило. Но многие другие из этих страниц имели большие подробные графические изображения для иллюстрации. Вскоре обычное использование этого интерфейса операторами нашего оборудования вызвало огромные выделения памяти, которые никогда не очищались и в конечном итоге забивали все процессы на машине. После того, как стремительное развитие началось с цунами, а затем и приливов, я, наконец, решил устранить утечки памяти раз и навсегда. Я не буду вдаваться в подробности всех трюков, которые я реализовал, чтобы очистить память ( WeakReference s для изображений, отцепить обработчики событий в Unload (), используя специальный таймер, реализующий IWeakEventListener интерфейс и т.д ...). Ключевое изменение, которое я сделал, состояло в том, чтобы перейти к страницам, используя Uri вместо объекта ( NavigationService.Navigate Method (Uri) ). При использовании этого типа навигации есть два важных отличия:
IsKeepAlive
по умолчанию установлено на false
.
- Теперь сборщик мусора попытается очистить навигационный объект, как если бы
IsKeepAlive
было установлено на false
.
Так что теперь моя навигация выглядит так:
MainNavigation.MainNavigationProperty.Navigate(
new Uri("/Pages/UserInterfacePage.xaml", UriKind.Relative));
Здесь следует еще кое-что отметить: это влияет не только на то, как объекты очищаются сборщиком мусора, но и на то, как они изначально выделяются в памяти , как я скоро выясню.
Казалось, все работало отлично. Моя память быстро очищалась до моего начального состояния, когда я перемещался по страницам, интенсивно использующим графику, пока я не попал на эту конкретную страницу с конкретным вызовом dll dataSource, чтобы заполнить некоторые поля со списком. Тогда я получил это противное FatalEngineExecutionError
. После нескольких дней исследований и нахождения неясных предложений или весьма специфических решений, которые не относились ко мне, а также использования почти всех средств отладки в моем личном арсенале программирования, я, наконец, решил, что единственный способ, которым я действительно собираюсь прибить это Это была крайняя мера восстановления точной копии этой конкретной страницы, элемент за элементом, метод за методом, строка за строкой, пока я наконец не наткнулся на код, выдавший это исключение. Это было так утомительно и больно, как я намекаю, но я, наконец, отследил это.
Оказалось, что неуправляемая 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 будет иметь только два элемента выбора, в отличие от других ComboBox на странице, у всех из которых было три элемента выбора. Однако неуправляемый код не видел вещи так, как я это видел. Он получил массив, который я передал, и попытался записать массив size [3] в мой размер [2], и на этом все. * bang! * * crash! * Я изменил размер выделения на 3, и ошибка исчезла.
Теперь этот код уже работал без этой ошибки, по крайней мере, год. Но простой переход на эту страницу через Uri
в отличие от Object
вызвал сбой. Это подразумевает, что исходный объект должен быть размещен по-другому из-за метода навигации, который я использовал. Поскольку с моим старым методом навигации память просто складывалась на место и оставлялась, как мне показалось, вечной, казалось, не имеет значения, была ли она немного повреждена в одном или двух небольших местах. Как только сборщик мусора должен был что-то сделать с этой памятью (например, очистить ее), он обнаружил повреждение памяти и выдал исключение. По иронии судьбы, моей главной утечкой памяти было сокрытие фатальной ошибки памяти!
Очевидно, что мы собираемся пересмотреть этот интерфейс, чтобы избежать таких простых предположений, вызывающих такие сбои в будущем. Надеюсь, что это поможет некоторым другим узнать, что происходит в их собственном коде.