HELP! ForEach/FirstThat methods 
Author Message
 HELP! ForEach/FirstThat methods

Hi all

I'm stuck on trying to build a similar FirstThat routine to the one found
in turbovision's collection object.  However, I think it's my understanding
of using procedures within other procedures (poor) and assembler (awful) which
is fouling me up.

What happens is that the following object method cannot access the DataID
parameter passed to the GetHookForID. What is particularly wierd is that
the IDE *does* see it when I step through it.  And if I try to access any
variables (ie B) it cannot "see" them, and if I try to modify them I can
get all kinds of {*filter*} crashes.  How do I get this TestHookID method to
access the variables from GetHookFOrID?  The bit of assembler in .FirstThat
is cobbled out of the objects.pas unit.

I know it *can* be done because the TCollection object does it...

I hope there's enough here for some expert to just spot the problem - I
can't post the whole lot as there's a lot of code, but if anyone wants I'll
try and put together a self-contained example to demonstrate it.

Please please give a little help - it'll save me a lot of trouble and botched
code that I'm using 'cos I can't use this.

Thank you very much

Martin

{returns Node pointing to given DataID}
function TNodeStream.GetNodeForID(const FirstNodeID, DataID : longint) :
PNode;var B : boolean;

  function TestNodeID(Node : PNode) : boolean; far;
  var B2 : boolean;
  begin
    B2 := (Node^.DataID = DataID);
    TestNodeID := B2;
  end;

begin

end;

{returns first node that matches passed test condition}
function TNodeStream.FirstThat;

  {recursive part to firstthat}
  function DoNode(Test : pointer; NodeID : longint) : PNode;
  var FoundNode : PNode;
      FOund : boolean;
      Node : PNode;
  begin
    FoundNode := nil;
    while (FoundNode=nil) and (NodeID<>-1) do begin
      Node := PNode(GetAt(NodeID));

      asm
        {put the node parameter on the stack}
        LES     DI,Node
        PUSH    ES
        PUSH    DI

        PUSH    WORD PTR [BP] {no idea - used for returning?}
        CALL    Test                                    {Call test given}
        MOV     Found,AL                        {load answer - from AL to Found}
      end;

      if Found then
        FoundNode := Node
      else begin
        if (Node^.Expanded) and (Node^.ChildID<>-1) then
           FoundNode := DoNode(Test, Node^.ChildID); {do children}
        NodeID := Node^.NextID;
        dispose(Node, done);
      end;
    end;

    DoNode := FoundNode;
  end;

begin
  FirstThat := DoNode(Test, RootNodeID); {start at root}
end;

---- end ---------



Wed, 18 Jun 1902 08:00:00 GMT  
 HELP! ForEach/FirstThat methods

Quote:

>Hi all

>I'm stuck on trying to build a similar FirstThat routine to the onefound
>in turbovision's collection object.  However, I think it's myunderstanding
>of using procedures within other procedures (poor) and assembler(awful) which
>is fouling me up.

[ snip - snip - snip ]

Hello Martin,

I'll explain how TP/BP implements calls to sub-procedures, then I'll
let you take a stab at correcting your problem.  If you still can't
get it to work, let be know.

Local variables are based on the stack.  In order to reference its
variables w/o getting entangled with other things, each routine
establishes a "stack frame".  This is simply a definition of the
routine's use of the stack relative to the BP register.  Once it has
been established, the value of BP remains fixed and therefore is
immune to subsequent changes in the stack pointer (SP).

Before a routine can establish its stack frame, it must save the
current stack frame.  This is done in the routine's prologue or
setup code.  When you use BASM, the compiler does it for you so
you might not be aware of it.  Typical sequence is:

    push  BP       ; save current stack frame
    mov   SP, SP   ; establish our stack frame
    sub   SP, xxxx ; allocate storage for local variables

When the routine ends, the exit code restores the previous frame,
If there are no local variables the compiler issues a pop BP
instruction, other wise it issues -

    mov   SP, BP
    pop   BP

