How to access fields of a TTestCase in a TTestSetup class - delphi

I am creating unit tests with DUnit. I have a class that takes quite a long time to initialize.
I derive a class TMyTestSetup from TTestSetup and override its Setup method. This SetUp method is only called once for all the tests in my TTestCase. I put the Initialization process in the TMyTestSetup.SetUp routine to increase performance.
My problem is how can I access the object I want to initialize, which is a field of my TMyTest in the TestSetup class? Is the only way to do it declaring it globally?
untested short example:
TMyTestSetup = class(TTestSetup)
protected
procedure SetUp; override;
end;
TMyTest = class(TTestcase)
public
fTakes4Ever2Init : TInits4Ever2Init;
published
procedure Test1;
end;
implementation
procedure TMyTestSetup.Setup;
begin
// How can I access fTakes4Ever2Init from here?
fTakes4Ever2Init.create // This is the call that takes long
end;
procedure TMyTest.Test1;
begin
fTakes4Ever2Init.DoSomething;
end;
initialization
RegisterTest(TMyTestSetup.Create(TMyTest.Suite));

The trick is to use a public class variable in the TMyTestSetup class.
Like this (tested and working, complete) example:
unit TestTestUnit;
interface
uses
TestFramework, TestExtensions;
type
TInits4Ever2Init = class
private
FValue: integer;
public
constructor Create;
procedure DoSomething1;
procedure DoSomething2;
procedure DoSomething3;
end;
type
TMyTestSetup = class(TTestSetup)
public class var
fTakes4Ever2Init: TInits4Ever2Init;
protected
procedure SetUp; override;
end;
TMyTest = class(TTestCase)
published
procedure Test1;
procedure Test2;
procedure Test3;
end;
implementation
uses
SysUtils, Windows;
{ TMyTestSetup }
procedure TMyTestSetup.Setup;
begin
fTakes4Ever2Init := TInits4Ever2Init.create; // This is the call that takes long
end;
{ TMyTest }
procedure TMyTest.Test1;
begin
TMyTestSetup.fTakes4Ever2Init.DoSomething1;
end;
procedure TMyTest.Test2;
begin
TMyTestSetup.fTakes4Ever2Init.DoSomething2;
end;
procedure TMyTest.Test3;
begin
TMyTestSetup.fTakes4Ever2Init.DoSomething3;
end;
{ TInits4Ever2Init }
constructor TInits4Ever2Init.Create;
begin
inherited Create;
// FValue and Format('%p, %d', [Pointer(Self), FValue])) are to confirm
// that we are talking to the same object for all the tests,
// but that the object is different each time we run the test suite.
Randomize;
FValue := Random(10000);
OutputDebugString(pAnsiChar('-- TInits4Ever2Init.Create: '
+ Format('%p, %d', [Pointer(Self), FValue])));
end;
procedure TInits4Ever2Init.DoSomething1;
begin
OutputDebugString(pAnsiChar('-- TInits4Ever2Init.DoSomething1: '
+ Format('%p, %d', [Pointer(Self), FValue])));
end;
procedure TInits4Ever2Init.DoSomething2;
begin
OutputDebugString(pAnsiChar('-- TInits4Ever2Init.DoSomething2: '
+ Format('%p, %d', [Pointer(Self), FValue])));
end;
procedure TInits4Ever2Init.DoSomething3;
begin
OutputDebugString(pAnsiChar('-- TInits4Ever2Init.DoSomething3: '
+ Format('%p, %d', [Pointer(Self), FValue])));
end;
initialization
RegisterTest(TMyTestSetup.Create(TMyTest.Suite));
end.
As the comments in the sample indicate, I have used a randomised private variable, and some debug trace output, to confirm that each test call with the test suite is to the same copy of the target object, but that we are getting a different copy of the target object each time the test suite is run.

You can derive a new Test Suite class from TTestSuite class, and override its SetUp and TearDown methods, then you can add your test cases to this particular test suite, and register the suite.
This way, Setup and TearDown methods of your test suite class will be called once, and SetUp and TearDown methods of each test case will be called for every test method defined in that test case.
Execution order will be like this:
TestSuite.SetUp;
-- TestCase1.Setup;
---- TestCase1.Test1;
-- TestCase1.TearDown;
-- TestCase1.Setup;
---- TestCase1.Test2;
-- TestCase1.TearDown;
-- TestCase2.Setup;
---- TestCase2.Test1;
-- TestCase2.TearDown;
-- TestCase2.Setup;
---- TestCase2.Test2;
-- TestCase2.TearDown;
-- TestCaseN.Setup;
---- TestCaseN.Test1;
-- TestCaseN.TearDown;
-- TestCaseN.Setup;
---- TestCaseN.Test2;
-- TestCaseN.TearDown;
TestSuite.TearDown;

