Original in Russian: http://18delphi.blogspot.ru/2013/04/iunknown.html
About containers. Table of contents
Borland (now - Embarcadero), naturally, has implementation of IUnknown. It is called TInterfacedObject.
And it is not bad.
But I personally am not satisfied with its dissymmetry. If one has created the interface, he should work through the interface and, I pray, forget about the object. Put it in container as interface and pass on as interface. Compared to the object, the interface is extra expenses – for the extra indirect character of the calls of methods (something like virtual) and for the extra AddRef/Release while assigning to local variables. The more so because of the fact that the public methods of the object are different from those of the interface. That is as it should be. The public character of methods can depend on the authority of the user of the object (that is, by the way, a subject of a separate post, which, besides, gunsmoker has approached in some way here: http://www.gunsmoker.ru/2013/02/delphi-friendliness.htm
I hope I understood him correctly and did not interpret his thoughts the way I liked).
I am not happy with it in my daily tasks. Moreover, historically, my implementation of reference counting dates back to Delphi 1, even BEFORE the concept of interfaces has been introduced. And the interfaces I emulated using hack VMT and message (as far as I can tell, gunsmoker also writes about the similar technique).
The history of how my own reference counting has appeared is worth of special post. Meanwhile, briefly – editor and Undo/Redo. Where objects do not have the centralized "dad" (Owner/Parent). All objects equally can contain a reference to other objects. It is guaranteed that the objects they refer to are “alive” as long as they contain the reference correctly.
Of course, the true holy war could be raised on “do as they say” and “wise men do not think up with no purpose”. But I would like to address this issue in a separate post, and here I’ll tell about my implementation and at the same time demonstrate one more time “micro”-UML and the technique of using “mixins”.
The previous series were here:
http://18delphi.blogspot.com/2015/02/containers-1-implementation-of.html
Basing on this knowledge, let’s construct an object that implements IUnknown.
Let’s draw a pair of diagrams:
-- this is a “mixin” implementation of IUnknown.
And the specific implementation (inherited from TObject) is as follows:
The code for this all is like this:
The diagram of the tests of this all:
And the code of the test:
I hope the “symmetry” of reference counting is clear.
Let’s move on.
We’ll add an object that implements the “real” interface.
Let’s draw a pair of diagrams:
-- the classes diagram.
-- the diagram of implementation.
And the code:
And the test for this all:
And the code:
That is all about it...
The next series will be devoted to the implementation of various containers.
P.S. ??? It is obvious that I can write:
-- and get TmyInterfacedPersistentObject inherited from TPersistent ???
There are certain problems with such an inheritance from TComponent or TControl due to RegisterClass. Later – I will describe them. But if RegisterClass is not called for such classes, then EVERYTHING IS OK. This technique can be used. If it is called, it means I’ve “hacked” VCL in some way. Later – I will tell how. I just don’t understand – why uniqueness of the names in RegisterClass should be controlled. I have disabled the control and all works. For already 10 years.
Oh! For TComponent (and its descendants, particularly – Tcontrol) implicit instantiation of mixin” should be applied, because it - ALREADY - has QueryInterface and there’s no need to redefine it (and it is harmful). Later I’ll tell how. In a nutshell – of course, using IfDef.
Actually, “partial admixing” is fun :-) That is when mixin is not fully admixed and just a part of it, that the right IfDef has got.
------------------------------------
This symmetry of reference counting eliminates the problem described here: http://www.gunsmoker.ru/2013/04/plugins-9.html
("Mixing of manual and automatic management of lifetime " and "Double interfaces release").
About containers. Table of contents
Borland (now - Embarcadero), naturally, has implementation of IUnknown. It is called TInterfacedObject.
And it is not bad.
But I personally am not satisfied with its dissymmetry. If one has created the interface, he should work through the interface and, I pray, forget about the object. Put it in container as interface and pass on as interface. Compared to the object, the interface is extra expenses – for the extra indirect character of the calls of methods (something like virtual) and for the extra AddRef/Release while assigning to local variables. The more so because of the fact that the public methods of the object are different from those of the interface. That is as it should be. The public character of methods can depend on the authority of the user of the object (that is, by the way, a subject of a separate post, which, besides, gunsmoker has approached in some way here: http://www.gunsmoker.ru/2013/02/delphi-friendliness.htm
I hope I understood him correctly and did not interpret his thoughts the way I liked).
I am not happy with it in my daily tasks. Moreover, historically, my implementation of reference counting dates back to Delphi 1, even BEFORE the concept of interfaces has been introduced. And the interfaces I emulated using hack VMT and message (as far as I can tell, gunsmoker also writes about the similar technique).
The history of how my own reference counting has appeared is worth of special post. Meanwhile, briefly – editor and Undo/Redo. Where objects do not have the centralized "dad" (Owner/Parent). All objects equally can contain a reference to other objects. It is guaranteed that the objects they refer to are “alive” as long as they contain the reference correctly.
Of course, the true holy war could be raised on “do as they say” and “wise men do not think up with no purpose”. But I would like to address this issue in a separate post, and here I’ll tell about my implementation and at the same time demonstrate one more time “micro”-UML and the technique of using “mixins”.
The previous series were here:
http://18delphi.blogspot.com/2015/02/containers-1-implementation-of.html
Basing on this knowledge, let’s construct an object that implements IUnknown.
Let’s draw a pair of diagrams:
-- this is a “mixin” implementation of IUnknown.
And the specific implementation (inherited from TObject) is as follows:
The code for this all is like this:
RefCountedPrim.imp.pas:
{$IfNDef RefCountedPrim_imp}
//
// The library "L3$Basic Concepts"
// Generated from UML model, root element: <<impurity::class>> Shared Delphi Requirements for low-level libraries::L3$Basic Concepts::Ref Counting::RefCountedPrim
//
{$Define RefCountedPrim_imp}
_RefCountedPrim_ = {mixin} class(_RefCountedPrim_Parent_)
private
// private fields
f_RefCount : Integer;
{* Field for the property RefCount}
protected
// overridden protected methods
procedure FreeInstance; override;
public
// overridden public methods
destructor Destroy; override;
class function NewInstance: TObject; override;
protected
// protected methods
procedure Cleanup; virtual;
{* the function of object fields cleaning. }
public
// public methods
function Use: Pointer;
{* increment the reference counter by 1 and return the pointer to itself. }
function SetRefTo(var F): Boolean;
public
// public properties
property RefCount: Integer
read f_RefCount;
end;//_RefCountedPrim_
{$Else RefCountedPrim_imp}
// start class _RefCountedPrim_
procedure _RefCountedPrim_.Cleanup;
begin
end;//_RefCountedPrim_.Cleanup
function _RefCountedPrim_.Use: Pointer;
begin
if (Self <> nil) then
InterlockedIncrement(f_RefCount);
Result := Self;
end;//_RefCountedPrim_.Use
function _RefCountedPrim_.SetRefTo(var F): Boolean;
begin
if (Pointer(F) = Self) then
Result := false
else
begin
Result := true;
TObject(F).Free;
Pointer(F) := Self.Use;
end;//Pointer(F) = V
end;//_RefCountedPrim_.SetRefTo
destructor _RefCountedPrim_.Destroy;
begin
if (InterlockedDecrement(f_RefCount) = 0) then
begin
Inc(f_RefCount);
try
try
Cleanup;
finally
inherited Destroy;
end;//try..finally
finally
Dec(f_RefCount);
end;{try..finally}
end;//InterlockedDecrement(f_RefCount) = 0
end;//_RefCountedPrim_.Destroy
class function _RefCountedPrim_.NewInstance: TObject;
begin
Result := inherited NewInstance;
_RefCounted_(Result).Use;
end;//_RefCountedPrim_.NewInstance
procedure _RefCountedPrim_.FreeInstance;
begin
if (f_RefCount = 0) then
inherited FreeInstance;
end;//_RefCountedPrim_.FreeInstance
{$EndIf RefCountedPrim_imp}
-------------------------------
RefCounted.imp.pas:
{$IfNDef RefCounted_imp}
// The library "L3$Basic Concepts"
// Generated from UML model, root element: <<impurity::class>> Shared Delphi Requirements for low-level libraries::L3$Basic Concepts::Ref Counting::RefCounted
//
// In this class we collect methods which we are not allowed to override or call directly
//
{$Define RefCounted_imp}
_RefCountedPrim_Parent_ = _RefCounted_Parent_;
{$Include RefCountedPrim.imp.pas}
_RefCounted_ = {mixin} class(_RefCountedPrim_)
{* In this class we collect methods which we are not allowed to override or call directly }
public
// public methods
destructor Destroy;
{* To prevent being lured in overriding destroy. }
class function NewInstance: TObject;
procedure FreeInstance;
procedure AfterConstruction;
procedure BeforeDestruction;
end;//_RefCounted_
{$Else RefCounted_imp}
{$Include RefCountedPrim.imp.pas}
// start class _RefCounted_
destructor _RefCounted_.Destroy;
begin
assert(false, 'We are not supposed to get here');
inherited;
end;//_RefCounted_.Destroy
class function _RefCounted_.NewInstance: TObject;
begin
Result := nil;
assert(false);
end;//_RefCounted_.NewInstance
procedure _RefCounted_.FreeInstance;
begin
assert(false);
end;//_RefCounted_.FreeInstance
procedure _RefCounted_.AfterConstruction;
begin
assert(false);
end;//_RefCounted_.AfterConstruction
procedure _RefCounted_.BeforeDestruction;
begin
assert(false);
end;//_RefCounted_.BeforeDestruction
{$EndIf RefCounted_imp}
-------------------------------------------------
UnknownImpl.imp.pas:
{$IfNDef UnknownImpl_imp}
// The library "L3$Basic Concepts"
// Generated from UML model, root element: <<impurity::class>> Shared Delphi Requirements for the low-level libraries::L3$Basic Concepts::Ref Counting::UnknownImpl
//
{$Define UnknownImpl_imp}
_RefCounted_Parent_ = _UnknownImpl_Parent_;
{$Include RefCounted.imp.pas}
_UnknownImpl_ = {mixin} class(_RefCounted_)
public
// realized methods
function _AddRef: Integer; stdcall;
{* Increments reference counter. }
function _Release: Integer; stdcall;
{* Decrements reference counter. }
function QueryInterface(const IID: TGUID;
out Obj): HResult; stdcall;
{* Brings base interface to the required, if it is possible. }
end;//_UnknownImpl_
{$Else UnknownImpl_imp}
{$Include RefCounted.imp.pas}
// start class _UnknownImpl_
function _UnknownImpl_._AddRef: Integer;
begin
Use;
Result := RefCount;
// - here we’ve got problems with multithreading
end;//_UnknownImpl_._AddRef
function _UnknownImpl_._Release: Integer;
var
l_RC : Integer;
begin
l_RC := RefCount - 1;
Free;
Result := l_RC;
// - here we’ve got problems with multithreading
end;//_UnknownImpl_._Release
function _UnknownImpl_.QueryInterface(const IID: TGUID;
out Obj): HResult;
begin
if TObject(Self).GetInterface(IID, Obj) then
Result := S_Ok
else
Result := E_NoInterface;
end;//_UnknownImpl_.QueryInterface
{$EndIf UnknownImpl_imp}
---------------------------------
Unknown.imp.pas:
{$IfNDef Unknown_imp}
// The library "L3$Basic Concepts"
// Generated from UML model, root element: <<impurity::class>> Shared Delphi Requirements for the low-level libraries::L3$Basic Concepts::Ref Counting::Unknown
//
{$Define Unknown_imp}
_UnknownImpl_Parent_ = _Unknown_Parent_;
{$Include UnknownImpl.imp.pas}
_Unknown_ = {mixin} class(_UnknownImpl_, IUnknown)
end;//_Unknown_
{$Else Unknown_imp}
{$Include UnknownImpl.imp.pas}
{$EndIf Unknown_imp}
---------------------------------
myInterfacedObject.pas:
unit myInterfacedObject;
// The library "SandBox"
Interfaces::TmyInterfacedObject
//
interface
type
_Unknown_Parent_ = TObject;
{$Include Unknown.imp.pas}
TmyInterfacedObject = class(_Unknown_)
end;//TmyInterfacedObject
implementation
uses
Windows
;
{$Include Unknown.imp.pas}
end.
The diagram of the tests of this all:
And the code of the test:
unit myInterfacedObjectTest; // The library "SandBoxTest" // Generated from UML model, root element: <<testcase::class>> Shared Delphi Sand Box::SandBoxTest::Core::TmyInterfacedObjectTest // // interface uses TestFrameWork ; type TmyInterfacedObjectTest = class(TTestCase) published // published methods procedure DoIt; end;//TmyInterfacedObjectTest implementation uses myInterfacedObject, SysUtils ; // start class TmyInterfacedObjectTest procedure TmyInterfacedObjectTest.DoIt; var l_O : TmyInterfacedObject; l_AnotherRef : TmyInterfacedObject; l_A : IUnknown; l_B : IUnknown; begin l_AnotherRef := nil; try l_O := TmyInterfacedObject.Create; try Check(l_O.RefCount = 1); l_A := l_O; Check(l_O.RefCount = 2); l_A := nil; Check(l_O.RefCount = 1); l_AnotherRef := l_O.Use; Check(l_O.RefCount = 2); l_B := l_O; Check(l_O.RefCount = 3); finally FreeAndNil(l_O); end;//try..finally Check(l_AnotherRef.RefCount = 2); l_B := nil; Check(l_AnotherRef.RefCount = 1); finally FreeAndNil(l_AnotherRef); end;//try..finally end;//TmyInterfacedObjectTest.DoIt initialization TestFramework.RegisterTest(TmyInterfacedObjectTest.Suite); end.
I hope the “symmetry” of reference counting is clear.
Let’s move on.
We’ll add an object that implements the “real” interface.
Let’s draw a pair of diagrams:
-- the classes diagram.
-- the diagram of implementation.
And the code:
myReferenceCountGuard.pas:
unit myReferenceCountGuard;
// The library "SandBox"
// Generated from UML model, root element: <<simpleclass::class>> Shared Delphi Sand Box::SandBox::Basic Interfaces::TmyReferenceCountGuard
//
// Class for examples only
//
interface
uses
myInterfacedObject
;
type
ImyReferenceCountGuard = interface(IUnknown)
{* Interface for examples only }
['{84AAAF31-F3AC-4BBC-A1B7-4E338748921F}']
function GetRefCount: Integer;
end;//ImyReferenceCountGuard
TmyReferenceCountGuard = class(TmyInterfacedObject, ImyReferenceCountGuard)
{* Class for examples only }
protected
// realized methods
function GetRefCount: Integer;
public
// public methods
class function Make: ImyReferenceCountGuard; reintroduce;
{* Factory TmyReferenceCountGuard.Make }
end;//TmyReferenceCountGuard
implementation
// start class TmyReferenceCountGuard
class function TmyReferenceCountGuard.Make: ImyReferenceCountGuard;
var
l_Inst : TmyReferenceCountGuard;
begin
l_Inst := Create;
try
Result := l_Inst;
finally
l_Inst.Free;
end;//try..finally
end;
function TmyReferenceCountGuard.GetRefCount: Integer;
begin
Result := RefCount;
end;//TmyReferenceCountGuard.GetRefCount
end.
And the test for this all:
And the code:
myReferenceCountGuardTest.pas: unit myReferenceCountGuardTest; // The library "SandBoxTest" // Generated from UML model, root element: <<testcase::class>> Shared Delphi Sand Box::SandBoxTest::Core::TmyReferenceCountGuardTest // interface uses TestFrameWork ; type TmyReferenceCountGuardTest = class(TTestCase) published // published methods procedure DoIt; procedure CheckWithClause; end;//TmyReferenceCountGuardTest implementation uses SysUtils, myReferenceCountGuard ; // start class TmyReferenceCountGuardTest procedure TmyReferenceCountGuardTest.DoIt; var l_G : ImyReferenceCountGuard; l_Another : ImyReferenceCountGuard; begin l_G := TmyReferenceCountGuard.Make; Check(l_G.GetRefCount = 1); l_Another := l_G; Check(l_G.GetRefCount = 2); l_G := nil; Check(l_Another.GetRefCount = 1); l_Another := nil; end;//TmyReferenceCountGuardTest.DoIt procedure TmyReferenceCountGuardTest.CheckWithClause; var l_G : ImyReferenceCountGuard; begin // - here I wanted to show that inside the operator with the compiler increments the reference counter on interface/object “in parasite manner”, but I did not succeed in it: it turns out that in trivial cases it does not do it; when I find a non-trivial case I’ll show l_G := TmyReferenceCountGuard.Make; Check(l_G.GetRefCount = 1); with l_G do begin Check(GetRefCount = 1); Check(GetRefCount = 1); end;//with l_G Check(l_G.GetRefCount = 1); end;//TmyReferenceCountGuardTest.CheckWithClause initialization TestFramework.RegisterTest(TmyReferenceCountGuardTest.Suite); end.
That is all about it...
The next series will be devoted to the implementation of various containers.
P.S. ??? It is obvious that I can write:
---------------------------------
myInterfacedPersistentObject.pas:
unit myInterfacedPersistentObject;
// The library "SandBox"
// Generated from UML model, root element: <<simpleclass::class>> Shared Delphi Sand Box::SandBox::Basic Interfaces::TmyInterfacedPersistentObject
//
interface
type
_Unknown_Parent_ = TPersistent;
{$Include Unknown.imp.pas}
TmyInterfacedPersistentObject = class(_Unknown_)
end;//TmyInterfacedPersistentObject
implementation
uses
Windows
;
{$Include Unknown.imp.pas}
end.
-- and get TmyInterfacedPersistentObject inherited from TPersistent ???
There are certain problems with such an inheritance from TComponent or TControl due to RegisterClass. Later – I will describe them. But if RegisterClass is not called for such classes, then EVERYTHING IS OK. This technique can be used. If it is called, it means I’ve “hacked” VCL in some way. Later – I will tell how. I just don’t understand – why uniqueness of the names in RegisterClass should be controlled. I have disabled the control and all works. For already 10 years.
Oh! For TComponent (and its descendants, particularly – Tcontrol) implicit instantiation of mixin” should be applied, because it - ALREADY - has QueryInterface and there’s no need to redefine it (and it is harmful). Later I’ll tell how. In a nutshell – of course, using IfDef.
Actually, “partial admixing” is fun :-) That is when mixin is not fully admixed and just a part of it, that the right IfDef has got.
------------------------------------
This symmetry of reference counting eliminates the problem described here: http://www.gunsmoker.ru/2013/04/plugins-9.html
("Mixing of manual and automatic management of lifetime " and "Double interfaces release").






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