четверг, 4 декабря 2014 г.

Коротко. Контроль за созданием/освобождением объектов. Только код. №4

По мотивам - Коротко. Контроль за созданием/освобождением объектов. Только код. №3

Теперь мы прикручиваем контроль за зомби-объектами.

Их список мы ведём в f_DefferedObjects : TmsDefferedObjects.

Репозитарий:

https://bitbucket.org/ingword/mindstream/src/ee7a505dc542a2d71487a1b0e06ff2dc5827f9f1/Core/msCoreObjects.pas?at=MS-6_AddTestToMindStream

Код: 
unit msCoreObjects;

interface

uses
 System.Classes,
 System.Generics.Collections
 ;

type
 TmsLog = class;

 TmsLogLambda = reference to procedure (aLog: TmsLog);

 TmsLog = class
  strict private
   f_FS : TFileStream;
  public
   class procedure Log(const aFileName: String; aLambda: TmsLogLambda);
   constructor Create(const aFileName: String);
   destructor Destroy; override;
   procedure ToLog(const aString: AnsiString);
 end;//TmsLog

 TmsClassInstanceCount = record
  public
   rCount : Integer;
   rMaxCount : Integer;
   constructor Create(aCount: Integer);
   constructor IncCreate(const anOther: TmsClassInstanceCount);
   constructor DecCreate(const anOther: TmsClassInstanceCount);
 end;//TmsClassInstanceCount

 TmsClassInstanceCountList = TDictionary<String, TmsClassInstanceCount>;
 // ms-help://embarcadero.rs_xe7/libraries/System.Generics.Collections.TDictionary.html

 TmsDefferedObjects = class(TList<TObject>)
 // - список отложенных объектов
 //   http://programmingmindstream.blogspot.ru/2014/11/blog-post_8.html
 public
  destructor Destroy; override;
 end;//TmsDefferedObjects

 TmsObjectsWatcher = class
  // - следилка за объектами
  // НЕ является ПОТОКОБЕЗОПАСНОЙ
 private
  class var f_ObjectsCreatedCount : Integer;
  class var f_ObjectsCreated: TmsClassInstanceCountList;
  class var f_DefferedObjects : TmsDefferedObjects;
 public
  class procedure CreateObject(aClass: TClass; var theInstance: TObject);
  class procedure DestroyObject(anObject: TObject);
  class procedure ObjectCreated(anObject: TObject);
  class procedure ObjectDestroyed(anObject: TObject);
  class destructor Destroy;
 end;//TmsObjectsWatcher

 TmsWatchedObject = class abstract(TObject)
 // - Класс, который умеет контроллировать создание/уничтожение своих экземпляров
 public
  class function NewInstance: TObject; override;
  // ms-help://embarcadero.rs_xe7/libraries/System.TObject.NewInstance.html
  procedure FreeInstance; override;
  // ms-help://embarcadero.rs_xe7/libraries/System.TObject.FreeInstance.html
 end;//TmsWatchedObject

 TmsStringList = class abstract(TStringList)
 // - Класс, который умеет контроллировать создание/уничтожение своих экземпляров
 public
  class function NewInstance: TObject; override;
  // ms-help://embarcadero.rs_xe7/libraries/System.TObject.NewInstance.html
  procedure FreeInstance; override;
  // ms-help://embarcadero.rs_xe7/libraries/System.TObject.FreeInstance.html
 end;//TmsStringList

 TmsInterfacedNonRefcounted = class abstract(TmsWatchedObject)
  // - реализация объектов реализующих интерфейсы, но БЕЗ подсчёта ссылок
  //   т.е. присваиваемый объект - НЕ ЗАХВАТЫВАЕТСЯ и "владелец" - НЕ УПРАВЛЯЕТ временем жизни
  //   Зачем? Чтобы избежать кросс-ссылок.
  //   От TmsInterfacedNonRefcounted должны наследоваться объекты-контейнеры,
  //   которые хотят сообщать своим "детям" свои интерфейсы.
  //
  //   Тут есть одна ТОНКОСТЬ - объект-контейнер - в СВОЮ очередь может являться
  //   "ребёнком", но мы это потом - РАЗРУЛИМ, когда дойдём.
 protected
  function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
  function _AddRef: Integer; stdcall;
  function _Release: Integer; stdcall;
 end;//TmsInterfacedNonRefcounted

 TmsInterfacedRefcounted = class abstract(TInterfacedObject)
  // Реализация объектов, реализующих интерфейсы. С ПОДСЧЁТОМ ссылок.
  //
  // НЕ САМАЯ хорошая реализация, лучше реализация тут - http://18delphi.blogspot.ru/2013/04/iunknown.html
  // но в учётом ARC - пользуемся пока "нативной реализаией"
  //
  // Таже ещё есть вот что "почитать":
  // - http://18delphi.blogspot.ru/2013/07/blog-post_3683.html
  // - http://18delphi.blogspot.ru/2013/07/1.html
  // - http://18delphi.blogspot.ru/2013/07/2.html
  // - http://18delphi.blogspot.ru/2013/07/2_18.html
  // - http://18delphi.blogspot.ru/2013/07/blog-post_8789.html
 public
  class function NewInstance: TObject; override;
  // ms-help://embarcadero.rs_xe7/libraries/System.TObject.NewInstance.html
  procedure FreeInstance; override;
  // ms-help://embarcadero.rs_xe7/libraries/System.TObject.FreeInstance.html
 end;//TmsInterfacedRefcounted

