• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 323
  • Last Modified:

Using a "Class of object" and Virtualization

I have several descendants based on "TMyObject" (which is in unit1 -see below.  Descendants are in different units).  Each descendant implements its own "Execute" method.

In each case I want to create an instance of the descendant, run its execute method and destroy it. I am trying to have one common function in unit 1 that does this (in order to avoid duplication).  However I am unable to properly instantiate the child object by passing its classtype as an argument to the "VirtualisedRun" function.   If virtualization is working OK, I'd expect DescendentClass.Create(Ptr, result) to call the create function in unit 2.  That seems to work Ok. However the virtual "Execute" doesn't seem to get called.  See pseudo code below.  

Basically, I want a working sample that can I can pass a derived class as an argument to a function defined in the parent unit, and have the function properly instantiate an object of that class.


unit Unit1;
 
interface
  TMyObject=class(TObject)
  public
    constructor Create(Ptr:Pointer; var result:LongInt);virtual;
    function Execute:longint;virtual;abstract;  //must be defined in the decendent class.
    Destructor Destroy;override;
  end;
 
  TMyObjectClass = Class of TMyObject;
 
function VirtualisedRun(DescendentClass:TMyObjectClass; Ptr: Pointer): LongInt; stdcall;
 
implementation
 
function VirtualisedRun(DescendentClass:TMyObjectClass; Ptr: Pointer): LongInt; stdcall;
 
begin;
  with DescendentClass.Create(Ptr, result) do
  try
    if result <> 0 then result := Execute;
  finally
    Destroy;
  end;
end;
 
 ============================================================
 
 unit Unit2;
 
 interface
  TMyChildObject1=class(TMyObject)
  public
    constructor Create(Ptr:Pointer; var result:LongInt);virtual;
    Destructor Destroy;override;
    function Execute:longint;virtual;
  end;
 
  function DoExecute(Ptr:Pointer):longint;
  
 implementation
 
  function DoExecute(Ptr:Pointer):longint;
  
  begin;
    result := VirtualisedRun(TMyChildObject, Ptr);
  end;
  
 
constructor TMyChildObject1.Create(Ptr:Pointer; var result:LongInt);virtual;
 
begin;
  inherited create(Ptr, result);
  FOtherObject := TOtherObject.Create;
end;
 
destructor TMyChildObject1.Destroy;
 
begin;
  FOtherObject.Free;
  inherited;
end;
 
constructor TMyChildObject1.Execute:longint;
 
begin;
  result := blah...
end;

Open in new window

0
ahalya
Asked:
ahalya
  • 9
  • 8
  • 4
  • +1
2 Solutions
 
ahalyaAuthor Commented:
Please ignore this question.  I'll reword it and post it again.

The "virtual" create works. The problem seems to be in abstract "Execute"
0
 
Geert GOracle dbaCommented:
hehe, i was gonna post a possible solution ...
0
 
Geert GOracle dbaCommented:
still interested ?
0
Cloud Class® Course: Amazon Web Services - Basic

Are you thinking about creating an Amazon Web Services account for your business? Not sure where to start? In this course you’ll get an overview of the history of AWS and take a tour of their user interface.

 
ahalyaAuthor Commented:
Sure, why not.

Go ahead. please.  Thanks.


 



0
 
Russell LibbySoftware Engineer, Advisory Commented:
You should be overriding in the child class...

---- Base ----
unit Base;

interface

type
  TMyObject         =  class(TObject)
  public
     constructor    Create(Ptr: Pointer; var result: LongInt); virtual;
     function       Execute: LongInt; virtual; abstract;
     destructor     Destroy; override;
  end;

type
  TMyObjectClass    =  class of TMyObject;

function   VirtualisedRun(DescendentClass: TMyObjectClass; Ptr: Pointer): LongInt; stdcall;

implementation

function VirtualisedRun(DescendentClass: TMyObjectClass; Ptr: Pointer): LongInt; stdcall;
begin;

  with DescendentClass.Create(Ptr, result) do
  try
     if (result <> 0) then result:=Execute;
  finally
     free;
  end;

end;

constructor TMyObject.Create(Ptr: Pointer; var result: LongInt);
begin

  inherited Create;
  result:=0;

end;

destructor TMyObject.Destroy;
begin

  inherited Destroy;

end;

end.

---- Child1 ----

unit Child1;

interface

uses
  Windows, SysUtils, Classes, Base;

type
  TMyChildObject1   =  class(TMyObject)
  private
     // Private declarations
     FOtherObject:  TStringList;
  public
     constructor    Create(Ptr: Pointer; var result: LongInt); override;
     destructor     Destroy; override;
     function       Execute: LongInt; override;
  end;

function   DoExecute(Ptr: Pointer): LongInt;

implementation

function DoExecute(Ptr: Pointer): LongInt;
begin;

  result:=VirtualisedRun(TMyChildObject1, Ptr);

end;

function TMyChildObject1.Execute: LongInt;
begin

  // Example
  result:=FOtherObject.Count;

end;