Having just one published method, which in turn call all your other test methods is the
lazy but quicker way of having the Setup and TearDown procedure called only once.

You can't initialize TTestCase fields for a whole test suite, and here is an explanation why:
unit Tests3;
interface
uses
TestFramework, TestExtensions, Windows, Forms, Dialogs, Controls, Classes,
SysUtils, Variants, Graphics, Messages;
type
TMyTestCase = class(TTestCase)
private
FValue: Integer;
published
procedure Test1;
procedure Test2;
end;
implementation
{ TMyTestCase }
procedure TMyTestCase.Test1;
begin
FValue:= 99;
ShowMessage(Format('%p, %d', [Pointer(Self), FValue]));
end;
procedure TMyTestCase.Test2;
begin
ShowMessage(Format('%p, %d', [Pointer(Self), FValue]));
end;
initialization
RegisterTest(TMyTestCase.Suite);
end.
If you run the above unit test you will see that the 'Self' addresses shown in Test1 and Test2 methods are different. That means that TMyTestCase object instances are different for Test1 and Test2 calls.
Consequently, any fields you may declare in TMyTestCase class are volatile between test method's calls.
To perform "global" initialization you should declare your object globally, not as TMyTestCase field.

Using TTestSetup you could do something like this:
type
TMyTestSetup = class(TTestSetup)
private
FValue: Integer;
protected
procedure SetUp; override;
procedure TearDown; override;
end;
TMyTestCase = class(TTestCase)
published
procedure TestSomething;
end;
var
TestSetup: TMyTestSetup;
procedure TMyTestSetup.SetUp;
begin
inherited;
TestSetup := Self;
FValue := 42;
end;
procedure TMyTestSetup.TearDown;
begin
TestSetup := nil;
inherited;
end;
procedure TMyTestCase.TestSomething;
begin
CheckEquals(TestSetup.FValue, 42);
end;
initialization
TestFramework.RegisterTest(TMyTestSetup.Create(
TTestSuite.Create('My test suite', [TMyTestCase.Suite])
));
It feels somewhat revolting mind you, but it does the job!

Depending on your Delphi version, you can simply make the TMyTest.fTakes4Ever2Init field a public class var to initialize it from the test setup. (This would be more OOP style compared to a unit-global variable.)

