Delphi7, передавая интерфейс объекта - вызывает Invalid Pointer Operation при освобождении объекта - PullRequest
3 голосов
/ 17 сентября 2009

У меня есть класс, который реализует интерфейс, который сделан доступным для плагинов. Объявление класса довольно просто. Существует только один экземпляр этого класса для всего приложения. Когда вызывается функция, которая возвращает интерфейс, она вызывает _AddRef для извлеченного интерфейса перед передачей его в качестве результата. К сожалению, это работает, пока я не попытаюсь освободить объект (см. Раздел «Завершение») - он сообщает о недопустимой операции указателя. Если я закомментирую его, он будет работать нормально (однако FastMM сообщает об утечках памяти, поэтому объект не освобождается).

Вот часть кода в функции, которая возвращает интерфейс (фактически это переопределенный QueryInterface моего класса "ServicesManager").

if ConfigManager.GetInterface(IID, obj) then
begin
  ISDK_ConfigManager(obj)._AddRef;
  result:= 0;
end

и код класса ConfigManager ...

type
  TConfigManager = class(TInterfacedObject, ISDK_ConfigManager)
  private
  ... 
  end;

var
  ConfigManager: TConfigManager;
implementation

...

initialization
  ConfigManager:= TConfigManager.Create();
finalization
  if ConfigManager <> nil then
    FreeAndNil(ConfigManager); //if I comment it out, it leaks the memory but no Invalid Ptr. Op. raises

Что я делаю не так? Мне нужно передать ссылку именно на этот экземпляр ConfigManager.

Ответы [ 4 ]

10 голосов
/ 17 сентября 2009

Совет номер один, который вы услышите при работе с интерфейсами, заключается в том, чтобы никогда не смешивать ссылки на интерфейсы с ссылками на объекты . Это означает, что как только вы начинаете ссылаться на объект через ссылку на интерфейс, вы перестаете ссылаться на него через ссылку на объект. Когда-либо.

Причина в том, что при первом назначении интерфейсной переменной счетчик ссылок объекта становится равным 1. Когда эта переменная выходит из области видимости или получает новое значение, счетчик ссылок становится равным нулю, и объект освобождается сам. Это все без какой-либо модификации исходной переменной объекта-ссылки, поэтому, когда вы позже попытаетесь использовать эту переменную, это не пустой указатель, а объект, на который она ссылается, ушел - это свисающая ссылка . Когда вы пытаетесь освободить то, что не существует, вы получаете исключение операции неверного указателя.

Объявите вашу переменную ConfigManager как интерфейс. Не освобождай это сам. Как только вы это сделаете, вы можете переместить всю декларацию TConfigManager в раздел реализации, потому что ни один код вне этого модуля никогда не будет ссылаться на него.

Кроме того, редко есть какая-либо причина предоставить собственную реализацию QueryInterface. (Вы сказали, что отвергли это, но это невозможно, потому что это не виртуально.) Достаточно того, что предоставлено TInterfacedObject. Тот, который вы предоставляете, на самом деле вызывает утечку памяти, потому что вы увеличиваете счетчик ссылок, когда этого не должно быть. GetInterface уже вызывает _AddRef (выполняя назначение интерфейса), поэтому вы возвращаете объекты с завышенным количеством ссылок.

2 голосов
/ 17 сентября 2009

Вы сказали, что это система плагинов? Вы загружаете свои плагины как BPL? Я столкнулся с этой проблемой на прошлой неделе, на самом деле. Вы не можете полагаться на финализация для очистки ссылок на интерфейс. Вы должны обязательно очистить их перед тем, как выгрузить плагин, иначе его пространство памяти станет недействительным.

Редактировать: Под "очисткой ссылок на интерфейсы" я имею в виду вызов _Release для них, либо установив вручную значение nil, либо выпустив ссылки из области видимости. Если ваш диспетчер интерфейсов содержит ссылки на интерфейсы для плагинов, они будут очищены при разрушении диспетчера интерфейсов.

1 голос
/ 17 сентября 2009

Я полностью согласен с Робом.

Скорее всего, помогает переписать код инициализации, как показано ниже.

Теперь ConfigManager имеет тип ISDK_ConfigManager, и, присваивая ему значение nil, счетчик ссылок будет уменьшаться. Когда счетчик ссылок становится равным нулю, он автоматически освобождается.

type
  TConfigManager = class(TInterfacedObject, ISDK_ConfigManager)
  private
  ...
  end;

var
  ConfigManager: ISDK_ConfigManager;
implementation

...

initialization
  ConfigManager:= TConfigManager.Create();
finalization
  ConfigManager := nil;
end;

- Йерун

0 голосов
/ 30 сентября 2009

имеет ли класс TConfigManager какой-либо метод, объявленный как "опубликованный"?

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