constructor TMyChildObject1.Create(Ptr: Pointer; var result: LongInt);
begin;

  // Perform inherited
  inherited Create(Ptr, result);

  // Example
  Inc(result);

  // Startup
  FOtherObject:=TStringList.Create;

end;

destructor TMyChildObject1.Destroy;
begin;

  // Resource protection
  try
     // Cleanup
     FOtherObject.Free;
  finally
     // Perform inherited
     inherited Destroy;
  end;

end;

end.

----------

Regards,
Russell
0
 
Geert GOracle dbaCommented:
i believe what your looking for is this:
i use this to populate for example items of a combobox
i left only the relevant code for you:

the thing you're looking for is :

var
  iRoot: TRoot;
  ClassRef: TRootClass;
begin
    ClassRef := Self;
    iRoot := ClassRef.Create;

call it like
  TChildObject.DoExecute;

type
  TRoot = class(TObject)
  protected
    procedure Execute; virtual;
  public
    class procedure DoExecute;  
  end;
 
  TRootClass = class of TRoot;
 
class procedure TRoot.DoExecute;
var iRoot: TRoot;
  ClassRef: TRootClass;
begin
  Items.Clear;
  if InheritsFrom(TRoot) then
  begin
    ClassRef := Self;
    iRoot := ClassRef.Create;
    try
      with iRoot do
      begin
        // here you can call any method of your child object
        iRoot.Execute;
      end;
    finally
      FreeAndNil(iRoot);
    end;
  end;
end;
 
procedure TRoot.Execute; 
begin
  Raise Exception.Create('procedure Execute must be overriden in child !');
end;

Open in new window

0
 
ahalyaAuthor Commented:
Geert,

For your info:
When stepping in the CPU window, the call to "Execute" takes me to @AbstractError and not to the execute function on the derived class.
0
 
Geert GOracle dbaCommented:
i'm still adapting to your code, just a minute :


0
 
ahalyaAuthor Commented:
rllibby:

Your comment about missing 'override' takes me to the derived function, but still seem to have problems accessing the private variables of the derived class.  I'll check this and get back to you.

This is the first time I am using an abstract method, and am a little puzzled.  Shouldn't "abstract" imply override.  (What other option is there ?)

Geert:
Need time to study your code.
0
 
Geert GOracle dbaCommented:
You call descendants of my object like
 
type
  TChildOfRoot = class(TRoot)
  protected
    procedure Execute; override;
  end;

TChildOfRoot.DoExecute;

note the override as Russel did ...

0
 
ahalyaAuthor Commented:
Russell,

OK, got your point.  You meant overriding the constructor as well.




0
 
Russell LibbySoftware Engineer, Advisory Commented:
>> Your comment about missing 'override' takes me to the derived function, but still seem to have problems accessing the private variables of the derived class.  I'll check this and get back to you. <<

Please check my example again. My derived class created, accessed, and then freed the string list object which is declared as private in the class.

Russell


0
 