The better solution (... IMHO)
It's a pretty old question, but I can imagine people still bumping into this. I did.
My initial solution to this problem also used class vars or globals. But indeed this solution is bad as it makes it very hard to re-use TTestSetup derived classes. Hence I debugged a bit to find how DUnit works internally. (I use DUnit extensively on my flagship app and libs)
As it turns out you actually can get access to the subtests: from within TTestSetup.RunTest. In this method you get a handle to the wrapped/decorated Subtest, which actually turned out to be a TTestSuite, created from my TTestCase.Suite. So I loop through the ITestsuite subtests (which are actually method calls for each published method in your TtestCase), and check if they support my ITestDecoratable interface, if so I call the SetupDecoration.
Next, the actual test is performed by calling the inherited Runtest.
And finally we go through the same loop again, this time calling TearDownDecoration.
This did not fix the nested TTestsetup case, so I added a check if TTestDecorator.Test supports ITestDecoratable directly, and execute accordingly. For that matter, I alsom implemented the ITestDecoratable in my TDecoratedTestSetup so nesting is also supported.
And came up with this solution. I even created a unit test for it, and everything works as intended.
I can imagine one would rather implement these methods in TTestCase and TTestDecorator directly, but for now I have put it in a separate unit. I'll add a ticket to the corresponding sourceforge site.
Here's my solution:
unit uDecoratorTestBase;
interface
uses TestFramework,TestExtensions;
type
/// <summary>
/// when a test implements the interface below, and the TDecoratedTestSetup
/// is used, these methods get called dureing testing.
/// </summary>
ITestDecoratable=interface (ITest)
['{468A66E9-937B-4C45-9321-A1796F93470C}']
/// <summary>
/// gets called before the Setup call
/// </summary>
procedure SetupDecoration(const aDecorator:ITestDecorator);
/// <summary>
/// gets called after the teardown call
/// </summary>
procedure TeardownDecoration(const aDecorator:ITestDecorator);
end;
/// <summary>
/// an alternatine to TTestSetup this implementation tries to decorate
/// any subtests when it is executed through the ITestDecoratable interface
/// bonus feature is that iself also supports the ItestDecoratable interface
/// allowing for multiple layes of decoration
/// </summary>
TDecoratedTestSetup=class(TTestDecorator,ITestDecoratable)
private
protected
procedure RunTest(ATestResult: TTestResult); override;
procedure SetupDecoration(const aDecorator:ITestDecorator); virtual;
procedure TeardownDecoration(const aDecorator:ITestDecorator); virtual;
end;
/// <summary>
/// Same as TTestcase, but adds the ITestDecoratable interface. Override
/// the routines below to get values from the decorator class through
/// the provided ITestDecorator interface.
/// </summary>
TDecoratedTestCase=class(TTestCase,ITestDecoratable)
protected
procedure SetupDecoration(const aDecorator:ITestDecorator); virtual;
procedure TeardownDecoration(const aDecorator:ITestDecorator); virtual;
end;
implementation
uses
sysutils;
{ TDecoratedTestSetup }
procedure TDecoratedTestSetup.RunTest(ATestResult: TTestResult);
var lDecoratable:ITestDecoratable;
var lSuite:ITestSuite;
begin
if Supports(Test,ITestDecoratable,lDecoratable) then
try
lDecoratable.SetupDecoration(self);
inherited;
finally
lDecoratable.TeardownDecoration(self);
end
else if Supports(Test,ITestSuite,lSuite) then
try
for var I := 0 to lSuite.Tests.Count-1 do
if Supports(lSuite.Tests[i],ITestDecoratable,lDecoratable) then
lDecoratable.SetupDecoration(self);
inherited;
finally
for var I := 0 to lSuite.Tests.Count-1 do
if Supports(lSuite.Tests[i],ITestDecoratable,lDecoratable) then
lDecoratable.TeardownDecoration(self);
end
else inherited;
end;
procedure TDecoratedTestSetup.SetupDecoration(const aDecorator: ITestDecorator);
begin
// override to initialize class fields using the decorator
end;
procedure TDecoratedTestSetup.TeardownDecoration(const aDecorator: ITestDecorator);
begin
// override to finalize class fields previously initialized through SetupDecoration
end;
{ TDecoratedTestCase }
procedure TDecoratedTestCase.SetupDecoration(const aDecorator: ITestDecorator);
begin
// override to initialize class fields using the decorator
end;
procedure TDecoratedTestCase.TeardownDecoration(
const aDecorator: ITestDecorator);
begin
// override to finalize class fields previously initialized through SetupDecoration
end;
end.
Unit Test
And here's the unit test I created for my solution. Running this should shed some light and hopefully make you understand what's going on.
unit UnitTestDecorator;
interface
uses
TestFrameWork,uDecoratorTestBase;
type
/// <summary>
/// Perofms the actuel self-test by running decorated testcases
/// </summary>
TTestDecoratorTest=class(TTestCase)
private
protected
procedure SetUp; override;
published
procedure TestDecorated;
end;
implementation
type
TMyDecoratedTestCase=class(TDecoratedTestCase)
private
class var FDecorateCalls:integer;
class var FUndecorateCalls:integer;
protected
procedure SetupDecoration(const aDecorator:ITestDecorator); override;
procedure TeardownDecoration(const aDecorator:ITestDecorator); override;
procedure Setup; override;
procedure TearDown; override;
published
procedure CheckSetupTearDown;
procedure FailTest;
end;
TMyInnerDecoratedTestSetup=class(TDecoratedTestSetup)
private
class var FDecorateCalls:integer;
class var FUndecorateCalls:integer;
protected
procedure SetupDecoration(const aDecorator:ITestDecorator); override;
procedure TeardownDecoration(const aDecorator:ITestDecorator); override;
procedure Setup; override;
procedure TearDown; override;
published
procedure CheckSetupTearDown;
end;
TMyOuterDecoratedTestSetup=class(TDecoratedTestSetup)
private
class var FDecorateCalls:integer;
class var FUndecorateCalls:integer;
protected
procedure SetupDecoration(const aDecorator:ITestDecorator); override;
procedure TeardownDecoration(const aDecorator:ITestDecorator); override;
published
procedure CheckSetupTearDown;
end;
{ TTestDecoratorTest }
procedure TTestDecoratorTest.Setup;
begin
inherited;
TMyDecoratedTestCase.FDecorateCalls:=0;
TMyDecoratedTestCase.FUndecorateCalls:=0;
TMyInnerDecoratedTestSetup.FDecorateCalls:=0;
TMyInnerDecoratedTestSetup.FUndecorateCalls:=0;
TMyOuterDecoratedTestSetup.FDecorateCalls:=0;
TMyOuterDecoratedTestSetup.FUndecorateCalls:=0;
end;
procedure TTestDecoratorTest.TestDecorated;
begin
var lTestCaseSuite:=TMyDecoratedTestCase.Suite;
var lInnerTestSetup:=TMyInnerDecoratedTestSetup.Create(lTestCaseSuite) as ITest;
var lOuterTestSetup:=TMyOuterDecoratedTestSetup.Create(lInnerTestSetup) as ITest;
var lTestResult:=TTestResult.Create;
try
lOuterTestSetup.RunTest(lTestResult);
CheckEquals(0,lTestResult.ErrorCOunt,'lTestResult.ErrorCOunt');
CheckEquals(1,lTestResult.FailureCOunt,'lTestResult.FailureCOunt');
finally
lTestResult.Free;
end;
CheckEquals(2,TMyDecoratedTestCase.FDecorateCalls,'TMyDecoratedTestCase.FDecorateCalls');
CheckEquals(TMyDecoratedTestCase.FDecorateCalls,TMyDecoratedTestCase.FUndecorateCalls,'TMyDecoratedTestCase.FUndecorateCalls');
CheckEquals(1,TMyInnerDecoratedTestSetup.FDecorateCalls,'TMyInnerDecoratedTestSetup.FDecorateCalls');
CheckEquals(TMyInnerDecoratedTestSetup.FDecorateCalls,TMyInnerDecoratedTestSetup.FUndecorateCalls,'TMyInnerDecoratedTestSetup.FUndecorateCalls');
CheckEquals(0,TMyOuterDecoratedTestSetup.FDecorateCalls,'TMyOuterDecoratedTestSetup.FDecorateCalls');
CheckEquals(TMyOuterDecoratedTestSetup.FDecorateCalls,TMyOuterDecoratedTestSetup.FUndecorateCalls,'TMyOuterDecoratedTestSetup.FUndecorateCalls');
end;
{ TMyDecoratedTestCase }
procedure TMyDecoratedTestCase.CheckSetupTearDown;
begin
CheckNotEquals(0,FDecorateCalls,'FDecorateCalls');
CheckEquals(0,FUnDecorateCalls,'FUnDecorateCalls');
end;
procedure TMyDecoratedTestCase.FailTest;
begin
Fail('Intentionally');
end;
procedure TMyDecoratedTestCase.Setup;
begin
inherited;
CheckNotEquals(0,FDecorateCalls,'FDecorateCalls'); // decorate must take place BEFORE setup
end;
procedure TMyDecoratedTestCase.SetupDecoration(
const aDecorator: ITestDecorator);
begin
inherited;
inc(FDecorateCalls);
end;
procedure TMyDecoratedTestCase.TearDown;
begin
inherited;
CheckEquals(0,FUnDecorateCalls,'FUnDecorateCalls'); // undecorate must take place AFTER Teardown
end;
procedure TMyDecoratedTestCase.TeardownDecoration(
const aDecorator: ITestDecorator);
begin
inherited;
inc(FUnDecorateCalls);
end;
{ TMyInnerDecoratedTestSetup }
procedure TMyInnerDecoratedTestSetup.CheckSetupTearDown;
begin
CheckNotEquals(0,FDecorateCalls,'FDecorateCalls');
CheckEquals(0,FUnDecorateCalls,'FUnDecorateCalls');
end;
procedure TMyInnerDecoratedTestSetup.Setup;
begin
inherited;
CheckNotEquals(0,FDecorateCalls,'FDecorateCalls'); // decorate must take place BEFORE setup
end;
procedure TMyInnerDecoratedTestSetup.SetupDecoration(
const aDecorator: ITestDecorator);
begin
inc(FDecorateCalls);
inherited;
end;
procedure TMyInnerDecoratedTestSetup.TearDown;
begin
inherited;
CheckEquals(0,FUnDecorateCalls,'FUnDecorateCalls'); // undecorate must take place AFTER Teardown
end;
procedure TMyInnerDecoratedTestSetup.TeardownDecoration(
const aDecorator: ITestDecorator);
begin
inherited;
inc(FUnDecorateCalls);
end;
{ TMyOuterDecoratedTestSetup }
procedure TMyOuterDecoratedTestSetup.CheckSetupTearDown;
begin
CheckEquals(0,FDecorateCalls);
CheckEquals(0,FUnDecorateCalls);
end;
procedure TMyOuterDecoratedTestSetup.SetupDecoration(
const aDecorator: ITestDecorator);
begin
inherited;
inc(FDecorateCalls);
end;
procedure TMyOuterDecoratedTestSetup.TeardownDecoration(
const aDecorator: ITestDecorator);
begin
inherited;
inc(FUnDecorateCalls);
end;
initialization
RegisterTests('Decorator Test setup extensions for DUnit',
[
TTestDecoratorTest.Suite
]);
end.

