Недопустимая операция с указателем в TMonitor.Уничтожить

StackOverflow https://stackoverflow.com/questions/2064315

  •  20-09-2019
  •  | 
  •  

Вопрос

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

Это многопоточная DLL (где потоки создаются Outlook), которая загружается в Outlook.При компиляции через Delphi 2010 всякий раз, когда я закрываю форму, я сталкиваюсь с "недопустимой операцией с указателем" внутри TMonitor.Уничтожить...то есть тот, что в 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(???)
Это было полезно?

Решение

Указатель на 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 со всеми проверками.

Редактировать:

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

Другие советы

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

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

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

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

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

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

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

PClass(TForm)^ := TMyOwnClass 

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

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

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

В Delphi есть два TMonitor:

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top