Recent

Author Topic: CALLBACK Syntax - I'm missing something somewhere...  (Read 646 times)

keith

  • New Member
  • *
  • Posts: 13
CALLBACK Syntax - I'm missing something somewhere...
« on: March 19, 2026, 09:10:58 am »
Greetings all coders. I'm having a bit of a head scratcher, and I can't afford to lose any more hair.

I've got a Callback sequence I thought was set up correctly but obviously not. I'm adding a progress signal to the COPYDIR unit (from Bastla).

I define the callback signature and a variable to hold it in COPYDIR. I Added a parameter to COPYDIR.CREATE to pass the COPYDIR routine. Following is generic illustration code, not the actual code.

Lazarus 4.2/FPC 3.2.2 on Windows 11/64bit

Code: Pascal  [Select][+][-]
  1. unit longprocess;   {in actual code this is the COPYDIR unit}
  2. {$MODE DELPHI}
  3. type
  4.   tProgressCallback = function(const n,m:integer; const text:string):boolean; // NOT of object;
  5.  
  6.   tlongrunner = class
  7.      private
  8.         _ProgressCallback : tProgressCallback; //assigned at create
  9.      public
  10.        constructor create(myCallBack : tProgressCallback);
  11.        function ProgressStep(n, m: integer; aText: string): boolean;
  12.        procedure GoLong;
  13. end;  
  14.  

The "catcher" is in a utility unit. It is NOT an object member.

Code: Pascal  [Select][+][-]
  1. unit Utils;
  2. {
  3.   This is a general purpose utility unit
  4.   Among other things it contains the actual callback routine which is
  5.   invoked by the longrunner program.
  6. }
  7. {$MODE DELPHI}
  8.  
  9. interface
  10. uses
  11.   LongProcess;  // get access to the call back routine signature
  12. ...
  13. Function HandleCallback : tProgressCallback; //(const n,m:integer; text:string):boolean;
  14.  

The main unit has the code to invoke the long running process and to handle the periodic progress messages.

Code: Pascal  [Select][+][-]
  1. unit Main;
  2. {
  3.   This is the main form unit.
  4.   The form has, among other gumpf, a progress bar and a label
  5.  
  6.   When the longrunning operation decides to report its progress,
  7.   the label and the progress bar are to be updated.
  8. }
  9. {$MODE DELPHI}
  10.  
  11. interface
  12.   uses Utils, LongRunning;
  13.  
  14. type
  15.  
  16.   { TForm1 }
  17.  
  18.   TForm1 = class(TForm)
  19.       DetailProgress: TProgressBar;
  20.       CurrentOperation: TLabel;
  21.   public
  22.     procedure StartErUp;    //invokes long running process
  23.     Function HandleProgess(const n,m:integer; const text:string):boolean;
  24. end;
  25.  
  26. implementation
  27.   Function tForm1.StartErUp;
  28. var
  29.    MyLongRunner : tLongRunner;
  30. begin
  31.    // create longrunner and install callback
  32.    myLongRunner := tLongRunner.Create(HandleCallback);
  33.    myLOngRunner.GoLOng;
  34. end;
  35.  
  36. Function tForm1.HandleProgess(const n,m:integer; const text:string):boolean;
  37. // although the signature here is the same as tProgressCallback
  38. // but this call is isolated from the callback mechanism (supposedly)
  39. begin
  40.   Form1.CurrentOperation.Caption := text;
  41.   Form1.DetailProgress.max := m;
  42.   Form1.DetailProgress.Position := n;
  43.   Application.ProcessMessages;
  44.   result := true;
  45. end;
  46.  

So,
1) the Main unit creates the longrunning object routine, specifies the callback routine (that is in the utils unit) and starts the longrunning process.

2) Periodically the longrunning process checks for a defined callback and if found invokes it.

Code: Pascal  [Select][+][-]
  1. {unit longprocess implementation}
  2. tLongrunner.progressstep(n, m: integer; aText: string): boolean;
  3. begin
  4.   if assigned(_progresscallback)
  5.   then result := progressCallback(n, m, aText);
  6. end;
  7.  
  8. tLongrunner.GoLong;
  9. BEGIN
  10.   while moretodo do begin
  11.      ProgressStep(currentstep, maxsteps, '...working...');
  12.      DoSomeProcessStep;
  13.   end;
  14.   ProgressStep(maxSteps, maxSteps, '...All Done...');
  15. END;
  16.  