Related

Misleading memory leak on mocked method using Spring4D

I have a class TMyClass, on which I inject interface IFileManager. In this interface there is a method GetCompanyWorkbook(const ACompanyId: System.Integer; const AStream: TStream). This method fills AStream depend from ACompanyId. Everything works fine on real code, but when I run unit tests for class TMyClass and mocked IFileManager via framework Spring4D, FastMM reports for memory leak 13-20 bytes: TIndexWrapper x 1. I used last Spring4D version 1.26 from repository(branch main/master)
unit Unit1.pas
interface
DUnitX.TestFramework,
Spring.Mocking;
type
IFileManager = interface (IInvokable)
procedure GetCompanyWorkbook(const ACompanyId: System.Integer; const AStream: TStream);
end;
TMyClass = class
strict private
FFileManager: IFileManager;
public
constructor Create(const AFileManager: IFileManager);
procedure GenerateInvoice(const ACompanyId: System.Integer);
end;
[TestFixture]
TMyClassTests = class
strict private
FMockStream: TStream;
FMyClass: TMyClass;
FFileManager: Mock<IFileManager>;
procedure SetupFileManagerMock();
procedure InitMockStream(const AMockFile: string);
public
[Setup]
procedure Setup();
[TearDown]
procedure TearDown();
[TestCase('Test invoice generation', '2|invoice_2023.xls', '|')]
procedure TestGenerateInvoice(const ACompanyId: System.Integer; const AMockFile: string);
end;
implementation
uses
System.Classes,
Spring;
constructor TMyClass.Create(const AFileManager: IFileManager);
begin
Guard.CheckNotNull(AFileManager, 'AFileManager');
inherited Create();
Self.FFileManager := AFileManager;
end;
procedure TMyClass.GenerateInvoice(const ACompanyId: System.Integer);
begin
var sTmpFile := Self.GetTempInvoiceFile(ACompanyId);
var fs := TFileStream.Create(sTmpFile, fmCreate);
try
Self.FFileManager.GetComparyWorkbook(ACompanyId, fs);
// Do some operations with stream
finally
fs.Free();
end;
end;
procedure TMyClassTests.Setup();
begin
Self.FMockStream := nil;
Self.FMyClass := TMyClass.Create(Self.FFileManager);
end;
procedure TMyClassTests.TearDown();
begin
Self.FMyClass.Free();
Self.FMockStream.Free();
end;
procedure TMyClassTests.InitMockStream(const AMockFile: string);
begin
Self.FMockStream := TFileStream.Create(AMockFile, fmOpenRead);
end;
procedure TMyClassTests.SetupFileManagerMock();
begin
Self.FFileManager.Setup.Executes(
function(const callInfo: TCallInfo): TValue
begin
callInfo.Args[1].AsType<TStream>.CopyFrom(Self.FMockStream);
end)
.When(Args.Any)
.GetCompanyWorkbook(Arg.IsAny<System.Integer>, Arg.IsAny<TStream>);
end;
procedure TMyClassTests.TestGenerateInvoice(const ACompanyId: System.Integer; const AMockFile: string);
begin
Self.InitMockStream(AMockFile);
Self.SetupFileManagerMock();
Assert.WillNotRaiseAny(
procedure
begin
Self.FMyClass.GenerateInvoice(ACompanyId);
end
);
end;
The issue is that you are using this construct which is redundant:
.When(Args.Any)
.GetCompanyWorkbook(Arg.IsAny<System.Integer>, Arg.IsAny<TStream>);
Either pass Args.Any to When or use individual Arg matching on the parameters.
Passing Args.Any causes the mock internally to ignore the individual parameter matching. That causes the temporarily constructed object for the parameter matching to be leaked which is not trivial to be fixed.
Update: I was able to fix the memory leak in develop branch

