Как найти висячий интерфейс, который вызывает AV в Delphi - PullRequest
11 голосов
/ 29 июня 2010

У меня сложное приложение, в которое я только что внес некоторые изменения, добавив пару новых классов с интерфейсами и удалив некоторые другие. Функционально все это работает, но я получаю нарушение прав доступа сразу после процедуры Destroy класса:

"Нарушение доступа по адресу 0040B984 в модуле 'xxxx.exe'. Чтение адреса 80808088".

Я знаю, что это в коде класса «Завершить», и, конечно, если я зайду в разборку (Delphi 2010), я пойму смысл AV. Я не могу найти простой способ узнать, какая из моих переменных вызывает это, хотя. Есть ли какая-то процедура, которой нужно следовать при таком глубоком проникновении, которая дала бы мне подсказку об экземпляре, о котором идет речь?

Спасибо Brian

Ответы [ 7 ]

13 голосов
/ 29 июня 2010

Эта ошибка выглядит так, как будто вы используете FastMM для управления памятью.Ошибка указывает, что вы ссылаетесь на указатель, который был очищен FastMM со значением DebugFillDWord.

Это означает, что вы используете интерфейс, который ссылается на объект, который уже был освобожден.
Это также означает, что вы не включили CatchUseOfFreedInterfaces.

. Для того, чтобы изменить их и отладить, вы не можете делать со стандартным FastMM, который поставляется с Delphi.
Вам нужно будет загрузить FastMM (версия 4.94).

После загрузки:

Как gabr уже упоминается, внутри FastMM4Options.inc, убедитесь, что вы включили FullDebugMode и CatchUseOfFreedInterfaces (который отключает CheckUseOfFreedBlocksOnShutdown, но вы не интересуетесь последним прямо сейчас).
Возможно, вы также захотите включить RawStackTraces;это зависит от того, достаточно ли хороша текущая трассировка стека.

Когда вы выполните эти настройки, , а затем запустите ваше приложение с FastMM через отладчик и установите точку останова для этого метода внутри.блок FastMM4:

procedure TFreedObject.InterfaceError;

Я немного изменил свой блок FastMM4, чтобы получить больше контекстной информации;Я могу поделиться этим с вами (я уже отправил его по почте команде FastMM4, но он еще не был включен в официальные источники).

Я написал довольно плотную статью в блоге об отладке с помощью FastMM, который может вам помочь.
Оставьте здесь записку, если это требует дальнейшего объяснения: -)

Удачи, и дайте нам знать, если вам нужны дальнейшие указания.

-jeroen

Edit : 20100701 - выделены биты, упомянутые в комментарии Брайана.

13 голосов
/ 29 июня 2010

В большинстве случаев такие ошибки могут быть обнаружены с помощью FastMM и компиляции приложения с условными определениями FullDebugMode и CatchUseOfFreedInterfaces . Просто убедитесь, что вы поставили FastMM4 на первое место в списке использований dpr.

6 голосов
/ 29 июня 2010

Шаги, чтобы найти проблему:

  1. Используйте FastMM в fulldebugmode, как предложил Габр (я думаю, что вы уже это делаете, глядя на паттерн 808080).
  2. Установите все интерфейсы, которые вы явно используете в своем классе, на nil в вашей процедуре Destroy.
  3. Поставьте точку останова в начале вашей процедуры Destroy.
  4. Теперь пройдитесь по вашей процедуре Destroy, когда обнуляете висячий интерфейс, вы получите Access Violation и узнаете, какой это был интерфейс.
  5. Если у вас все еще есть AV после того, как вы без проблем обработали все свои интерфейсы, выполните шаги 2 - 5 для родительского класса.

У меня тоже были эти проблемы, и описанный выше метод помог мне их найти. Мои проблемы были вызваны TComponents, которые реализовали интерфейсы. Скажем, у вас есть ComponentA и ComponentB, ComponentB реализует интерфейс. Вы назначаете ComponentB (или его интерфейс) для ComponentA и сохраняете ссылку на интерфейс. Теперь ComponentB уничтожается, но ComponentA не знает об этом. Когда вы уничтожаете ComponentA, он обнуляет интерфейс, вызывает метод _Release и вы получаете AV.

Решением этой проблемы является работа с TComponent.FreeNotification. Когда вы получаете бесплатное уведомление от ComponentB, вы обнуляете интерфейс в ComponentA. Я ничего не знаю о вашем коде, но если ваша проблема похожа, вы можете работать и с FreeNotifications.

Редактировать: добавлен шаг 5

3 голосов
/ 30 июня 2010

Я делаю подобное, и следующий код в деструкторе вашего объекта поможет

Destructor TMyObjectThatIAmDoingManualRefCounting.Destroy;
begin
  if FMyRefCount<>0 then
    messageDlg('You dork, you called Free on me when someone still had references to me');

  inherited;
end;

Тогда вы можете хотя бы выяснить, где вы освобождаете объект ненадлежащим образом. Возможно, вы освобождаете это слишком рано. Своевременное освобождение объекта на самом деле не вызовет проблемы, но когда вы освобождаете объект, который содержит ссылку на интерфейс уже освобожденного объекта, вы получите ошибку.

Еще одна вещь, которую вы можете сделать, это установить точки останова в ваших методах addref и release и отследить, кто хранит ссылки на интерфейс и, если тот же объект освобождает их впоследствии.

Также общая проблема заключается в следующем, если вы получаете интерфейс и освобождаете объект тем же способом

var
  o:TSomeObject;
begin
  o:=TSomeObject.Create;
  (o as ISomeInterface).DoSomething;
  o.free
end;

Это вызовет AV в конце метода, потому что компилятор создает поддельную переменную интерфейса, которая освобождается в конце метода.

вам нужно будет сделать это

var
  o:TSomeObject;
  i:ISomeInterface;
begin
  o:=TSomeObject.Create;
  i:=(o as ISomeInterface); // or Supports or whatever
  i.DoSomething;
  i:=nil;
  o.free
end;
3 голосов
/ 29 июня 2010

Подобная ошибка, которая меня укусила, была ссылка на интерфейс, которая была установлена ​​для существующего объекта, счетчик ссылок на интерфейс не будет автоматически уменьшаться, когда объект-владелец освобождается. Это можно решить с помощью if Assigned(FMyInterface) then FMyInterface := nil; в деструкторе объекта-владельца.

2 голосов
/ 29 июня 2010

Может быть с таким инструментом, как EurekaLog?http://delphi.about.com/od/productreviews/ss/eurekalog.htm

2 голосов
/ 29 июня 2010

В вашем коде нужно искать одну вещь:

FInterfacedObject.GetInterface 

в том же объеме, что и

FInterfacedObject := TInterfacedObjectClass.Create.

где FInterfacedObject - переменная класса.

Вы можете вызывать GetInterface из внутренней функции, если хотите, но если вы вызываете GetInterface в той же области, в которой вы создали FInterfacedObject, по любой причине вы сбросите счетчик ссылок до 0 и освободите объект, но он выиграл ноль, так что если вы делаете

if assigned(FInterfacedObject) then
    FInterfacedObject.Free;

вы получите нарушение прав доступа.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...