The callback is in the Utils unit. It catches the callback and forwards it to the invoking form's routine

Code: Pascal  [Select][+][-]
  1. {unit utils implementation}
  2. FUNCTION HandleCallback;                    //(const n, m: integer; const text: string): boolean;
  3. // notice no signature specified... therefore use signature from interface definition
  4. BEGIN
  5.  // call the progress bar updating routine in the mainform
  6.   Form1.HandleProgress(n,m,text);
  7. end;
  8.  

The COMPILER ERROR occurs on the "Form1.HandleProgress(n,m,text);" (line 6 here).

"Error: identifier idents no member "HandleProgress" 

I used to do this kind of thing in Delphi fairly regularly, but I can't figure out what FPC's problem is here.

I see something in the various documentation about using an '@' sign to indicate reference, but not in Delphi mode. I see something about a new syntax using the word 'reference' and/or '&reference' but I don't grok what that is telling me different.

So the fundamental issue is "Why can't the function in the UTILS unit 'see' the Form's method? Any advice accepted.

Thanks in advance.

Keith

Zvoni

  • Hero Member
  • *****
  • Posts: 3376
Re: CALLBACK Syntax - I'm missing something somewhere...
« Reply #1 on: March 19, 2026, 09:27:56 am »
At a guess: Your Unit "utils" doesn't know the Form1-Variable.
in a nutshell: You're running into a circular reference (Main-Unit uses "Utils", but "Utils" needs Main-Unit to access the Form1-Variable).

IIRC, you need a "Uses Main" under implementation-Section
One System to rule them all, One Code to find them,
One IDE to bring them all, and to the Framework bind them,
in the Land of Redmond, where the Windows lie
---------------------------------------------------------------------
Code is like a joke: If you have to explain it, it's bad

Khrys

  • Sr. Member
  • ****
  • Posts: 439
Re: CALLBACK Syntax - I'm missing something somewhere...
« Reply #2 on: March 19, 2026, 11:07:45 am »
Two things:
  • The function signatures differ
  • HandleCallback  should return a function, not call it


Regarding #1, there's already a hint in  longprocess:

Code: Pascal  [Select][+][-]
  1. tProgressCallback = function(const n,m:integer; const text:string):boolean; // NOT of object;     <------ See this comment?

TProgressCallback  is a plain function, not a method (i.e.  of object) - but your function in  TForm1  is a method.
To convert it into a plain function, make the following change:

Code: Pascal  [Select][+][-]
  1. function HandleProgess(const N, M: Integer; const Text: String): Boolean;

Code: Pascal  [Select][+][-]
  1. class function HandleProgess(const N, M: Integer; const Text: String): Boolean; static;

(Side note:  const  has no effect on integer parameters - on desktop platforms at least)



Regarding #2, do it like this:

Code: Pascal  [Select][+][-]
  1. function HandleCallback(): TProgressCallback;
  2. begin
  3.   Result := @TForm1.HandleProgress;
  4. end;

Thaddy

  • Hero Member
  • *****
  • Posts: 19165
  • Glad to be alive.
Re: CALLBACK Syntax - I'm missing something somewhere...
« Reply #3 on: March 19, 2026, 12:00:03 pm »
That would be the same as in Delphi, btw: your original assumption about it working in Delphi was wrong.
In trunk you have more options, btw: anonymous and function references.
(Which is modern Delphi compatible)
« Last Edit: March 19, 2026, 12:04:55 pm by Thaddy »
objects are fine constructs. You can even initialize them with constructors.

Remy Lebeau

  • Hero Member
  • *****
  • Posts: 1590
    • Lebeau Software
Re: CALLBACK Syntax - I'm missing something somewhere...
« Reply #4 on: March 22, 2026, 12:54:26 am »
I used to do this kind of thing in Delphi fairly regularly, but I can't figure out what FPC's problem is here.

Your code is broken, even for Delphi. Your concept is fine, but your implementation is flawed.

