Недопустимая операция указателя в TMonitor.Destroy

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

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

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

Изменить стек вызовов в момент возникновения проблемы:

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(???)

person Paul-Jan    schedule 14.01.2010    source источник


Ответы (4)


Указатель на 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;

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

Изменить:

Судя по трассировке стека, монитор действительно назначен. Чтобы узнать, почему я бы использовал точку останова по данным. Мне не удалось заставить их работать с 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. Когда форма будет создана, в окне сообщения будет показан адрес экземпляра монитора. Запишите адрес и нажмите ОК. Появится отладчик, и вы установите точку останова при доступе на запись к этому указателю, например:

ba w4 A32D00

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

person mghie    schedule 14.01.2010
comment
Хороший совет! Удалось воспроизвести проблему за пределами DLL, что очень помогает заставить IDE фактически останавливать работу в тот момент, когда что-то идет наперекосяк. - person Paul-Jan; 14.01.2010

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

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

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

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

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

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

person Rob Kennedy    schedule 14.01.2010
comment
Хорошие общие указатели, спасибо. IsMultithread определенно включен, он очень необходим и с Delphi 5. Стек вызовов из деструктора TMonitor не так полезен (как это часто бывает с стеками вызовов деструктора), я добавлю его к вопросу. FastMM следующий в моем списке. - person Paul-Jan; 14.01.2010
comment
Fulldebugmode FastMM, обычно мой лучший друг в подобных ситуациях, ничего не дает. - person Paul-Jan; 14.01.2010

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

PClass(TForm)^ := TMyOwnClass 

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

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

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

person Paul-Jan    schedule 18.01.2010

В Delphi есть два TMonitor:

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

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

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

person vcldeveloper    schedule 14.01.2010
comment
Я так не думаю. Любой модуль, в котором упоминается класс Forms, должен включать Forms в его предложение uses. Где бы это ни происходило, все элементы в Forms имеют более близкую область видимости, чем что-либо в System - самые последние использованные элементы модуля обнаруживаются первыми во время разрешения имен. Любой старый код, который использовал пустой идентификатор TMonitor для ссылки на Forms.TMonitor, будет продолжать ссылаться на этот класс. Только код, который не использует Forms, будет ссылаться на System.TMonitor, но в Delphi 5 такой код никогда бы не компилировался. Проблема в другом. - person Rob Kennedy; 14.01.2010
comment
Я не использую Forms.TMonitor. Я знаю, что делает Forms.TMonitor, я имею общее представление о том, что делает новый System.TMonitor, я просто не знаю, почему он взрывается мне в лицо (если мой код не использует это явно). - person Paul-Jan; 14.01.2010