implementation

uses
 System.SysUtils,
 Math
 ;

// TmsClassInstanceCount

constructor TmsClassInstanceCount.Create(aCount: Integer);
begin
 rCount := aCount;
 rMaxCount := rCount;
end;

constructor TmsClassInstanceCount.IncCreate(const anOther: TmsClassInstanceCount);
begin
 Self := anOther;
 Inc(rCount);
 rMaxCount := Max(anOther.rMaxCount, rCount);
end;

constructor TmsClassInstanceCount.DecCreate(const anOther: TmsClassInstanceCount);
begin
 Self := anOther;
 Dec(rCount);
end;

destructor TmsDefferedObjects.Destroy;
var
 l_Object : TObject;
begin
 for l_Object in Self do
  FreeMem(Pointer(l_Object), l_Object.InstanceSize);
 inherited;
end;

// TmsObjectsWatcher

class procedure TmsObjectsWatcher.CreateObject(aClass: TClass; var theInstance: TObject);
begin
 GetMem(Pointer(theInstance), aClass.InstanceSize);
 // - распределяем память подобъекты сами. Зачем?
 //   Чтобы следить за повторным удалением.
 //   http://programmingmindstream.blogspot.ru/2014/11/blog-post_8.html
 aClass.InitInstance(theInstance);
 ObjectCreated(theInstance);
end;

class procedure TmsObjectsWatcher.DestroyObject(anObject: TObject);
const
 cMaxDefferedObjectsCount = 1000;
var
 l_P : Pointer;
begin
 if (f_DefferedObjects <> nil) then
  if (f_DefferedObjects.IndexOf(anObject) >= 0)then
   raise Exception.Create('Объект класса ' + anObject.ClassName + ' уже был освобождён');
 ObjectDestroyed(anObject);
 anObject.CleanupInstance;
 if (f_DefferedObjects = nil) then
  f_DefferedObjects := TmsDefferedObjects.Create;
 f_DefferedObjects.Add(anObject);
 if (f_DefferedObjects.Count > cMaxDefferedObjectsCount) then
 begin
  l_P := f_DefferedObjects.First;
  FreeMem(l_P);
  f_DefferedObjects.Delete(0);
 end;//f_DefferedObjects.Count > cMaxDefferedObjectsCount
 //FreeMem(Pointer(anObject));
end;

class procedure TmsObjectsWatcher.ObjectCreated(anObject: TObject);
var
 l_ClassName : String;
begin
 Inc(f_ObjectsCreatedCount);
 if (f_ObjectsCreated = nil) then
  f_ObjectsCreated := TmsClassInstanceCountList.Create;
 l_ClassName := anObject.ClassName;
 if (not f_ObjectsCreated.ContainsKey(l_ClassName)) then
  f_ObjectsCreated.Add(l_ClassName, TmsClassInstanceCount.Create(1))
 else
  f_ObjectsCreated.Items[l_ClassName] := TmsClassInstanceCount.IncCreate(f_ObjectsCreated.Items[l_ClassName]);