Namely, your Utils unit defines a function that returns a (pointer to) a callback handler. It is not the actual handler itself, despite what the comment at the top of the unit says. The actual handler is in TForm1, but it is not compatible with your callback implementation, and Utils is trying to delegate to TForm1 in the wrong way.

The simplest solution is to fix Utils.HandleCallback() itself (and fix TForm1.HandleProgress() to use its Self pointer instead of the global Form1 pointer). It should look like this instead:

Code: Pascal  [Select][+][-]
  1. unit Utils;
  2. {
  3.   This is a general purpose utility unit
  4.   Among other things it contains the actual callback routine which is
  5.   invoked by the longrunner program.
  6. }
  7. {$MODE DELPHI}
  8.      
  9. interface
  10.  
  11. // no need to use LongProcess in this unit!
  12.  
  13. function HandleCallback(const n, m: integer; const text: string): boolean;
  14.  
  15. implementation
  16.  
  17. uses
  18.   Main;
  19.  
  20. function HandleCallback(const n, m: integer; const text: string): boolean;
  21. begin
  22.   // call the progress bar updating routine in the mainform
  23.   Form1.HandleProgress(n,m,text);
  24. end;
  25.  
  26. end.

Code: Pascal  [Select][+][-]
  1. unit Main;
  2. {
  3.   This is the main form unit.
  4.   The form has, among other gumpf, a progress bar and a label
  5.      
  6.   When the longrunning operation decides to report its progress,
  7.   the label and the progress bar are to be updated.
  8. }
  9. {$MODE DELPHI}
  10.      
  11. interface
  12.  
  13. uses
  14.   ...;
  15.      
  16. type
  17.   { TForm1 }
  18.      
  19.   TForm1 = class(TForm)
  20.     DetailProgress: TProgressBar;
  21.     CurrentOperation: TLabel;
  22.   public
  23.     procedure StartErUp;    //invokes long running process
  24.     function HandleProgress(const n, m: integer; const text: string): boolean;
  25.   end;
  26.      
  27. implementation
  28.  
  29. uses
  30.   Utils, LongRunning; // <-- move these down here
  31.  
  32. function TForm1.StartErUp;
  33. var
  34.   MyLongRunner : tLongRunner;
  35. begin
  36.   // create longrunner and install callback
  37.   myLongRunner := tLongRunner.Create(@Utils.HandleCallback);
  38.   myLongRunner.GoLong;
  39. end;
  40.      
  41. function TForm1.HandleProgress(const n, m: integer; const text: string): boolean;
  42. begin
  43.   CurrentOperation.Caption := text;
  44.   DetailProgress.max := m;
  45.   DetailProgress.Position := n;
  46.   Application.ProcessMessages;
  47.   Result := true;
  48. end;
  49.  
  50. end.

But, as you can see, this introduces a circular unit dependency (Main uses Utils which uses Main). You could break that dependency like this:

Code: Pascal  [Select][+][-]
  1. unit Utils;
  2. {
  3.   This is a general purpose utility unit
  4.   Among other things it contains the actual callback routine which is
  5.   invoked by the longrunner program.
  6. }
  7. {$MODE DELPHI}
  8.      
  9. interface
  10.  
  11. uses
  12.   LongProcess;
  13.  
  14. var
  15.   HandleCallback: tProgressCallback;
  16.  
  17. implementation
  18.  
  19. end.

Code: Pascal  [Select][+][-]
  1. unit Main;
  2. {
  3.   This is the main form unit.
  4.   The form has, among other gumpf, a progress bar and a label
  5.      
  6.   When the longrunning operation decides to report its progress,
  7.   the label and the progress bar are to be updated.
  8. }
  9. {$MODE DELPHI}
  10.      
  11. interface
  12.  
  13. uses
  14.   ...;
  15.      
  16. type
  17.   { TForm1 }
  18.      
  19.   TForm1 = class(TForm)
  20.     DetailProgress: TProgressBar;
  21.     CurrentOperation: TLabel;
  22.   public
  23.     procedure StartErUp;    //invokes long running process
  24.     class function HandleProgress(const n, m: integer; const text: string): boolean; static;
  25.   end;
  26.      
  27. implementation
  28.  
  29. uses
  30.   Utils, LongRunning; // <-- move these down here
  31.  
  32. function TForm1.StartErUp;
  33. var
  34.   MyLongRunner : tLongRunner;
  35. begin
  36.   // create longrunner and install callback
  37.   Utils.HandleCallback := @TForm1.HandleProgress;
  38.   myLongRunner := tLongRunner.Create(HandleCallback);
  39.   myLongRunner.GoLong;
  40. end;
  41.      
  42. class function TForm1.HandleProgress(const n, m: integer; const text: string): boolean; static;
  43. begin
  44.   Form1.CurrentOperation.Caption := text;
  45.   Form1.DetailProgress.max := m;
  46.   Form1.DetailProgress.Position := n;
  47.   Application.ProcessMessages;
  48.   Result := true;
  49. end;
  50.  
  51. end.