Interfaced object being dumped from memory

We have a funny one.
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
ITestInterface = interface(IInvokable)
['{4059D1CD-A342-48EE-B796-84B8B5589AED}']
function GetPort: string;
function GetRoot: string;
end;
TTestInterface = class(TInterfacedObject, ITestInterface)
private
FPort: string;
FRoot: string;
public
constructor Create(FileName: TFileName);
destructor Destroy; override;
function GetPort: string;
function GetRoot: string;
end;
{ TTestInterface }
constructor TTestInterface.Create(FileName: TFileName);
begin
FPort := '8080';
FRoot := 'top';
end;
destructor TTestInterface.Destroy;
begin
// ^ Place Breakpoint here
inherited;
end;
function TTestInterface.GetPort: string;
begin
Result := FPort;
end;
function TTestInterface.GetRoot: string;
begin
Result := FRoot;
end;
type
TTestService = class
protected
FTest : TTestInterface;
public
constructor Create;
destructor Destroy; override;
procedure Process;
end;
{ TTestService }
constructor TTestService.Create;
begin
FTest := TTestInterface.Create('');
(FTest as IInterface)._AddRef;
end;
destructor TTestService.Destroy;
begin
FTest.Free;
inherited;
end;
procedure TTestService.Process;
begin
writeln( 'Config Root: ', FTest.GetRoot );
writeln( 'Config Port: ', FTest.GetPort );
end;
var
TS : TTestService;
begin
TS := TTestService.Create;
try
TS.Process;
finally
TS.Free;
end;
end.
When this application finishes it generates an Invalid Pointer Operation.
The really strange part is that setting a break point on the destructor, you can see that it generates the error the first time it gets called, which rules out it being freed twice. It is almost as if the object is dumped from memory without calling the destructor at all.
By removing the _AddRef everything works as expected.
We managed to produce this on Delphi 6. Can anyone confirm this behavior on any other version?
Use two variables: one for the class, and one for the interface.
Use the interface variable to manage the instance lifetime. Don't call free, but set the interface variable to nil (or out of scope) to let the instance running.
Use the class variable to have direct raw access to the instance, if needed - but it shouldn't be the case, or at least let the class be accessible only from protected/private members of the owner class.
So your code becomes:
type
TTestService = class
protected
FTest: ITestInterface;
FTestInstance : TTestInterface;
public
constructor Create;
procedure Process;
end;
{ TTestService }
constructor TTestService.Create;
begin
FTestInstance := TTestInterface.Create('');
FTest := FTestInstance;
end;
procedure TTestService.Process;
begin
writeln( 'Config Root: ', FTest.GetRoot );
writeln( 'Config Port: ', FTest.GetPort );
end;
var
TS : TTestService;
begin
TS := TTestService.Create;
try
TS.Process;
finally
TS.Free;
end;
end.
The problem is that you are manually freeing an interfaced object that has a reference count greater than zero. The exception is raised here :
procedure TInterfacedObject.BeforeDestruction;
begin
if RefCount <> 0 then {!! RefCount is still 1 - you made it that way!}
Error(reInvalidPtr);
end;
So... you could just call (FTest as IInterface)._Release; in the destructor in place of FTest.Free, but this feels like fixing one mistake by making another. Either you want reference counting or you don't - if you do, then you should work with the object in that way (using interface variables and letting scope and variable lifetime manage the object lifetime). If you don't want reference counting then disable it. Either way you should pick a lifetime management model and work with it in the normal way.
Case 1 : Disable Reference Counting
If you want to disable automatic reference counting and you're using Delphi 2009 or higher you can simply do this by inheriting from TSingletonImplementation instead of TInterfacedObject :
TTestInterface = class(TSingletonImplementation, ITestInterface)
private
FPort: string;
FRoot: string;
public
constructor Create(FileName: TFileName);
destructor Destroy; override;
function GetPort: string;
function GetRoot: string;
end;
Otherwise, you can implement this yourself by adding the required methods :
TTestInterface = class(TObject, ITestInterface)
private
FPort: string;
FRoot: string;
{ ** Add interface handling methods ** }
protected
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
{ ** ---------------------- ** }
public
constructor Create(FileName: TFileName);
destructor Destroy; override;
function GetPort: string;
function GetRoot: string;
end;
which you implement as :
function TTestInterface.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
if GetInterface(IID, Obj) then
Result := S_OK
else
Result := E_NOINTERFACE;
end;
function TTestInterface._AddRef: Integer;
begin
Result := -1;
end;
function TTestInterface._Release: Integer;
begin
Result := -1;
end;
Case 2 : Use Interface References Normally
If you absolutely need reference counting and you still need to access the concrete class members then the simplest solution is to strictly use interface variables, let your container class pin the object lifetime, and cast to the concrete type when needed. Lets introduce some state to the class :
TTestInterface = class(TInterfacedObject, ITestInterface)
private
FPort: string;
FRoot: string;
public
Foo : integer; { not an interface member...}
constructor Create(FileName: TFileName);
destructor Destroy; override;
function GetPort: string;
function GetRoot: string;
end;
Your container class then becomes :
type
TTestService = class
protected
FTest : ITestInterface;
public
constructor Create;
procedure Process;
end;
{ TTestService }
constructor TTestService.Create;
begin
FTest := TTestInterface.Create('');
end;
procedure TTestService.Process;
begin
writeln( 'Config Root: ', FTest.GetRoot );
writeln( 'Config Port: ', FTest.GetPort );
WriteLn( 'Foo : ', TTestInterface(FTest).Foo); {Cast to access class members}
end;
Note that the above cast of TTestInterface(FTest) only works in Delphi 2010 and higher. For versions older than this you must keep a separate object reference as in #ArnaudBouchez's answer. In either case, the point is to use interface references in the normal way to manage the object lifetime and to not rely on hacking the reference count manually.