Russell LibbySoftware Engineer, Advisory Commented:
Yep.
 Keep in mind that when you declare virtual methods in a parent class, you should be overriding these methods in the children. Otherwise, you end up calling the VMT method (which is still loaded with the parent's method). Overriding allows you to extend the functionality PLUS be able to call the parent's virtual declaration by calling "inherited"

Russell

0
 
Geert GOracle dbaCommented:
this is a combination with your virtualisedrun and a class function

you can call it like
Result := TMyChildObject.Run(Ptr);

or
Result := VirtualisedRun(TMyChildObject, Ptr);
unit UnitMyObject;
 
interface
 
type
  TMyObject = class(TObject)
  public
    constructor Create(Ptr: Pointer; var result: LongInt); virtual;
    function Execute: Longint; virtual;
    destructor Destroy; override;
 
    class function Run(Ptr: Pointer): Longint;
  end;
 
  TMyObjectClass = class of TMyObject;
 
  TMyChildObject = class(TMyObject)
  private
    fOtherObject: TStrings;
  public
    constructor Create(Ptr: Pointer; var result: LongInt); override;
    destructor Destroy; override;
    function Execute: longint; override;
  end;
 
function VirtualisedRun(DescendentClass: TMyObjectClass; Ptr: Pointer): LongInt; stdcall;
 
implementation
 
function VirtualisedRun(DescendentClass: TMyObjectClass; Ptr: Pointer): LongInt; stdcall;
begin
  Result := DescendentClass.Run(Ptr);
end;
 
{ TMyObject }
 
constructor TMyObject.Create(Ptr: Pointer; var result: Integer);
begin
  inherited Create(Ptr, Result);
  // create objects
end;
 
destructor TMyObject.Destroy;
begin
  // free objects
  inherited Destroy;
end;
 
class function TMyObject.Run(Ptr: Pointer): Longint;
var iObject: TMyObject;
  ClassRef: TMyObjectClass;
begin
  Result := 0;
  if InheritsFrom(TRoot) then
  begin
    ClassRef := Self;
    iRoot := ClassRef.Create(Ptr, Result);
    try
      if Result <> 0 then
        Result := iRoot.Execute;
    finally
      FreeAndNil(iRoot);
    end;
  end;
end;
 
{ TMyChildObject }
 
constructor TMyChildObject.Create(Ptr: Pointer; var Result: LongInt);
begin;
  inherited Create(Ptr, Result);
  FOtherObject := TStringList.Create;
end;
 
destructor TMyChildObject.Destroy;
begin;
  FOtherObject.Free;
  inherited Destroy;
end;
 
function TMyChildObject.Execute: Longint;
begin;
  Result := FOtherObject.Count;
end;
 
end.

Open in new window

0
 
itsmeandnobodyelseCommented:
You posted in the C++ TA too?

Does that mean you are interested in a C++ solution as well?

0
 
ahalyaAuthor Commented:
Thanks to both of you.  I'll double the points, and then split between the two.

Russell you suggestions fixed my problem. But, I am going to adapt Geert's idea because it looks like that will simplify the code further (by adding some complexity :-).

Geert, I learnt something new from your code.  Never thought of your "Run" trick.  Using your approach, I  I don't even need a centralised "VirtualizedRun" function. I can simply call DescendentClass.Run and let it do all the work.  (in my DoExecute function in the derived unit).

Now, what is the difference between a "class function" and a "function" ? (your Run method)
0
 
ahalyaAuthor Commented:
Hey its "you" andnobodyelse:

I wouldn't not have minded whether the solution is in Delphi or C++. But already have Delphi solutions, so would not need cpp solution. (But would presume same approach works in cpp). Thans
0
 
Geert GOracle dbaCommented:
with a class function you don't need to create an instance of an object

but you don't have access to any variables of the object
as they don't exist yet (no memory reserved yet)
0
 
ahalyaAuthor Commented:
Thanks Folks.

I learnt some basics, and a neat trick
0
 
ahalyaAuthor Commented:
Geert,

I simplified your Run method to the following.  Do you see any potential pitfalls in doing a self.create ?

Am I right in assuming that there is no need to check for InheritsFrom(TFeedbackControl) because "Run" is defined as a method of TFeedbackControl ?

Thanks.



class function TFeedBackControl.Run(Ptr: Pointer): Longint;
 
begin
  with Self.Create(Ptr, Result) do
  try
    if result = ecNormal then
      Result := Execute;
  finally
    Free;
  end;
end;

Open in new window

0
 
Russell LibbySoftware Engineer, Advisory Commented:
from the docs...
>>
In the defining declaration of a class method, the identifier Self represents the class where the method is called (which could be a descendant of the class in which it is defined). If the method is called in the class C, then Self is of the type class of C. Thus you cannot use Self to access fields, properties, and normal (object) methods, but you can use it to call constructors and other class methods. A class method can be called through a class reference or an object reference. When it is called through an object reference, the class of the object becomes the value of Self.
<<

Used the way you have it is fine. And there is no need to check for InheritsFrom, as a class function can only be called from a compatible type class refererence. (Base or child)

Regards,
Russell
0
 
Geert GOracle dbaCommented:
lol,
do you know how many hours i pained my brain on this ?
now i learned something too :)
0
 
itsmeandnobodyelseCommented:
>>>> (But would presume same approach works in cpp)
Hmm. Actually I have problems to fully understand PASCAL code ...

But in C++ for your issue there is a well-known design pattern called 'factory'. The clue is to have something like a 'virtual' constructor without the baseclass have to know all derived classes. In C++ the pattern can be solved by 'registering' function pointers for each class which create a new instance of the class together with a key, e. g. the class name into a container managed by the baseclass (or some singleton Factory manager class). That way the baseclass could create instances of any derived (and registered) class by only knowing the name of the class. For example, you could write a textfile with lines preceeded with a classname followed by varying attributes separated by semicolon. Then, the baseclass could read the file line by line, parse the classname, retrieve a function pointer from the container by name, create an instance of the class using that function pointer, and pass the rest of the line to that instance to let it load its attributes. Instead of a function pointer you also could store a baseclass pointer of a sample instance of the derived class. Then, instead of a function call you would use a virtual call using the sample instance to create new instances. Each derived class would provide the function (or the object) to create new instances by means of a static initialization:

// derived.h

   class Derived : public Base
   {
        static bool registered;
        static Base* createDerived() { return new Derived(); }

        ....
   };

// derived.cpp

...
bool Derived::registered = Base::registerForFactory("Derived", Base::createDerived);


// base.h

// forward declaration
class Base;

// function pointer
typedef Base* (*CreateFunc)();
...
class Base
{
     std::map<std::string, CreateFunc> factory;
public:
     static bool registerForFactory(const std::string classname,
                                                    CreateFunc func)
     {
          factory[classname] = func;
          return true;
     }

      ...
};

Is it the same as the above solution in Delphi?
0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

Join & Write a Comment

Featured Post

Cloud Class® Course: CompTIA Healthcare IT Tech

This course will help prep you to earn the CompTIA Healthcare IT Technician certification showing that you have the knowledge and skills needed to succeed in installing, managing, and troubleshooting IT systems in medical and clinical settings.

  • 9
  • 8
  • 4
  • +1
Tackle projects and never again get stuck behind a technical roadblock.
Join Now