Reputation: 9096
If I understand correctly this is fine:
type
IMyInterface = interface['{60E314E4-9FA9-4E29-A09A-01B91F2F27C7}']
procedure MyMethod;
end;
type
TMyIClass = class(TInterfacedObject, IMyInterface)
public
procedure MyMethod; // Forget the implementations in this example
end;
var
lMyIClass: IMyInterface;
lSupports: Boolean;
begin
lMyIClass := TMyIClass.Create;
lSupports := Supports(lMyIClass,IMyInterface);
Memo1.Lines.Add('lMyIClass supports IMyInterface: ' + BoolToStr(lSupports,true));
if lSupports then DoSomethingWith(lMyIClass);
Now I have a class implementing multiple interfaces:
type
IFirstInterface = interface['{4646BD44-FDBC-4E26-A497-D9E48F7EFCF9}']
procedure SomeMethod1;
end;
ISecondInterface = interface['{B4473616-CF1F-4E88-9EAE-1AAF1B01A331}']
procedure SomeMethod2;
end;
TMyClass = class(TInterfacedObject, IFirstInterface, ISecondInterface)
procedure SomeMethod1;
procedure SomeMethod2;
end;
I can call another overloaded Support() returning the interface and do something with it):
var
MyClass1,MyClass2 : TMyClass;
i1: IFirstInterface;
i2: ISecondInterface;
bSupports: Boolean;
begin
Memo1.Clear;
MyClass1 := TMyClass.Create;
bSupports := Supports(MyClass1,IFirstInterface,i1);
if bSupports then
begin
Memo1.Lines.Add('MyClass1 supports IFirstInterface');
DoSomethingWith(i1);
end
else
Memo1.Lines.Add('MyClass1 does not support IFirstInterface');
bSupports := Supports(MyClass1,ISecondInterface,i2);
if bSupports then
begin
Memo1.Lines.Add('MyClass1 supports ISecondInterface');
DoSomethingElseWith(i2);
end
else
Memo1.Lines.Add('MyClass1 does not support ISecondInterface');
MyClass1 := nil;
i1 := nil;
i2 := nil;
MyClass2 := TMyClass.Create;
bSupports := Supports(MyClass2,IFirstInterface,i1);
if bSupports then
begin
Memo1.Lines.Add('MyClass2 supports IFirstInterface');
DoSomethingWith(i1);
end
else
Memo1.Lines.Add('MyClass2 does not support IFirstInterface');
bSupports := Supports(MyClass2,ISecondInterface,i2);
if bSupports then
begin
Memo1.Lines.Add('MyClass2 supports ISecondInterface');
DoSomethingElseWith(i2);
end
else
Memo1.Lines.Add('MyClass2 does not support ISecondInterface');
I have three questions about this:
The MyClass1, MyClass2
are now object types, not interface types as in the simple example. Is this OK?
Should I Free() or 'nil' MyClass1 or maybe even leave it alone?
After having handled 2., are the two ix:= nil
statements still required with regard to the reference counts?
Upvotes: 1
Views: 692
Reputation: 386
In principal...I agree with everything everyone is saying about mixing Interface and Object model and don't mix them...
It's an easy way to get yourself in trouble and doing late night tracking of weird GPF's...
But...(there's always a but)...
You can do it...if you understand the Gotcha's...
Checkout Button4Click and Button5Click...Button4Click does it by Interface...Button5Click does by both.
IReqBase = Interface(IInterface)
['{B71BD1C3-CE4C-438A-8090-DA6AACF0B3C4}']
procedure FillWithTemplateData;
end;
IReqLogIn = Interface(IInterface)
['{133D2DFF-670C-4942-A81C-D18CBE825A93}']
procedure SetupPassword(aUserName, aPassword: string);
end;
type
TWebAct = (ttlogin,
ttsignin);
TForm59 = class(TForm)
Button1: TButton;
Memo1: TMemo;
CheckBox1: TCheckBox;
Button2: TButton;
Button3: TButton;
Button4: TButton;
Button5: TButton;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure Button4Click(Sender: TObject);
procedure Button5Click(Sender: TObject);
private
{ Private declarations }
FReqList: Array of IReqBase;
procedure CreateBaseList;
procedure ClearBaseList;
public
{ Public declarations }
end;
TJSONStructure = class(TInterfacedObject);
TReqBaseClass = class of TReqBase;
TReqBase = class(TJSONStructure, IReqBase)
private
token: Int64;
protected
class function ReqClass: TReqBaseClass; virtual; abstract;
public
Constructor Create; virtual;
procedure FillWithTemplateData; virtual;
class function ReqBase: IReqBase;
end;
TReqLogin = class(TReqBase, IReqLogIn)
private
Fusername,
Fpassword: String;
Fmodule : Integer;
protected
class function ReqClass: TReqBaseClass; override;
public
Constructor Create; override;
Destructor Destroy; override;
procedure SetupPassword(aUserName, aPassword: string);
procedure FillWithTemplateData; override;
end;
TReqSignIn = class(TReqBase)
private
Fusername,
Fpassword: String;
Fmodule : Integer;
protected
class function ReqClass: TReqBaseClass; override;
public
Constructor Create; override;
Destructor Destroy; override;
procedure FillWithTemplateData; override;
end;
var
Form59: TForm59;
implementation
{$R *.dfm}
procedure TForm59.Button1Click(Sender: TObject);
begin
Memo1.Lines.Clear;
IReqBase(FReqList[integer(CheckBox1.Checked)]).FillWithTemplateData;
end;
procedure TForm59.Button2Click(Sender: TObject);
begin
CreateBaseList;
end;
procedure TForm59.Button3Click(Sender: TObject);
begin
if CheckBox1.Checked then
TReqSignIn.ReqBase.FillWithTemplateData
else
TReqLogin.ReqBase.FillWithTemplateData;
end;
procedure TForm59.Button4Click(Sender: TObject);
var
a_IReqBase1: IReqBase;
a_IReqBase2: IReqBase;
a_IReqLogIn: IReqLogIn;
begin
a_IReqBase1 := TReqSignIn.Create;
a_IReqBase2 := TReqLogin.Create;
a_IReqLogIn := a_IReqBase2 as IReqLogIn;
a_IReqLogIn.SetupPassword('houseofdexter', 'dexter');
a_IReqBase1.FillWithTemplateData;
a_IReqBase2.FillWithTemplateData;
a_IReqLogIn := a_IReqBase2 as IReqLogIn;
a_IReqLogIn.SetupPassword('houseofdexter', 'dexter');
end;
procedure TForm59.Button5Click(Sender: TObject);
var
a_ReqBase1: TReqSignIn;
a_ReqBase2: TReqLogin;
a_IReqBase1: IReqBase;
a_IReqBase2: IReqBase;
a_IReqLogIn: IReqLogIn;
begin
a_ReqBase1 := TReqSignIn.Create;
a_ReqBase2 := TReqLogin.Create;
a_IReqBase1 := a_ReqBase1;
a_IReqBase2 := a_ReqBase2;
a_IReqLogIn := a_IReqBase2 as IReqLogIn;
//note we are working with the object...
a_ReqBase2.SetupPassword('houseofdexter', 'dexter');
a_IReqBase1.FillWithTemplateData;
a_IReqBase2.FillWithTemplateData;
a_IReqLogIn := a_IReqBase2 as IReqLogIn;
a_IReqLogIn.SetupPassword('houseofdexter', 'dexter');
end;
procedure TForm59.ClearBaseList;
begin
SetLength(FReqList, 0);
end;
procedure TForm59.CreateBaseList;
begin
if High(FReqList) = Ord(High(TWebAct)) +1 then
ClearBaseList;
SetLength(FReqList, Ord(High(TWebAct)) + 1 );
FReqList[ord(ttlogin)] := TReqLogin.ReqBase;
FReqList[ord(ttsignin)] := TReqSignIn.ReqBase;
end;
procedure TForm59.FormCreate(Sender: TObject);
begin
CreateBaseList;
end;
procedure TForm59.FormDestroy(Sender: TObject);
begin
ClearBaseList;
end;
{ TReqLogin }
constructor TReqLogin.Create;
begin
inherited;
FUserName := 'Rick';
FPassword := 'Test';
Fmodule := 100;
end;
destructor TReqLogin.Destroy;
begin
Form59.Memo1.Lines.Add('Destroyed: ' +ClassName);
Form59.Memo1.Lines.Add('RefCount: ' + IntToStr(TInterfacedObject(Self).RefCount));
end;
procedure TReqLogin.FillWithTemplateData;
begin
inherited;
Form59.Memo1.Lines.Add(Fusername);
Form59.Memo1.Lines.Add(FPassword);
Form59.Memo1.Lines.Add(IntToStr(FModule));
end;
class function TReqLogin.ReqClass: TReqBaseClass;
begin
Result := TReqLogin;
end;
procedure TReqLogin.SetupPassword(aUserName, aPassword: string);
begin
Fusername := aUserName;
Fpassword := aPassword;
end;
{ TReqBase }
constructor TReqBase.Create;
begin
inherited;
Form59.Memo1.Lines.Add('Created: ' +ClassName);
Token := -1;
end;
procedure TReqBase.FillWithTemplateData;
begin
Form59.Memo1.Lines.Add(Self.ClassName);
Form59.Memo1.Lines.Add('RefCount: ' + IntToStr(TInterfacedObject(Self).RefCount));
Form59.Memo1.Lines.Add(IntToStr(Token));
end;
class function TReqBase.ReqBase: IReqBase;
begin
Result := ReqClass.Create;
end;
{ TReqSignIn }
constructor TReqSignIn.Create;
begin
inherited;
Form59.Memo1.Lines.Add('RefCount: ' + IntToStr(TInterfacedObject(Self).RefCount));
FUserName := 'Peterson';
FPassword := 'TestPW';
Fmodule := 101;
end;
destructor TReqSignIn.Destroy;
begin
Form59.Memo1.Lines.Add('Destroyed: ' +ClassName);
Form59.Memo1.Lines.Add('RefCount: ' + IntToStr(TInterfacedObject(Self).RefCount));
inherited;
end;
procedure TReqSignIn.FillWithTemplateData;
begin
inherited;
Form59.Memo1.Lines.Add(Fusername);
Form59.Memo1.Lines.Add(FPassword);
Form59.Memo1.Lines.Add(IntToStr(FModule));
end;
class function TReqSignIn.ReqClass: TReqBaseClass;
begin
Result := TReqSignIn;
end;
end.
Upvotes: 0
Reputation: 163317
A common piece of advice is to never mix object references with interface references. What that means is that if you need to instantiate a class and use any of its interfaces, it's best to not refer to it via an object-reference type. You've violated that advice by changing your variables to be of type TMyClass
instead of an interface type. Declare them as interface variables instead; I'd use IUnknown
.
The reason for this advice is that object references are not treated the same as interface references. The compiler always inserts reference-counting code for interface variables, and that code is oblivious to any object references anywhere else in your program. Due to reference counting, an object-reference variable could become invalid after changes to some interface variable, and it's easy to overlook that while writing programs. If you never have an object-reference variable, then you don't need to worry about that possibility; an interface reference should always be valid.
If MyClass1
is an object reference, then you should not call Free
on it after you've assigned it to an interface variable. Here's some of your code, annotated with the object's reference count:
MyClass1 := TMyClass.Create; // initialized to 0
bSupports := Supports(MyClass1,IFirstInterface,i1); // incremented to 1
if bSupports then
begin
Memo1.Lines.Add('MyClass1 supports IFirstInterface');
DoSomethingWith(i1);
end
else
Memo1.Lines.Add('MyClass1 does not support IFirstInterface');
bSupports := Supports(MyClass1,ISecondInterface,i2); // incremented to 2
if bSupports then
begin
Memo1.Lines.Add('MyClass1 supports ISecondInterface');
DoSomethingElseWith(i2);
end
else
Memo1.Lines.Add('MyClass1 does not support ISecondInterface');
MyClass1 := nil; // still 2
i1 := nil; // decremented to 1
i2 := nil; // decremented to 0; the object gets destroyed
If you were to call MyClass1.Free
at any point, your program would crash. Freeing the object yourself would not change the values in i1
or i2
, so the compiler's automatically inserted reference-counting code would still execute. It would attempt to reduce the reference count of an already-freed object, which is obviously not good.
But suppose you waited until after you cleared i1
and i2
, as in this code:
i1 := nil;
i2 := nil;
MyClass1.Free;
That's still wrong. Clearing the variables sets the reference count to 0, so the object gets destroyed upon assigning to i2
; the value in MyClass1
is invalid, so you shouldn't call Free
on it there, either.
The safest thing to do, once you've assigned an object reference to an interface reference, is to clear the object reference immediately. Then you won't be tempted to use it anymore.
There is typically no need to clear an interface variable. It gets cleared automatically at the end of its lifetime (which for local variables is when they go out of scope at the end of the function). Furthermore, if you call Supports
and pass in an already-assigned interface reference, it will either receive an interface reference to the new object, or it will be cleared; it will not continue holding its previous value.
That is, when you call Supports(MyClass2,IFirstInterface,i1);
, there was no need to clear i1
first. The call to Supports
will either fill i1
with a reference to the IFirstInterface
for the object referenced by MyClass2
, or it will store nil
in i1
.
Upvotes: 4