Which, as you can see, now makes the Utils unit redundant and you can just get rid of it completely. Since TForm1 is creating the TLongRunner object directly, it should just assign its own callback handler directly:

Code: Pascal  [Select][+][-]
  1. unit Main;
  2. {
  3.   This is the main form unit.
  4.   The form has, among other gumpf, a progress bar and a label
  5.      
  6.   When the longrunning operation decides to report its progress,
  7.   the label and the progress bar are to be updated.
  8. }
  9. {$MODE DELPHI}
  10.      
  11. interface
  12.  
  13. type
  14.   { TForm1 }
  15.      
  16.   TForm1 = class(TForm)
  17.     DetailProgress: TProgressBar;
  18.     CurrentOperation: TLabel;
  19.   public
  20.     procedure StartErUp;    //invokes long running process
  21.     class function HandleProgress(const n, m: integer; const text: string): boolean; static;
  22.   end;
  23.      
  24. implementation
  25.  
  26. uses
  27.   LongRunning; // <-- no more Utils!
  28.  
  29. function TForm1.StartErUp;
  30. var
  31.   MyLongRunner : tLongRunner;
  32. begin
  33.   // create longrunner and install callback
  34.   myLongRunner := tLongRunner.Create(@TForm1.HandleProgress);
  35.   myLongRunner.GoLong;
  36. end;
  37.      
  38. class function TForm1.HandleProgress(const n, m:integer; const text: string): boolean; static;
  39. begin
  40.   Form1.CurrentOperation.Caption := text;
  41.   Form1.DetailProgress.max := m;
  42.   Form1.DetailProgress.Position := n;
  43.   Application.ProcessMessages;
  44.   Result := true;
  45. end;
  46.      
  47. end.

But, if you are going to use a class method for the callback handler, then I suggest either:

1. giving TProgressCallback an extra parameter to pass user-defined data to the handler, that way you don't have to rely on any global variables, eg:

Code: Pascal  [Select][+][-]
  1. unit longprocess;
  2.  
  3. {$MODE DELPHI}
  4. type
  5.   tProgressCallback = function(const n, m: integer; const text: string; userData: Pointer): boolean;
  6.      
  7.   tLongRunner = class
  8.   private
  9.     _ProgressCallback : tProgressCallback; //assigned at create
  10.     _ProgressCallbackData : Pointer; //assigned at create
  11.   public
  12.     constructor create(myCallBack : tProgressCallback; myData: Pointer);
  13.     function ProgressStep(n, m: integer; aText: string): boolean;
  14.     procedure GoLong;
  15.   end;  
  16.  
  17. implementation
  18.  
  19. constructor tLongrunner.create(myCallBack : tProgressCallback; myData: Pointer);
  20. begin
  21.   _ProgressCallback := myCallBack;
  22.   _ProgressCallbackData := myData;
  23. end;
  24.  
  25. function tLongrunner.progressstep(n, m: integer; aText: string): boolean;
  26. begin
  27.   if assigned(_progresscallback) then
  28.     result := _progresscallback(n, m, aText, _ProgressCallbackData);
  29. end;
  30.  
  31. ...
  32.      
  33. end.

