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

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

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

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

Спасибо Brian

11
задан Brian Frost 29 June 2010 в 09:19
поделиться

7 ответов

Эта ошибка выглядит так, как будто вы используете 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: 2010701 - подчеркнул биты, упомянутые в комментарии Брайана.

13
ответ дан 3 December 2019 в 01:20
поделиться

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

13
ответ дан 3 December 2019 в 01:20
поделиться

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

2
ответ дан 3 December 2019 в 01:20
поделиться

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

  1. Используйте FastMM в режиме полной отладки, как предложил Gabr (я думаю, что вы уже это сделали, глядя на шаблон 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.

Edit: добавлен шаг 5

6
ответ дан 3 December 2019 в 01:20
поделиться

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

FInterfacedObject.GetInterface 

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

FInterfacedObject := TInterfacedObjectClass.Create.

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

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

if assigned(FInterfacedObject) then
    FInterfacedObject.Free;

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

2
ответ дан 3 December 2019 в 01:20
поделиться

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

3
ответ дан 3 December 2019 в 01:20
поделиться

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

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
ответ дан 3 December 2019 в 01:20
поделиться
Другие вопросы по тегам:

Похожие вопросы: