23 февраля 2013 г.

Всплывающие подсказки и QC85705

В VCL есть незакрытый баг QC85705. Он был открыт в июне 2010 года для Delphi 2010, но присутствует и в старых, и в новых версиях.

В ходе разработки одного из плагинов для ЛОЦМАН:PLM я столкнулся с ошибкой «Access violation at address...» при выгрузке плагина. Ошибка возникала не каждый раз, а только иногда, и приводила к краху всего приложения.

При запуске под отладчиком, в качестве адреса ошибки показывалась пустая область памяти, не занятая ни кодом, ни данными. Судя по всему, что-то портило память. Но с другой стороны никакого сложного кода в плагине не было, использовались давно проверенные и отлаженные решения.


После некоторого времени размышлений, я обратил внимание на адрес загрузки плагина $03500000 — он был немного меньше адреса возникновения ошибки $035AB036. Стало ясно, что после выгрузки DLL плагина остался выполняться поток, а так как код DLL уже выгружен, то возникает ошибка доступа к памяти. Добившись повторной загрузки плагина по адресу $03500000, я посмотрел в отладчике, что же находится по адресу $035AB036. В нормально режиме там находился код функции HintMouseThread из модуля Forms.pas.


К сожалению, на тот момент, времени дальше разбираться с ошибкой не было, и проблема была решена максимально просто: Application.ShowHint := False.

Второй подход


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

Баг, судя по всему, был либо в VCL, либо в компонентах Virtual Trees, так как с данной ошибкой я столкнулся только при работе с Virtual Trees. При работе с другими компонентами эта ошибка не проявляется. Поэтому я попутно отправил Issue 229 в Issue Tracker компонентов Virtual Trees, с вопросом, не могут ли они как-нибудь самостоятельно победить этот баг. Но там никто не откликнулся.

Решено было посмотреть, что по этому поводу пишут в Интернете. После недолгих поисков нашелся в Quality Central баг QC85705. Там был описан как раз мой случай — выгрузка DLL и оставшийся поток HintMouseThread. В качестве решения предлагалось исправить код TApplication.ActivateHint, но я решил поступить проще — написать маленькую функцию, которая вызовет UnhookHintHooks. Править VCL это все-таки крайний случай, и если без этого можно обойтись, то лучше поступить именно так. Решением стал такой модуль, включаемый в DLL:
unit VCLPatch;

interface

implementation

uses
    Classes, Controls, Forms;

type
    TApplicationHelper = class helper for TApplication
        procedure _CancelHint;
    end;

procedure TApplicationHelper._CancelHint;
begin
    Self.FHintControl := TControl(1);
    Self.CancelHint();
end;

initialization

finalization
    Application._CancelHint();

end.

Код использует Class Helpers и будет компилироваться только в новых версиях Delphi (начиная с версии 2009).

Комментариев нет:

Отправить комментарий