end;

class procedure TmsObjectsWatcher.ObjectDestroyed(anObject: TObject);
var
 l_ClassName : String;
begin
 Assert(f_ObjectsCreatedCount > 0, 'Какие-то объекты уже были освобождены несколько раз');
 if (f_ObjectsCreated <> nil) then
 begin
  l_ClassName := anObject.ClassName;
  if f_ObjectsCreated.ContainsKey(l_ClassName) then
   f_ObjectsCreated.Items[l_ClassName] := TmsClassInstanceCount.DecCreate(f_ObjectsCreated.Items[l_ClassName]);
 end;//f_ObjectsCreated <> nil
 Dec(f_ObjectsCreatedCount);
end;

class procedure TmsLog.Log(const aFileName: String; aLambda: TmsLogLambda);
var
 l_Log : TmsLog;
begin
 l_Log := Create(aFileName);
 try
  aLambda(l_Log);
 finally
  FreeAndNil(l_Log);
 end;//try..finally
end;

constructor TmsLog.Create(const aFileName: String);
begin
 inherited Create;
 f_FS := TFileStream.Create(aFileName, fmCreate);
end;

destructor TmsLog.Destroy;
begin
 FreeAndNil(f_FS);
 inherited;
end;

procedure TmsLog.ToLog(const aString: AnsiString);
const
 cEOL : ANSIString = #13#10;
begin//OutLn
 f_FS.Write(aString[1], Length(aString));
 f_FS.Write(cEOL[1], Length(cEOL));
end;//OutLn

class destructor TmsObjectsWatcher.Destroy;
begin
 if (f_ObjectsCreated <> nil) then
  if (f_ObjectsCreated.Count > 0) then
  begin
   // Далее выводим статистику неосвобождённых объектов в лог:
   TmsLog.Log(ParamStr(0) + '.objects.log',
    procedure (aLog: TmsLog)
    var
     l_Key : String;
     l_Value : TmsClassInstanceCount;
    begin
     aLog.ToLog('Неосвобождено объектов: ' + IntToStr(f_ObjectsCreatedCount));
     for l_Key in f_ObjectsCreated.Keys do
     begin
      l_Value := f_ObjectsCreated[l_Key];
      aLog.ToLog(l_Key + ' Неосвобождено: ' + IntToStr(l_Value.rCount) + ' Максимально распределено: ' + IntToStr(l_Value.rMaxCount));
     end;//for l_Key
    end
   );
  end;//f_ObjectsCreated.Count > 0
 FreeAndNil(f_ObjectsCreated);
 FreeAndNil(f_DefferedObjects);
 if (f_ObjectsCreatedCount > 0) then
  raise Exception.Create('Какие-то объекты не освобождены: ' + IntToStr(f_ObjectsCreatedCount));
end;

// TmsWatchedObject

class function TmsWatchedObject.NewInstance: TObject;
begin
 TmsObjectsWatcher.CreateObject(Self, Result);
end;

procedure TmsWatchedObject.FreeInstance;
begin
 TmsObjectsWatcher.DestroyObject(Self);
end;

// TmsStringList

class function TmsStringList.NewInstance: TObject;
begin
 TmsObjectsWatcher.CreateObject(Self, Result);
end;

procedure TmsStringList.FreeInstance;
begin
 TmsObjectsWatcher.DestroyObject(Self);
end;

// TmsInterfacedNonRefcounted

function TmsInterfacedNonRefcounted.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
 Result := E_NoInterface;
end;

function TmsInterfacedNonRefcounted._AddRef: Integer;
begin
 Result := -1;
end;

function TmsInterfacedNonRefcounted._Release: Integer;
begin
 Result := -1;
end;

//TmsInterfacedRefcounted

class function TmsInterfacedRefcounted.NewInstance: TObject;
begin
 TmsObjectsWatcher.CreateObject(Self, Result);
 TmsInterfacedRefcounted(Result).FRefCount := 1;
end;

procedure TmsInterfacedRefcounted.FreeInstance;
begin
 TmsObjectsWatcher.DestroyObject(Self);
end;

end.

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

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