TIdSync synchronizes incorrectly

I have a problem that when I synchronize TIdSync in Delphi 7 (with latest Indy) inside multiple threads at the same time, it executes the same TIdSync instance multiple times.
Here is a simple code. TIdSync is created and called inside my TThread. It should pass and show its handle for this case. But I get a list with same handles in the memo. It works correctly in Delphi 2010+ (probably because it supports anonymous methods).
I am not sure if I understand how TIdSync should work, use it wrong, or there is a bug or some race condition?
type
TMySync = class(TIdSync)
protected
procedure DoSynchronize; override;
public
FID: Integer;
end;
type
TTestThread = class(TThread)
private
{ Private declarations }
protected
procedure Execute; override;
end;
procedure TTestThread.Execute;
var
sync: TMySync;
begin
// synchronize some data
sync:=TMySync.Create;
try
sync.FID:=Integer(sync); // this handle
sync.Synchronize;
finally
sync.Free;
end;
end;
procedure TMySync.DoSynchronize;
begin
// show handle and stored handle
Form1.Memo1.Lines.Add(IntToStr(Integer(Self))+' : '+IntToStr(FID));
end;
procedure TForm1.Button2Click(Sender: TObject);
var
i: Integer;
begin
// execute multiple threads
for i:=0 to 10 do
with TTestThread.Create(True) do
Resume;
end;
I get list like this in Delph 7 with same hadnles (so it does not pass right values)
38972948 : 38972948
38970260 : 38970260
38970260 : 38970260
38970260 : 38970260
...
and in Delphi 2010 it is correct
39063248 : 39063248
39063296 : 39063296
39063312 : 39063312
39063328 : 39063328
It is quite unpleasant problem as you see. Any hint appreciated.
Please check again with a small change to lock out the reuse of memory addresses:
type
TTestThread = class( TThread )
private
sync: TMySync;
protected
procedure Execute; override;
public
destructor Destroy; override;
end;
{ TTestThread }
destructor TTestThread.Destroy;
begin
inherited;
sync.Free;
end;
procedure TTestThread.Execute;
begin
inherited;
sync := TMySync.Create;
sync.Id := Integer( sync );
sync.Synchronize;
end;
As you will see, there is no problem at all

