2010-01-28 5 views
4

У меня возникли проблемы, когда я пытаюсь определить, действительна ли ссылка на объект. Но это, кажется, возвращает странные результаты.Как проверить, действительна ли ссылка на объект?

procedure TForm1.Button1Click(Sender: TObject); 
    var form1 : TForm; 
     ref2 : TControl; 
begin 
    form1 := TForm.Create(nil); 
    form1.Name := 'CustomForm'; 
    form1.Parent := self; //Main Form 

    form1.Show; 

    ref2 := form1; 
    showmessage(ref2.ClassName+' - '+ref2.Name+' - '+BoolToStr(ref2.visible,true)); 
    freeandnil(form1); 
    showmessage(ref2.ClassName+' - '+ref2.Name+' - '+BoolToStr(ref2.visible,true)); 
end; 

Первый возвращает ShowMessage - "ТГогт - CustomForm - True" (Так же, как я бы ожидать, что это).

Второе возвращение showmessage - «TForm - - False». Я действительно надеялся на какое-то нарушение прав доступа, которое я мог бы затем поймать и узнать, что ссылка недействительна.

В моем приложении мне нужно скомпилировать список случайных потомков TForm по мере их создания, а затем проверить позже, если они ушли (или не видны). К сожалению, это система с плагинами, поэтому я могу изменить все эти Формы, чтобы опубликовать сообщение «Я сделал сообщение».

Может ли такой код быть безопасным для использования (при условии, что я действительно проверяю нарушения доступа)? У кого-нибудь есть идеи, что происходит.

Благодаря

+0

Пожалуйста, смотрите более ранний вопрос об использовании вещи после вызова FreeAndNil. http://stackoverflow.com/questions/364184/using-an-object-after-freeandnil –

+2

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

+0

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

ответ

6

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

Одна из возможностей заключается в переключении на другой менеджер памяти Delphi, который может определять использование освобожденных объектов. Например, у FastMM4 есть несколько «проверок памяти», которые очень полезны для отладки, но даже тогда вы не поймаете все эти ошибки немедленно.

download FastMM4 from SourceForge.

+4

Высокое, но не уверенное на 100%. В общем, решение состоит в том, чтобы иметь какой-то план или систему для того, кто несет ответственность за освобождение, кто не разделяет ссылки волей-неволей. –

+0

+1 для упоминания FastMM4 –

+0

Я решил реализовать по-другому, когда мне не нужно выяснить, действительно ли ссылки действительны. Вышеупомянутый код действительно работал, но я не хочу полагаться на хакеры/ошибки/несогласованности памяти, которые могут измениться позже. Спасибо – tmjac2

2

После

freeandnil(form1); 

менеджер памяти Delphi просто отмечает память, выделенную form1 как свободные, но все form1 данные по-прежнему существует, и может быть доступен через ref2, пока менеджер памяти не повторно использовать освободившуюся память для некоторых другой объект (ы).

Вы не можете проверить этот путь, если ref2 ссылается на действительный объект или нет. Подобный код не может быть безопасным, на самом деле это ошибка. Если вы хотите получить доступ к нарушению 100% изменить код следующим образом (здесь ref2 = ноль, если form1 освобождается):

procedure TForm1.Button1Click(Sender: TObject); 
    var form1 : TForm; 
     ref2 : ^TControl; 
begin 
    form1 := TForm.Create(nil); 
    form1.Name := 'CustomForm'; 
    form1.Parent := self; //Main Form 

    form1.Show; 

    ref2 := @form1; 
    showmessage(ref2^.ClassName+' - '+ref2^.Name+' - '+BoolToStr(ref2^.visible,true)); 
    freeandnil(form1); 
    showmessage(ref2^.ClassName+' - '+ref2^.Name+' - '+BoolToStr(ref2^.visible,true)); 
end; 
1

В подобном случае я использую объект одноплодного, который держит список все созданные формы. В каждой форме есть поле со ссылкой на этот объект.

TMyForm = class(TForm) 
private 
    //*** This is the reference to the singleton... 
    FFormHandler: TFormHandler; 
public 
    ... 
    //*** you might want to publish it as a property: 
    property FormHandler: TFormHandler read FFormHandler write FFormHandler; 
end; 

Вы можете установить эту ссылку, например. при вызове конструктора:

TMyForm.Create(aFormHandler: TFormHandler; aOwner: TComponent) 
begin 
    FFormHandler := aFormHandler; 
    inherited Create(aOwner); 
end; 

(Или вы можете установить поле извне непосредственно после создания формы, если вы не хотите, чтобы изменить параметры конструктора).

Когда форма IST уничтожены уведомляет обработчик и говорит ему, чтобы удалить форму из списка - что-то вроде этого:

TMyForm.Destroy(Sender: TObject); 
begin 
    FFormHandler.RemoveFromFormList(Self); 
    inherited; 
end; 

(Детали трекового учета не включены в expample - например, метод «AddToFomList» или что-то, так что потребуется)

+0

Любите эту идею, но на самом деле не очень хорошо отвечаете на вопрос. В итоге я сделаю что-то похожее на это, заменив событие OnDestroy на другое событие (а затем называя оригинал), чтобы сохранить список активных форм. – tmjac2

-1

Если вы не можете проверить другим способом, вы можете использовать это в качестве последнего средства ±

function IsValidClass(Cls: TClass): Boolean; 
var 
    i: Integer; 
begin 
    for i := 0 to 99 do begin 
     Result := (Cls = TObject); // note that other modules may have a different root TObject! 
     if Result then Exit; 
     if IsBadReadPtr(Cls, sizeof(Pointer)) then Break; 
     if IsBadReadPtr(Pointer(Integer(Cls) + vmtParent), sizeof(Pointer)) then Break; 
     Cls := Cls.ClassParent; 
    end; 
    Result := False; 
end; 

function IsValidObject(Obj: TObject): Boolean; 
begin 
    Result := not IsBadReadPtr(Obj, sizeof(Pointer)) and IsValidClass(Obj.ClassType) and not IsBadReadPtr(Obj, Obj.InstanceSize); 
end; 

IsBadReadPtr происходит из Windows.

+0

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

+0

@ Rob: Да, вы правы. Я забыл упомянуть, что я использую это в сочетании с FastMM в режиме отладки. Я использую этот код в одном из наших обработчиков исключений, где я пытаюсь спасти как можно больше информации, доступной в файле журнала, перед завершением работы приложения. Я согласен с вами в том, что это для отладки, а не для регулярного использования. –

1

Нет надежного способа сделать то, что вы пытаетесь сделать, используя технику, которую вы пытаетесь сделать. Формы, которые «ушли», могут повторно использовать их память, возможно, даже для новой формы.

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

0

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

  • Вы 1 way of doing it по проверки, если ссылка на объект еще, что он должен быть на глядя на VMT. Эта идея была , впервые опубликованной Ray Lischner (кто выступал за именно по этой причине) и позже Hallvard Vassbotn: см. this SO answer.

  • Другой, лучше, но вводя основное замедление, заключается в использовании FastMM4 в FullDebugmode, чтобы он мог заменить все освобожденные объекты экземпляром TFreeObject вместо простого освобождения памяти в доступный пул.

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

6

Любой ТСотропепЬ (например, ТГогт потомок) может зарегистрироваться для получения уведомлений, когда другие компоненты будут уничтожены.

В вашей форме, вызовите FreeNotification (формы) для каждой формы, которую вы хотите получать уведомления о разрушении. Затем в той же форме переопределите метод Notification().Если какой-либо форме (или другой компонент), для которого вы назвали FreeNotification() разрушен, ваш метод Notification() будет называться с компонента параметра, ссылающегося форму и операции из opRemove.

Если я понял, что вы пытаетесь достичь, я думаю, что для этого достаточно информации, чтобы разработать подход к тому, что вам нужно.

+0

Это прекрасный способ сделать это. Я просто столкнулся с похожим сценарием, и это сработало отлично. Как ни странно, я никогда не видел этого, пока вы его не подняли. Спасибо – tmjac2

0

это так просто, как по сравнению с NIL:

// object declaration 

Type object; 

object = new Type(); 

... 


// here you want to be sure of the existance of the object: 

if (object <> nil) 

    object.free; 
+0

Нет, не шанс. Код MyObject.Free освобождает объект, но не устанавливает MyObject в nil. Для этого вы можете использовать FreeAndNil. Однако это не учитывает наличие нескольких ссылок на один и тот же объект. – tmjac2