Проблема исключения нарушения прав доступа вызвана вызовом метода Destroy
для уже уничтоженного объекта. Позвольте мне объяснить ситуацию.
Представьте, что у вас есть 3 вкладки со следующими индексами и массив Browsers
со следующими экземплярами браузера:
ChromeTabs.Tabs Browsers
---------------------- ----------------------
Index Tab name Index Browser
---------- ---------- ---------- ----------
0 Tab 1 0 Browser 1
1 Tab 2 1 Browser 2
2 Tab 3 2 Browser 3
Теперь вы нажимаете кнопку закрытия средней вкладки (вкладка проиндексирована цифрой 1) и в OnButtonCloseTabClick
запускаете это:
procedure TForm1.ChromeTabsButtonCloseTabClick(Sender: TObject;
ATab: TChromeTab; var Close: Boolean);
begin
// the ATab.Index equals 1
Browsers[ATab.Index].Destroy;
Close := True;
end;
Это приведет к уничтожению экземпляра браузера с именем Browser 2
из приведенной выше таблицы. Это не будет проблемой, если массив будет переиндексирован, как это делает коллекция Tabs
. Давайте посмотрим, что происходит с вкладками и вашим массивом:
ChromeTabs.Tabs Browsers
---------------------- ----------------------
Index Tab name Index Browser
---------- ---------- ---------- ----------
0 Tab 1 0 Browser 1
1 Tab 3 1 --- <-- dangling pointer
2 Browser 3
Как видите, коллекция Tabs
была переиндексирована после закрытия второй вкладки, а ваш массив - нет. Вы только что уничтожили экземпляр объекта, но висячий указатель от этого уничтоженного объекта все еще существует, в том же индексе.
Теперь, если вы снова нажмете кнопку закрытия второй вкладки (вкладка с именем Tab 3
), вы запустите тот же код, что и раньше, в своем обработчике событий:
procedure TForm1.ChromeTabsButtonCloseTabClick(Sender: TObject;
ATab: TChromeTab; var Close: Boolean);
begin
// as before, the ATab.Index equals 1, but this time you'll get AV
// since object from element Browsers[1] has been destroyed before
// and now that element contains just dangling pointer
Browsers[ATab.Index].Destroy; // <--
Close := True;
end;
Но на этот раз вы получите нарушение прав доступа, потому что объект из элемента Browsers[1]
уже был уничтожен ранее.
Для вашей цели нет массива нужного типа коллекции. Я бы посоветовал вам использовать коллекцию списков общих объектов TObjectList<T>
. Используя эту коллекцию, я бы переписал ваш код следующим образом:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
System.Generics.Collections, ChromeTabs, ChromeTabsClasses, cefvcl;
type
TForm1 = class(TForm)
ChromeTabs1: TChromeTabs;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure ChromeTabs1ButtonAddClick(Sender: TObject;
var Handled: Boolean);
procedure ChromeTabs1ButtonCloseTabClick(Sender: TObject;
ATab: TChromeTab; var Close: Boolean);
procedure ChromeTabs1ActiveTabChanging(Sender: TObject; AOldTab,
ANewTab: TChromeTab; var Allow: Boolean);
private
FBrowsers: TObjectList<TChromium>;
public
{ Public declarations }
end;
var
Form1: TForm1;
Tab_Closed:Boolean=False;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
// create instance of the object list and let it manage
// lifetime of the inserted objects
FBrowsers := TObjectList<TChromium>.Create;
FBrowsers.OwnsObjects := True;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
// release the object list
FBrowsers.Free;
end;
procedure TForm1.ChromeTabs1ButtonAddClick(Sender: TObject;
var Handled: Boolean);
var
ChromiumInstance: TChromium;
begin
// create an instance of the browser component and
// initiliaze its properties - here it's simplified
ChromiumInstance := TChromium.Create(nil);
ChromiumInstance.Parent := Self;
ChromiumInstance.SetBounds(8, 8, 150, 150);
// now add the new browser instance to the collection
FBrowsers.Add(ChromiumInstance);
end;
procedure TForm1.ChromeTabs1ButtonCloseTabClick(Sender: TObject;
ATab: TChromeTab; var Close: Boolean);
begin
// delete the browser instance from the collection; since we've
// assigned True to the OwnsObjects property of the collection,
// we don't need to care of freeing the browser instance
FBrowsers.Delete(ATab.Index);
// allow the tab to close
Close := True;
//and fix tab close
Tab_Closed:=True;
end;
procedure TForm1.ChromeTabs1ActiveTabChanging(Sender: TObject; AOldTab,
ANewTab: TChromeTab; var Allow: Boolean);
begin
// check if there's an "old tab" and if so, check also if we have its
// index in the range of our collection; if so, then hide the browser
if Assigned(AOldTab) and (AOldTab.Index < FBrowsers.Count) then
FBrowsers[AOldTab.Index].Visible := False;
// and show the activated tab browser
If((ChromeTabs.Tabs.Count<>1)) Then
Begin
If((ANewTab.Index=(ChromeTabs.Tabs.Count-1)) AND Tab_Closed=True) Then
FBrowsers[AOldTab.Index].Visible := True
Else
FBrowsers[ANewTab.Index].Visible := True;
End
Else FBrowsers[ANewTab.Index].Visible := True;
//Now Tab is not closed
Tab_Closed:=False;
end;
end.
person
TLama
schedule
17.07.2013
TChromium
объектов. Представьте, что у вас есть три вкладки: первая с индексом 0, вторая с индексом 1 и третья с индексом 2. Теперь вы закрываете вторую вкладку с индексом 1, уничтожаете объектTChromium
(элементBrowsers
с индексом 1), но этот элемент в массиве остается указывая на разрушенный объект. Затем вкладки переиндексируются, поэтому первая вкладка по-прежнему равна 0, но предыдущая третья получает индекс 1. И вы пытаетесь получить доступ к элементу с индексом 1, как и раньше, когда закрываете вторую вкладку. AV вы получаете, потому что вы вызываетеDestroy
метод на уничтоженном объекте. - person TLama   schedule 17.07.2013TObjectList<TChromium>
generics вместо этого массива. - person TLama   schedule 17.07.2013TObjectList<T>
- это коллекция объектов, которая не требует переиндексации, как это было бы с массивом. См. Справку для получения этой основной информации. - person TLama   schedule 17.07.2013