Delphi customised constructor in TComponent never runs

i am new to delphi and i am creating a component in delphi 6. but i can't get the constructor to run:
unit MyComms1;
...
type
TMyComms = class(TComponent)
public
constructor MyConstructor;
end;
implementation
constructor TMyComms.MyConstructor;
begin
inherited;
ShowMessage('got here');
end;
it doesn't matter what the constructor is called, but this code doesn't run the constructor at all.
edit
by request, here is how the TMyComms class is initialized (this code is in a different file called TestComms.pas):
unit TestComms;
interface
uses MyComms1, ...
type
TForm1 = class(TForm)
MyCommsHandle = TMyComms;
...
procedure BtnClick(Sender: TObject);
private
public
end;
var
Form1: TForm1;
implementation
procedure TForm1.BtnClick(Sender: TObject);
begin
MyCommsHandle.AnotherMyCommsProcedure;
end;
edit 2
reading some of the answers it looks like constructors must be manually called in delphi. is this correct? if so then this is certainly my main error - i am used to php where the __construct function is automatically called whenever a class is assigned to a handle.
Most likely you are not calling TMyComms.MyConstructor to test your unusual called and used constructor. The way marked with // ** would be th most usual.
type
TMyComms = class(TComponent)
public
constructor MyConstructor;
// the usual override;
// constructor Create(Owner:TComponent);override; // **
constructor Create(AOwner:TComponent);overload; override;
constructor Create(AOwner:TComponent;AnOtherParameter:Integer);overload;
end;
constructor TMyComms.Create(AOwner: TComponent);
begin
inherited ;
ShowMessage('got here Create');
end;
constructor TMyComms.Create(AOwner: TComponent; AnOtherParameter: Integer);
begin
inherited Create(AOwner);
ShowMessage(Format('got here Create with new parametere %d',[AnOtherParameter]));
end;
constructor TMyComms.MyConstructor;
begin
inherited Create(nil);
ShowMessage('got here MyConstructor');
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
TMyComms.MyConstructor.Free;
TMyComms.Create(self).Free;
TMyComms.Create(self,1234).Free;
end;
Your code does not follow the Delphi naming guidelines - the constructor should be named Create.
Since you didn't posted the code actually calling the ctor, I guess, that you may not have called it at all. Try to add a button to your form, doubleclick it and add the following code:
procedure TForm1.Button1Click(Sender : TObject)
var comms : TMyComms;
begin
comms := TMyComms.MyConstructor;
comms.Free;
end;
By the way, if you derive from TComponent, you should override constructor with a parameter - otherwise inherited methods may not work properly.
interface
type TMyComms = class(TComponent)
private
protected
public
constructor Create(AOwner : TComponent); override;
end;
implementation
constructor TMyComms.Create(AOwner : TComponent)
begin
inherited Create(AOwner);
// Your stuff
end;
// Somewhere in code
var comms : TMyComms;
begin
comms := TMyComms.Create(nil);
end;
Your custom constructor is not called because you did not call it.
MyComm := TMyComms.MyConstructor;
But you also have an error in your code. Because there is no derived constructor you can inherite with simple inherited.
type
TMyComms = class(TComponent)
public
constructor MyConstructor;
end;
implementation
constructor TMyComms.MyConstructor;
begin
inherited Create( nil ); // !
ShowMessage('got here');
end;
You can use the simple inherited if your custom constructor use the same name and parameters from an existing constructor.
type
TMyComms = class(TComponent)
public
constructor Create( AOwner : TComponent ); override;
end;
implementation
constructor TMyComms.Create( AOwner : TComponent );
begin
inherited; // <- everything is fine
ShowMessage('got here');
end;