So far so good, but most of us already knew all this.  What we need
is to understand the technique used by BP/TP to allow a sub-procedure
to access the parameters and local variables of previous levels.  
Since you know that a hidden parameter is passed when the compiler
constructs a call to an object's method, you shouldn't be too
surprised to know a hidden parameter is passed to a sub-procedure.
The object's methods is passed a pointer called "self" which points
to the particular instance.  A sub-procedure receives a word which is
the current stack frame of its parent procedure.  

Calling a sub-procedure requires two instructions.  The first pushes
a copy of the parent's stack frame, the second is the normal call.
Since the stack frame for the sub-procedure's parent procedure is
passed, it is simply a matter of pushing BP before calling a next
level procedure.  However, things get a little tricky when we want
to call sub-procedures at different levels, or want to access local
parameters or variables belonging to a parent.  One thing that you
have to know is the "size" of the procedures.  If it is NEAR then
the parent's stack frame is at [BP+4], but if it is FAR, the parent's
stack frame will be at [BP+6].  Since a change in the $F option
could really send the program out-to-lunch, it is a good idea to use
the keywords NEAR and FAR, or enclose the routine in the appropriate
$F compiler directives.

You may not need a lot of the following to solve your problem, but
being aware of it may help you and others stay upright and between
the ditches in the future ---