Code: Pascal  [Select][+][-]
  1. unit Main;
  2. {
  3.   This is the main form unit.
  4.   The form has, among other gumpf, a progress bar and a label
  5.      
  6.   When the longrunning operation decides to report its progress,
  7.   the label and the progress bar are to be updated.
  8. }
  9. {$MODE DELPHI}
  10.      
  11. interface
  12.  
  13. uses
  14.   ...;
  15.  
  16. type
  17.   { TForm1 }
  18.      
  19.   TForm1 = class(TForm)
  20.     DetailProgress: TProgressBar;
  21.     CurrentOperation: TLabel;
  22.   public
  23.     procedure StartErUp;    //invokes long running process
  24.     class function HandleProgress(const n, m: integer; const text: string; userData: Pointer): boolean; static;
  25.   end;
  26.      
  27. implementation
  28.  
  29. uses
  30.   LongRunning;
  31.  
  32. function tForm1.StartErUp;
  33. var
  34.   MyLongRunner : tLongRunner;
  35. begin
  36.   // create longrunner and install callback
  37.   myLongRunner := tLongRunner.Create(@TForm1.HandleProgress, Self);
  38.   myLongRunner.GoLong;
  39. end;
  40.      
  41. class function TForm1.HandleProgress(const n, m: integer; const text: string; userData: Pointer): boolean; static;
  42. begin
  43.   with TForm1(userData) do
  44.   begin
  45.     CurrentOperation.Caption := text;
  46.     DetailProgress.max := m;
  47.     DetailProgress.Position := n;
  48.   end;
  49.   Application.ProcessMessages;
  50.   Result := true;
  51. end;
  52.  
  53. end.

2. simply making tProgressCallback be declared as 'of object', and remove 'class ... static' from TForm1.HandleProgress(), eg:

Code: Pascal  [Select][+][-]
  1. unit longprocess;
  2.  
  3. {$MODE DELPHI}
  4. type
  5.   tProgressCallback = function(const n, m: integer; const text: string): boolean of object;
  6.      
  7.   tLongRunner = class
  8.   private
  9.     _ProgressCallback : tProgressCallback; //assigned at create
  10.   public
  11.     constructor create(myCallBack : tProgressCallback);
  12.     function ProgressStep(n, m: integer; aText: string): boolean;
  13.     procedure GoLong;
  14.   end;  
  15.  
  16. implementation
  17.  
  18. constructor tLongrunner.create(myCallBack : tProgressCallback);
  19. begin
  20.   _ProgressCallback := myCallBack;
  21. end;
  22.  
  23. function tLongrunner.progressstep(n, m: integer; aText: string): boolean;
  24. begin
  25.   if assigned(_progresscallback) then
  26.     result := _progresscallback(n, m, aText);
  27. end;
  28.  
  29. ...
  30.      
  31. end.

Code: Pascal  [Select][+][-]
  1. unit Main;
  2. {
  3.   This is the main form unit.
  4.   The form has, among other gumpf, a progress bar and a label
  5.      
  6.   When the longrunning operation decides to report its progress,
  7.   the label and the progress bar are to be updated.
  8. }
  9. {$MODE DELPHI}
  10.      
  11. interface
  12.  
  13. uses
  14.   ...;
  15.  
  16. type
  17.   { TForm1 }
  18.      
  19.   TForm1 = class(TForm)
  20.     DetailProgress: TProgressBar;
  21.     CurrentOperation: TLabel;
  22.   public
  23.     procedure StartErUp;    //invokes long running process
  24.     function HandleProgress(const n, m: integer; const text: string): boolean;
  25.   end;
  26.      
  27. implementation
  28.  
  29. uses
  30.   LongRunning;
  31.  
  32. function tForm1.StartErUp;
  33. var
  34.   MyLongRunner : tLongRunner;
  35. begin
  36.   // create longrunner and install callback
  37.   myLongRunner := tLongRunner.Create(@HandleProgress);
  38.   myLongRunner.GoLong;
  39. end;
  40.      
  41. function TForm1.HandleProgress(const n, m: integer; const text: string): boolean;
  42. begin
  43.   CurrentOperation.Caption := text;
  44.   DetailProgress.max := m;
  45.   DetailProgress.Position := n;
  46.   Application.ProcessMessages;
  47.   Result := true;
  48. end;
  49.  
  50. end.
« Last Edit: March 22, 2026, 01:02:32 am by Remy Lebeau »
Remy Lebeau
Lebeau Software - Owner, Developer
Internet Direct (Indy) - Admin, Developer (Support forum)

 

TinyPortal © 2005-2018