Inheriting a method from the ancestor's ancestor

I am working on a component that is derived from a commercial component suite, and have run into a challenge, which I've never considered before. Consider the following code snippet:
TMyClass = class
protected
procedure SomeMethod; virtual;
end;
TMyClass1 = class(TMyClass)
protected
procedure SomeMethod; override;
end;
TMyMode = (mmOne, mmTwo);
TMyClass2 = class(TMyClass1)
private
FMode: TMyMode;
protected
procedure SomeMethod; override;
public
property Mode: TMyMode read FMode write FMode;
end;
...
procedure TMyClass2.SomeMethod;
begin
if FMode = mmOne then inherited SomeMethod
else inherited TMyClass.SomeMethod;
end;
So if Mode = mmOne then I inherit as normal, but if it is mmTwo, I still want to inherit the code from my ancestor's ancestor, but not what was introduced in the ancestor. I've tried the above, with no success, and since I've never encountered this before, I gather it's not possible. Any takers?
You can do this with class helpers:
type
TA = class
public
procedure X; virtual;
end;
TB = class(TA)
public
procedure X; override;
end;
TA_Helper = class helper for TA
procedure A_X;
end;
TC = class(TB)
public
procedure X; override;
end;
procedure TA.X;
begin
// ...
end;
procedure TB.X;
begin
// ...
end;
procedure TA_Helper.A_X;
begin
inherited X; // TA.X
end;
procedure TC.X;
begin
A_X;
inherited X; // TB.X
end;
I think class helpers exist in D2006, but if they don't, you can also use a hack to the same effect:
// ...
TA_Helper = class(TA)
procedure A_X;
end;
// ...
procedure TC.X;
begin
TA_Helper(Self).A_X;
inherited X; // TB.X
end;
there is another solution of this task without class-helpers or additional methods (as in #hvd answer). you can get base class methods code address and invoke it with self Data-pointer:
updated code, without rtti
unit Unit4;
interface
type
TA = class(TObject)
protected
procedure Test(); virtual;
end;
TB = class(TA)
protected
procedure Test(); override;
end;
TC = class(TB)
public
procedure Test(); override;
end;
implementation
procedure TA.Test;
begin
writeln('TA.Test()');
end;
procedure TB.Test;
begin
writeln('TB.Test');
end;
procedure TC.Test;
var TATest : procedure of object;
begin
writeln('TC.Test();');
writeln('call inherited TB: ');
inherited Test();
writeln('call inherited TA:');
TMethod(TATest).Data := self;
TMethod(TATest).Code := #TA.Test;
TATest();
end;
end.

Resources