Недопустимая операция с указателем в TMonitor.Destroy - PullRequest
2 голосов
/ 14 января 2010

В настоящее время я работаю над переносом существующего приложения Delphi 5 на Delphi 2010.

Это многопоточная DLL (где потоки создаются Outlook), которая загружается в Outlook. При компиляции через Delphi 2010 каждый раз, когда я закрываю форму, я сталкиваюсь с «недопустимой операцией с указателем» внутри TMonitor.Destroy ... то есть в system.pas, то есть.

Поскольку это существующее и довольно сложное приложение, у меня есть лот направлений для изучения, а справка delphi даже не документирует едва документирует этот конкретный класс TMonitor для начала (я проследил это до некоторых постов Аллена Бауэра с дополнительной информацией) ... так что я решил сначала спросить, сталкивался ли кто-нибудь с этим раньше или у меня были какие-либо предложения о том, что может вызвать эту проблему. Для справки: я не использую функциональность TMonitor явно в своем коде, мы говорим о прямом порте кода Delphi 5 здесь.

Редактировать Callstack в момент возникновения проблемы:

System.TMonitor.Destroy
System.TObject.Free
Forms.TCustomForm.CMRelease(???)
Controls.TControl.WndProc(???)
Controls.TWinControl.WndProc((45089, 0, 0, 0, 0, 0, 0, 0, 0, 0))
Forms.TCustomForm.WndProc(???)
Controls.TWinControl.MainWndProc(???)
Classes.StdWndProc(15992630,45089,0,0)
Forms.TApplication.ProcessMessage(???)

Ответы [ 4 ]

7 голосов
/ 14 января 2010

Указатель на экземпляр System.Monitor каждого объекта сохраняется после всех полей данных. Если вы записываете слишком много данных в последнее поле объекта, может случиться так, что вы напишите поддельное значение по адресу монитора, что, скорее всего, приведет к падению, когда деструктор объекта попытается уничтожить поддельный монитор. Вы можете проверить, чтобы этот адрес был nil в методе BeforeDestruction ваших форм, для прямого порта Delphi 5 не должно быть назначено никаких мониторов. Что-то вроде

procedure TForm1.BeforeDestruction;
var
  MonitorPtr: PPMonitor;
begin
  MonitorPtr := PPMonitor(Integer(Self) + InstanceSize - hfFieldSize + hfMonitorOffset);
  Assert(MonitorPtr^ = nil);
  inherited;
end;

Если это проблема в вашем исходном коде, вы сможете обнаружить его в версии DLL вашей Delphi 5 с помощью диспетчера памяти FastMM4 со всеми активированными проверками. OTOH это также может быть вызвано увеличением размера символьных данных в сборках Unicode, и в этом случае они будут проявляться только в сборках DLL с использованием Delphi 2009 или 2010. По-прежнему будет хорошей идеей использовать последнюю версию FastMM4 со всеми проверками.

Edit:

Из вашей трассировки стека выглядит, что монитор действительно назначен. Чтобы выяснить, почему я бы использовал точку останова данных. Я не смог заставить их работать с Delphi 2009, но вы можете легко сделать это с WinDbg.

В обработчике OnCreate вашей формы укажите следующее:

var
  MonitorPtr: PPMonitor;
begin
  MonitorPtr := PPMonitor(Integer(Self) + InstanceSize - hfFieldSize + hfMonitorOffset);
  MessageDlg(Format('MonitorPtr: %p', [pointer(MonitorPtr)]), mtInformation,
    [mbOK], 0);
  DebugBreak;
  // ...

Теперь загрузите WinDbg, откройте и запустите процесс, который вызывает вашу DLL. Когда форма будет создана, в окне сообщения будет показан адрес экземпляра монитора. Запишите адрес и нажмите ОК. Появится отладчик, и вы установите точку останова на доступ к записи для этого указателя, например:

ба w4 A32D00

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

3 голосов
/ 14 января 2010

Недопустимая операция с указателем означает, что ваша программа пыталась освободить указатель, но с ней произошла одна из трех ошибок:

  • Он был выделен другим менеджером памяти.
  • Она уже была однажды освобождена.
  • Он никогда ничего не выделял.

Маловероятно, что у вас будет несколько менеджеров памяти, выделяющих TMonitor записей, поэтому я думаю, что мы можем исключить первую возможность.

Что касается второй возможности, если в вашей программе есть класс, который либо не имеет собственного деструктора, либо не освобождает память в своем деструкторе, то первое фактическое освобождение памяти для этого объекта может быть в TObject где он освобождает монитор объекта. Если у вас есть экземпляр этого класса, и вы пытаетесь освободить его дважды, эта проблема может появиться в виде исключения в TMonitor. Ищите двойные бесплатные ошибки в вашей программе. опции отладки в FastMM могут помочь вам в этом. Также, когда вы получите это исключение, используйте стек вызовов , чтобы узнать, как вы добрались до деструктора TMonitor.

Если третья возможность является причиной, значит, у вас повреждение памяти. Если у вас есть код, который делает предположения о размере объекта, то это может быть причиной. Начиная с Delphi 2009 TObject на четыре байта больше. Всегда используйте метод InstanceSize, чтобы получить размер объекта; не просто сложите размер всех его полей или используйте магическое число.

Вы говорите, что потоки созданы Outlook. Вы установили глобальную переменную IsMultithread? Ваша программа обычно устанавливает его в True, когда создает поток, но если вы не тот, кто создает потоки, он останется со значением по умолчанию False, которое влияет на то, заботится ли менеджер памяти о защите своих глобальных структур данных во время выделения и удаления , Установите значение True в главном программном блоке вашего файла DPR.

1 голос
/ 18 января 2010

После долгих копаний выясняется, что я неплохо справляюсь (читай: ужас, , но он правильно выполняет свою работу в наших приложениях delphi 5 целую вечность )

PClass(TForm)^ := TMyOwnClass 

где-то глубоко в недрах нашей прикладной среды. Очевидно, в Delphi 2010 есть некоторая инициализация класса, чтобы инициализировать «поле монитора», которого сейчас не было, в результате чего RTL попытался «освободить синхобъект» при уничтожении формы, потому что getFieldAddress вернул ненулевое значение. Тьфу.

Причина , почему мы делали этот хак в первую очередь, в том, что я хотел автоматически изменять createParams во всех экземплярах формы, чтобы получить форму без изменения размера. Я открою новый вопрос о том, как это сделать без взлома rtl (а пока просто добавлю красивый блестящий значок в формы).

Я отмечу предложение Мги в качестве ответа, потому что оно дало мне (и всем, кто читает эту ветку) очень глубокое понимание. Спасибо всем за участие!

0 голосов
/ 14 января 2010

В Delphi есть два TMonitor:

  1. System.TMonitor; которая является записью и используется для синхронизации потоков.
  2. Forms.TMonitor; это класс, представляющий подключенный монитор (устройство отображения).

System.TMonitor добавлен в Delphi с Delphi 2009; поэтому, если вы переносите код из Delphi 5, ваш код использовал Forms.TMonitor, а не System.TMonitor.

Я думаю, что имя класса указано без имени модуля в вашем коде, и это создает путаницу.

...