Как найти оборванный интерфейс, который вызывает AV в Delphi

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

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

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

Спасибо Brian

Ответ 1

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

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

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

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

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

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

procedure TFreedObject.InterfaceError;

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

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

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

- Йерун

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

Ответ 2

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

Ответ 3

Шаги для поиска проблемы:

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

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

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

Изменить: добавлен шаг 5

Ответ 4

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

Ответ 5

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

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;

Ответ 6

Одна вещь, которую нужно искать в вашем коде, - это

FInterfacedObject.GetInterface 

в той же области, что и

FInterfacedObject := TInterfacedObjectClass.Create.

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

Вы можете вызывать GetInterface из внутренней функции, если хотите, но если вы вызываете GetInterface в той же области, в которой вы создали FInterfacedObject, по какой-либо причине вы сбросите счетчик ссылок на 0 и освободите вещь, но она выиграла 't no nil, так что если вы делаете

if assigned(FInterfacedObject) then
    FInterfacedObject.Free;

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