PROCEDURE ParentProcess(VAR CommonParameter: ...);
VAR CommonVariable: Integer;

   Procedure NearSubProcess; NEAR;
   BEGIN
       ...
       ASM
          mov  BX, [BP+4]    { Parent's stack frame }
          mov  AX, [SS:BX+OFFSET CommonVariable]
          ....
          mov  BX, [BP+4]    { Parent's stack frame }
          les  DI, [SS:BX+OFFSET CommonParameter]
       END;
   END;

   Procedure FarSubProcess; FAR;

       Procedure PrivateProcess;
       BEGIN
       END;

       Procedure SubSubProcess; NEAR;
       BEGIN
           ...
           ASM
               mov  BX, [BP+4]     { Parent's stack frame }
               mov  BX, [SS:BX+6]  { next level stack frame }
               mov  AX, [SS:BX+OFFSET CommonVariable]
               ....

               mov  BX,[BP+4]         { Parent's stack frame }
               push [SS:BX+6]         { next level stack frame }
               call NearSubProcess

               mov  BX,[BP+4]         { Parent's stack frame }
               push [SS:BX+6]         { next level stack frame }
               call FarSubProcess

               push [BP+4]
               call SubSubProcess

               push BP
               call PrivateProcess
           END;
       END;

   BEGIN
       ...
       ASM
          push  [BP+6]   { <--<< parent's stack frame }
          call  SubProcess;
       END;
   END;

BEGIN
    ASM
        mov   AX, [CommonVariable]
        ...
        push  BP
        call  FarSubProcess
    END;
END;

Notice that in the examples that when a parent procedure calls a
child it simply pushes the BP register before making the call.  It
doesn't matter what level teh parent is, since it is teh parent, it
only has to push the BP register to pass a copy of it's stack frame.
Knowing this makes it relatively easy to implement a procedure
that is given a sub-procedure as a call back function.

PROCEDURE Process(CallBack: Pointer);
BEGIN
    ...

    ASM
         ... push any parameters
         ...
         push [BP.Word.0]           { Caller's stack frame  }
         call DWORD PTR [CallBack]  { Caller's sub-procedure }
    END;
    ...
END;

I hope I didn't make any typos.  If I did, the subsequent crash
couldn't be worse than what you already have.  :D

   ...red



Wed, 18 Jun 1902 08:00:00 GMT  
 HELP! ForEach/FirstThat methods

Thanks R.E.D, and Frank H.

I appreciate your help, and I now understand the ins andouts of BP's calling
procs.  BUT.  I can't get it to work.  Your description covered calling a
subproc from another procedure.  But I need to notonly call a subproc from
another procedures' subproc, but the callingsubproc may be recursing into
itself... (does that make sense?).  

So we have a proc A, which calls Proc B.  Proc B has a recursive SubProc,
called SubB, which calls proc A's subproc, called SubA.

So I thought, that's OK, I'll store the stack frame when entering Proc B,
and push that onto the stack when calling SubA.  Outline code below.
BUT wot happens is that SubA loses the TestFunc pointer (with all
kinds of {*filter*} crashing behaviour).  So it looks like I don't understand
it after all:

procedure A(TestFunc : pointer; RootNode : pointer);
var StackFrame : word;

  procedure SubA(Node);
  begin
     asm
       {put the node parameter on the stack}
        LES     DI,Node
        PUSH    ES
        PUSH    DI

        PUSH StackFrame                 {IDE reports stackframe contents OK}

        CALL  TestFunc                  {crash nastily}
     end
     if Node^.Child<>nil then SubA(Node);
  end;

begin
  asm
    PUSH WORD PTR [BP]
    POP StackFrame
  end

  SubA(RootNode);
end;

procedure SubB;

  function TestFunc(Node : PNode);
  begin
    <do something>
  end;

begin
  TestFunc(RootNode);
end;

Frank H says I ought to move SubA code into A, but there is a variation of
this routine where I pass the disk pointer (a longint) and it operates on
the memory linked list.  And anyway, as soon as it recurses we're in the
same problem because the stack frames are all wrong.  The above seems
dead right, the IDE debug watch window reports StackFrame right, but I seem
to be losing SubA's stackframe... ouch... wot do I do now?

Thanks a lot for your help.

Martin



Wed, 18 Jun 1902 08:00:00 GMT  
 HELP! ForEach/FirstThat methods

Quote:

>Thanks R.E.D, and Frank H.

>I appreciate your help, and I now understand the ins andouts of BP'scalling
>procs.  BUT.  I can't get it to work.  Your description coveredcalling a
>subproc from another procedure.  But I need to notonly call a subprocfrom
>another procedures' subproc, but the callingsubproc may be recursinginto
>itself... (does that make sense?).  

>So we have a proc A, which calls Proc B.  Proc B has a recursiveSubProc,
>called SubB, which calls proc A's subproc, called SubA.

>So I thought, that's OK, I'll store the stack frame when entering ProcB,
>and push that onto the stack when calling SubA.  Outline code below.
>BUT wot happens is that SubA loses the TestFunc pointer (with all
>kinds of {*filter*} crashing behaviour).  So it looks like I don'tunderstand
>it after all:

>procedure A(TestFunc : pointer; RootNode : pointer);
>var StackFrame : word;

>  procedure SubA(Node);
>  begin
>     asm
>       {put the node parameter on the stack}
>        LES     DI,Node
>        PUSH    ES
>        PUSH    DI

>        PUSH StackFrame                     {IDE reports stackframecontents OK}

This is root of your problem.  StackFrame is known to SubA somewhat
like a var parameter.  The IDE knows how to access and display it,
but the once inside an ASM block we are like abandoned children and
have to fend for ourselves.  Without our help, the compiler faithfully
translated "StackFrame" as a BP reference w/ offset -2, which caused
it to emit the code for "push [BP-2]"  -- Mmmmm, isn't this the
the value of ES we just pushed?  Ouch is right!

To access StackFrame, SubA must use Procedure A's stack frame!

   mov  BX,[BP+4]     { If SubA is NEAR, and mov BX,[BP+6] if FAR }
   push [SS:BX+OFFSET StackFrame]

This should work, but since we had to access the parent's stack frame
in order to access the StackFrame variable, why not just

   mov  BX,[BP+4]
   push [SS:BX]

Then you eliminate the code in Procedure A that you thought was going
to simplify matters.  Remember, each time SubA calls itself the parent's
stackframe gets further and further away.  The ability to access
common parameters and variables from within a subprocedure requires
some very late runtime binding.  

- Show quoted text -

Quote:

>        CALL  TestFunc                      {crash nastily}
>     end
>     if Node^.Child<>nil then SubA(Node);
>  end;

>begin
>  asm
>    PUSH WORD PTR [BP]
>    POP StackFrame
>  end

>  SubA(RootNode);
>end;

>procedure SubB;

>  function TestFunc(Node : PNode);
>  begin
>    <do something>
>  end;

>begin
>  TestFunc(RootNode);


Quote:
>end;

>Frank H says I ought to move SubA code into A, but there is avariation of
>this routine where I pass the disk pointer (a longint) and it operateson
>the memory linked list.  And anyway, as soon as it recurses we're inthe
>same problem because the stack frames are all wrong.  The above seems
>dead right, the IDE debug watch window reports StackFrame right, but Iseem
>to be losing SubA's stackframe... ouch... wot do I do now?

>Thanks a lot for your help.

>Martin

You could make the incoming stack frame a parameter that would
be passed to SubA, but it simply required more code and adds that
much more to the stack for each recursive call.  I guess it comes
down to preference, but considering that the parent's frame is
already furnished as a hidden parameter, why not use it?

One last question while I still have you ear (eye).  The statement
  "If Node^.Child <> NIL Then SubA(Node)" looks questionable.

Will TestFunc eventually make it NIL?  If this is supposed to be
  "If Node^.Child <> NIL Then SubA(Node^.Child)"
then I agree 100% with Frank H.  It's a little fool hardy to use
a recursive algorithm where an iterative loop would work.  I'm
not sure the maximum number of links you can expect in this list,
but it probably wouldn't take too many to consume the entire stack
and give your program another reason to go out-to-lunch!  :(

   ...red



Wed, 18 Jun 1902 08:00:00 GMT  
 HELP! ForEach/FirstThat methods

This is odd - I'm replying to my reply to my posting....

OK, for whoever is interested I've realised the problem.  I had the right
idea, but forgot (despite all that you'd told me) that StackFrame below
wouldn't work:

Quote:

>procedure A(TestFunc : pointer; RootNode : pointer);
>var StackFrame : word;

>  procedure SubA(Node);
>  begin
>     asm
>       {put the node parameter on the stack}
>        LES     DI,Node
>        PUSH    ES
>        PUSH    DI

>        PUSH StackFrame                     {IDE reports stackframe contents OK}

>        CALL  TestFunc                      {crash nastily}
>     end
>     if Node^.Child<>nil then SubA(Node);
>  end;

>begin
>  asm
>    PUSH WORD PTR [BP]
>    POP StackFrame
>  end

>  SubA(RootNode);
>end;

>procedure SubB;

>  function TestFunc(Node : PNode);
>  begin
>    <do something>
>  end;

>begin
>  TestFunc(RootNode);  <--- oops, should have been A(TestFunc, RootNode)
>end;

(Also, the testfunc was suddenly not working in the above example - I'd
previously passed it as a parameter to the subB)

What was getting me really confused was that, as far as the IDE was
concerned, StackFrame was fine.  And if I printed it (using Pascal rather
than assembler) it was fine, because (of course) the compiler was doing all
the stack frame messing about required.  So I've botched it a bit, in that
it certainly isn't very "elegant", but it works.  The modification
is just to SubA:

  procedure SubA(Node);
  var W : word; P : pointer;
  begin
     W := StackFrame;
     P := TestFunc;
     asm
       {put the node parameter on the stack}
       LES     DI,Node
       PUSH    ES
       PUSH    DI

       PUSH W

       CALL  P
     end
     if Node^.Child<>nil then SubA(Node);
  end;

The W and P are accessible by the assembly bit in a straight forward
manner, and all the complicated bit to do with the StackFrame and TestFunc
references are dealt with by the compiler.

Thanks all for your help, I wouldn't have got there without you.  Particular
apologies to Hivok who posted me an answer in comp.pascal.misc, and who for
some reason I remembered as Frank (no idea why).  Apologies to all Franks and
Hivoks.



Wed, 18 Jun 1902 08:00:00 GMT  
 
 [ 5 post ] 

 Relevant Pages 

1. HELP! ForEach/FirstThat methods

2. ForEach and traversal methods in OOP data structures

3. ForEach and traversal methods in OOP data structures

4. Transaction Methods in Interbase using Delphi 3.0 -Problems in commit-rollback methods

5. Temporary Tables / FOREACH

6. FPC: Wrong behavior in nested use of TCollection.ForEach

7. Bug in tCollection.ForEach?

8. Need help: got error trying to override a virtual method

9. Problem with AfterInsert Method, help needed...

10. Problem with Insert Method, Please Help...

11. HELP: CONNECT METHOD!

12. Search method in Delphi - Help!

 

 
Powered by phpBB® Forum Software