Board index » delphi » BP7/DOS: Chaining object methods (rather than calling them)

BP7/DOS: Chaining object methods (rather than calling them)

Consider:
    type
        pMyObject = ^MyObject;
        MyObject = object
            pNextObj : pMyObject;
            procedure Run;
        end;

    procedure MyObject.Run;
    begin
        { Do some stuff }
        { ... }
        if pNextObj<>NIL then
            pNextObj^.Run;
    end;

In other words, a linked list of objects where telling the first one to
"Run" will result in all other objects in the list running as well, in
sequence.

If there is a large number of items in the list then the stack
requirements of this are a problem. Given that I know that each Run
procedure calls the next object's Run procedure (if present) _always as
the last thing it does_ it would make much more sense to jump to the
subsequent Run procedures, rather than calling them.

One method is to have another object controlling them:

    procedure NewObject.Run;
    var P : pMyObject;
    begin
        P := First;
        while (P<>NIL) do
            begin
            P^.Run;
            P := P^.Next;
            end;
    end;

However, this to me is unnecessary (and in the application I am using it
in I have other reasons to wish to take the first method).

All I need, I assume, is some inline code. Problem is I don't know _what_
inline code :^(. Ideally what I suppose I'm looking for is something
along the lines of:

    procedure JumpToObjectMethod(pObj, pMethod : pointer); inline;

Any thoughts?

Mark.
--
int MeaningOfLife (void) { return 42; }   // m...@holly.demon.co.uk
function MeaningOfLife :integer; begin MeaningOfLife := 6 * 9 end;

 

Re:BP7/DOS: Chaining object methods (rather than calling them)


Quote
Mark Rogers wrote:
> All I need, I assume, is some inline code. Problem is I don't know _what_
> inline code :^(. Ideally what I suppose I'm looking for is something
> along the lines of:

>     procedure JumpToObjectMethod(pObj, pMethod : pointer); inline;

Here's some ASM rather than inline code that should show you how
to do what you need when the methods you want to call don't take
any parameters...  if you figure out how to make it work when
the method takes parameters, let me know.

I often like to link a TButton (in TurboVision) to an object and
one of its methods so that when the button is clicked on, that
method of that object gets executed.  Here's part of the code I
use within the overridden TButton.HandleEvent for my descendant of
TButton.  (PObj and PMethod are fields of that descendant.)

  procedure ExecMethod;
  var PO,PM: Pointer;
  begin
     if (PObj <> NIL) and (PMethod <> NIL)  
     then begin
            PO := PObj;    {Can't figure why I have to have these two
lines}
            PM := PMethod; {instead of using PObj and PMethod
directly.    }
            asm
              LES  DI, PO
              Push ES
              Push DI
              Call DWord Ptr PM
            end
          end
  end;

--
Rob Stow                When using the "reply" feature of your mail
s...@sk.sympatico.ca    or news reader, remove the asterix ( "*" ) from
                        the address so that it becomes like the one
                        at the left.

Re:BP7/DOS: Chaining object methods (rather than calling them)


Quote
Mark Rogers <m...@holly.demon.co.uk> wrote:

>Consider:
>    type
>        pMyObject = ^MyObject;
>        MyObject = object
>            pNextObj : pMyObject;
>            procedure Run;
>        end;

>    procedure MyObject.Run;
>    begin
>        { Do some stuff }
>        { ... }
>        if pNextObj<>NIL then
>            pNextObj^.Run;
>    end;

>In other words, a linked list of objects where telling the first one to
>"Run" will result in all other objects in the list running as well, in
>sequence.

One solution is to create separate methods.  Remove the logic that executes
linked objects from the "Run" method and create a new "RunAll" method.

   procedure MyObject.Run;
   begin
       { Do some stuff }
       { ... }
   end;

   procedure MyObject.RunAll;
   VAR p: pMyObject;
   begin
       p := @Self;
       REPEAT
          p^.Run;
          p := pNextObj;
       UNTIL p = NIL;
   end;

Now when you invoke RunAll for the object at the head of the list, all Run
methods will be executed in turn.  While this doesn't use a new object, it is
similar to your controlling object solution.  

Quote
>All I need, I assume, is some inline code. Problem is I don't know _what_
>inline code :^(. Ideally what I suppose I'm looking for is something
>along the lines of:

>    procedure JumpToObjectMethod(pObj, pMethod : pointer); inline;

This is a GOTO of extreme proportions that is easier said than done, but I guess
that's the reason for your post. :-)

The problem is the current method has built a stack frame, and when the next
method is entered, it too will build a stack frame.  This entry code will not
only cause stack overflow problems, but it must eventually be removed.

That's the scenario if you think in terms of tearing done the stack frame,
setting new parameters and jumping to a new method.  Truth is, things are much
simpler than that.  You are already "in" the method, so all you have to do is
modify parameters and "jump" back to the top of the block.

The following is similar to the previous two method solution or your control
object solution, but will not incur the overhead of calling a separate method
for each object.

   procedure MyObject.Run;
   LABEL AGAIN;
   begin
       AGAIN:
       { Do some stuff }
       { ... }
       ASM
          les  DI,[Self]
          les  DI,[ES:DI.pNextObj]
          mov  AX,ES
          or   AX,DI
          jz   @Exit

          mov  [Self.Word.0],DI
          mov  [Self.Word.2],ES
          jmp  AGAIN
       @Exit:
       END;
   end;

If you are using TP 5.5, or prefer to hide the GOTO aspect of this operation,
then you can use the following. :-)

 Procedure MyObject.Run;
 VAR MORE: WORD; { <**<< MUST BE FIRST LOCAL VARIABLE! <**<< }
 BEGIN
     REPEAT
        { Do some stuff }
        { ... }
        INLINE( $C4 /$7E /$06   { les  di,[bp+06] - Self }
               /$26 /$C4 /$3D   { les  di,es:[di]        }
               /$8C /$C0        { mov  ax,es             }
               /$09 /$F8        { or   ax,di             }
               /$89 /$46 /$FE   { mov  [bp-02],ax - More }
        );
     UNTIL More = 0;
   END;

Hope this helps to provide an explanation as well as a solution.

    ...red
cc

Re:BP7/DOS: Chaining object methods (rather than calling them)


Quote
rdon...@southeast.net (R.E.Donais) wrote:

[snip discussion on creating a tail recursive method ...]

My previous asm/inline solution assumes a static method.  If "run" is a virtual
method, you could not iterate through the list, but would have to invoke the
proper virtual method for each object.

In order to reference a virtual method you have to know its offset within the
object's virtual method table (VMT).  It's a shame that the compiler doesn't
contain a  feature to return this information.  In order to determine the
offset, you have to count all virtual methods, in the order that they were
initially declared. The position has nothing to do where a virtual method may be
declared in a descendant object, just its position when the method was declared
for the first time.  

For example, if your object was a descendent of tObject, then tObject.Done would
be the first virtual method.  If you then declared run and over-ride done, done
would still be first regardless if it appeared before or after "run" in the
descendant object.

As you count virtual methods, number the first method 0, the second 1, third, 2,
etc.  Then define the method's offset as:

CONST  MyObject_RUN = 0 * Sizeof(Pointer) + VMTheaderSize;

or if a direct descendant of tObject, maybe:

CONST Done_Method = 0 * Sizeof(Pointer) + VMTheaderSize;
      Run_Method  = 1 * Sizeof(Pointer) + VMTheaderSize;

PROCEDURE MyObject.Run;
BEGIN
    { Do some stuff }
    { ... }
    ASM
       les  DI,[Self]
       les  DI,[ES:DI.pNextObj]     { get next object }
       mov  AX,ES
       or   AX,DI
       jz   @Exit

       mov  [Self.Word.0],DI  { update Self to point  }
       mov  [Self.Word.2].ES  { to the next object &  }
       mov  BX,[ES:DI]        { get offset to the VMT }

       mov  SP,BP   { discard local variables. if any }
       pop  BP      { restore caller's stack frame    }

       { At this point, the stack has been restored to }
       { the state it was in just after the original   }
       { call. Since that return address is currently  }
       { at the top of the stack, we can jump through  }
       { the appropriate VMT entry to the new address  }
       { to simulate a call to the new run method.     }

       JMP  DWORD PTR [DS:BX.MyObject_RUN_Method]
  @Exit:
  end;

    ...red
cc Mark Rogers <m...@holly.demon.co.uk>

Re:BP7/DOS: Chaining object methods (rather than calling them)


Quote
Rob Stow wrote:
> I often like to link a TButton (in TurboVision) to an object and
> one of its methods so that when the button is clicked on, that
> method of that object gets executed.  Here's part of the code I
> use within the overridden TButton.HandleEvent for my descendant of
> TButton.  (PObj and PMethod are fields of that descendant.)

>   procedure ExecMethod;
>   var PO,PM: Pointer;
>   begin
>      if (PObj <> NIL) and (PMethod <> NIL)
>      then begin
>             PO := PObj;    {Can't figure why I have to have these two lines}
>             PM := PMethod; {instead of using PObj and PMethod directly.    }
>             asm
>               LES  DI, PO
>               Push ES
>               Push DI
>               Call DWord Ptr PM
>             end
>           end
>   end;

Greetings all;
Perhaps I am missing the point here, but why not just have the button handler call the method as per
normal procedures, without asm code etc?  Since you have saved the object as PObj, why not just

PObj^.Method ?

This works in BP7/Win, so I expect it would be ok under TV.

I presume that the Obj type is known at compile time; if not, you could force fixed virtual table
entries, again without resorting to asm.

Cheers
--
p. rowntree
Departement de chimie, Universite de Sherbrooke, Sherbrooke, Quebec, Canada

Re:BP7/DOS: Chaining object methods (rather than calling them)


Quote
Paul Rowntree wrote:

> Rob Stow wrote:

> > I often like to link a TButton (in TurboVision) to an object and
> > one of its methods so that when the button is clicked on, that
> > method of that object gets executed.  Here's part of the code I
> > use within the overridden TButton.HandleEvent for my descendant of
> > TButton.  (PObj and PMethod are fields of that descendant.)

> >   procedure ExecMethod;
> >   var PO,PM: Pointer;
> >   begin
> >      if (PObj <> NIL) and (PMethod <> NIL)
> >      then begin
> >             PO := PObj;    {Can't figure why I have to have these two lines}
> >             PM := PMethod; {instead of using PObj and PMethod directly.    }
> >             asm
> >               LES  DI, PO
> >               Push ES
> >               Push DI
> >               Call DWord Ptr PM
> >             end
> >           end
> >   end;

> Greetings all;
> Perhaps I am missing the point here, but why not just have the
> button handler call the method as per normal procedures, without
> asm code etc?  Since you have saved the object as PObj, why not just

> PObj^.Method ?

Now if PObj^.PMethod (where PMethod = @Method) worked that simply,
I'd think I'd died and gone to heaven.

Quote

> This works in BP7/Win, so I expect it would be ok under TV.

My reason for not doing it that way is that you have to create a new
descendant of the TButton for each different method of PObj that you
might want to use in your program.  If you are just going to have
a handful of such TButton descendants, its no big deal, but what if
you want to have far more than just a few?

Another consideration is if you initially think you might to just
run methods TA.a TA.b, TA.c, TB.e, but later on realize that you
also need to run TC.z, doing it my way you just use the constructor
for my existing TButton descendant and you have a button that will
execute method z of the object TC when it is pressed,  Doing it
your way, you have to decide what unit to put your new TButton
descendant in, add the declaration to the interface, add the
handleevent method to the implementation section, and so on.  
With my way, I've added a button that causes
method z of object TC to my dialog box in 20 seconds (yes, I'm
a slow typist), with your way I'd be lucky to be done in 5 minutes.

A final reason is that by creating a single general-purpose TButton
descendant, I can test it thoroughly and then rely on it, instead of
creating new variants all the time that need to be individually
tested.

Quote
> I presume that the Obj type is known at compile time; if not, you could
> force fixed virtual table entries, again without resorting to asm.

One reason I've resorted to ASM isn't that I don't always know at
compile time what the object type is:  its the method that is
sometimes not known until run time.

Quote
> Cheers
> --
> p. rowntree
> Departement de chimie, Universite de Sherbrooke, Sherbrooke, Quebec, Canada

--

Re:BP7/DOS: Chaining object methods (rather than calling them)


In article: <3339f01d.434418...@news.southeast.net>  

Quote
rdon...@southeast.net (R.E.Donais) writes:

> One solution is to create separate methods.  Remove the logic that
> executes linked objects from the "Run" method and create a new
> "RunAll" method.

Good idea - I've done this now. Thanks for the suggestion.

As mentioned elsewhere in this thread, direct jumps get difficult with
virtual methods, and - you guessed it - all my methods are virtual. I'd
completely forgotten that "minor" fact.

Thanks for all the help within this thread, and sorry for the slow reply.
My newsreader is giving me problems, so I've been reading mail with
the wonderful LIST file viewer for a while - unfortunately that doesn't
give me the abilty to reply :-(

Mark.
--
int MeaningOfLife (void) { return 42; }   // m...@holly.demon.co.uk
function MeaningOfLife :integer; begin MeaningOfLife := 6 * 9 end;

Other Threads