Recent

Author Topic: [CLOSED (almost)] Mixed "procedure" and "procedure of object" callback  (Read 14576 times)

lucamar

  • Hero Member
  • *****
  • Posts: 4217
A somewhat baffling problem suggested by this post and answers to it.

Let's say I'm extending/replacing a function like, for example, FindAllFiles() to allow for a procedure/function to be called on each file found, which can easily be done by making use of TFileSearcher's events.

Now, the type of the "callback" parameter could be declared either as a normal function/procedure or as a method (a function/procedure of object), so one would need two overloaded functions, each requiring one type or the other.

The question, now, is how to avoid duplicating code, that is, having two functions with basically the same code except for the callback without complicating matters too much. I came up with the concept of a third overload doing the main thing and to which it is passed a record such as:

Code: Pascal  [Select][+][-]
  1. TCallback = record
  2.   case IsMethod: Boolean of
  3.   True: (MethodCallback: TMethodCalback);
  4.   False: (NormalCallback: TNormalCallback);
  5. end;

This would allow us to make, from the first two overloads, something like:

Code: Pascal  [Select][+][-]
  1. procedure FindMostFiles(..., const OnFileFound: TNormalCalback =nil);
  2. var
  3.   Callback: TCallback;
  4. begin
  5.   { Call the third, full overloaded version}
  6.   Callback.IsMethod := False;
  7.   Callback.NormalCallback := OnFileFound;
  8.   FindMostFiles(...,  Callback);
  9. end;

But I don't really like this kind of solution, so now the question: Can someone suggest a better alternative for calling what is, basically, the same code allowing it to be passed either a normal callback or a method?

BTW, to prune the solution tree: yes, I though of using a TFileSearcher, setting its OnFileFound directly if we are called with a method or using an internal event handler and calling the function/proc indirectly if passed a normal proc/function.

Any more?

Thanks for playing! ;)
« Last Edit: April 22, 2020, 11:41:08 am by lucamar »
Turbo Pascal 3 CP/M - Amstrad PCW 8256 (512 KB !!!) :P
Lazarus/FPC 2.0.8/3.0.4 & 2.0.12/3.2.0 - 32/64 bits on:
(K|L|X)Ubuntu 12..18, Windows XP, 7, 10 and various DOSes.

eljo

  • Sr. Member
  • ****
  • Posts: 468
Re: Mixed "procedure" and "procedure of object" callback
« Reply #1 on: April 19, 2020, 08:10:26 pm »
you pick one and only one and stick to it. VCL and largely lcl use Methods for callbacks unless there is something simple like comparison(-1,0,1) or a yes/no something that might be useful in in multiple situations outside and in an object framework. The case with the filename filtering is one such case only use simple procedure callbacks this way when things get multi threading and at some point they will you do not have to worry about protecting a class and its private fields from accidents each thread will have its own stack of internal variables for the procedure and you are done

jamie

  • Hero Member
  • *****
  • Posts: 7587
Re: Mixed "procedure" and "procedure of object" callback
« Reply #2 on: April 19, 2020, 08:11:50 pm »
The TStringList that is  used in the code has a OnChange event. In this case it should be the one getting added to it..
 
 I think and I could be wrong here, if you use the existing version that accepts a StringList already created you can assign these events in your code and there by you should be able to get the added string notification in your code.

 I don't know how the internal code treats the stringlist you give it, it could be just adding to it or it could be also using it in the logic too..

  I think testing with the existing version that requires you to supply a string list would be interesting.

 
 
The only true wisdom is knowing you know nothing

Bart

  • Hero Member
  • *****
  • Posts: 5702
    • Bart en Mariska's Webstek
Re: Mixed "procedure" and "procedure of object" callback
« Reply #3 on: April 19, 2020, 08:15:02 pm »
Well, have the 2 overloads, one with a procedure of object, one with a normal procedure.
Then in the implementation section have a third one that has both parameters:

Code: Pascal  [Select][+][-]
  1. interface
  2.  
  3. procedure P(ACallback: TProcedure); overload;
  4. procedure P(ACallBack: TNotifyEvent): overload;
  5. ...
  6. implementation
  7.  
  8. procedure P(AProcCallBack: TProcedure; AEventCallBack: TNotifyEvent);
  9. begin
  10.   //do all stuff here
  11.   if Assigned(AProcCallBack) then
  12.     AProcCallBack
  13.   else
  14.     if Assigned(AEventCallBack) then
  15.     AEventCallBack(Self);
  16. end;
  17.  
  18. procedure P(ACallback: TProcedure); overload;
  19. begin
  20.   P(ACallBac, nil);
  21. end;
  22.  
  23. procedure P(ACallBack: TNotifyEvent): overload;
  24. begin
  25.   P(nil, ACallBack);
  26. end;

Bart

lucamar

  • Hero Member
  • *****
  • Posts: 4217
Re: Mixed "procedure" and "procedure of object" callback
« Reply #4 on: April 19, 2020, 08:26:46 pm »
you pick one and only one and stick to it

That's not really a "solution", is it? :D No, assume the problem is as specified: we need both versions. Imagine we want to be able to use it easily to build, say, a TFileListBox (accepting or not each file found) on one hand and also be able to use it in a very simple console program. Just as examples, o.c.; the requirement is to have both versions.

The TStringList that is  used in the code has a OnChange event. In this case it should be the one getting added to it..

[... etc ...]

The question is not specifically about FindAllFiles and its implementation, but a more generic one as in "how to do such a thing?". It just happens that your question got me thinking and it provides a good example of this kind of situations.

Well, have the 2 overloads, one with a procedure of object, one with a normal procedure.
Then in the implementation section have a third one that has both parameters

That's basically what I did, only with the special record to be absolutely certain that it could never be called with both kinds of callback set. Better safe than sorry :)
Turbo Pascal 3 CP/M - Amstrad PCW 8256 (512 KB !!!) :P
Lazarus/FPC 2.0.8/3.0.4 & 2.0.12/3.2.0 - 32/64 bits on:
(K|L|X)Ubuntu 12..18, Windows XP, 7, 10 and various DOSes.

Bart

  • Hero Member
  • *****
  • Posts: 5702
    • Bart en Mariska's Webstek
Re: Mixed "procedure" and "procedure of object" callback
« Reply #5 on: April 19, 2020, 08:30:19 pm »
Well, have the 2 overloads, one with a procedure of object, one with a normal procedure.
Then in the implementation section have a third one that has both parameters

That's basically what I did, only with the special record to be absolutely certain that it could never be called with both kinds of callback set. Better safe than sorry :)

Well, my version (the one in the implementation) will also never be called with both callbacks set.
And it's a bit simpler to read and implement IMO.

Bart

jamie

  • Hero Member
  • *****
  • Posts: 7587
Re: Mixed "procedure" and "procedure of object" callback
« Reply #6 on: April 19, 2020, 08:32:28 pm »
Keep in mind the idea by this is to be able to see what is about to be considered as a addition to the list and make a choice if you want that addition so you can reject it or simply use it as a reference to do some deeper test on the file its looking at and then decide to reject it.

etc..
The only true wisdom is knowing you know nothing

lucamar

  • Hero Member
  • *****
  • Posts: 4217
Re: Mixed "procedure" and "procedure of object" callback
« Reply #7 on: April 19, 2020, 08:46:52 pm »
Keep in mind the idea by this is to be able to see what is about to be considered as a addition to the list and make a choice if you want that addition so you can reject it or simply use it as a reference to do some deeper test on the file its looking at and then decide to reject it.

But that's in the other topic (yours); this topic is about how to implement a "double" callback. I'm using a "remodeled FindAllFiles" here just for reference and example. Hence the actual full implementation doesn't really matter, or only (though little) in that it uses internally a class with events.
Turbo Pascal 3 CP/M - Amstrad PCW 8256 (512 KB !!!) :P
Lazarus/FPC 2.0.8/3.0.4 & 2.0.12/3.2.0 - 32/64 bits on:
(K|L|X)Ubuntu 12..18, Windows XP, 7, 10 and various DOSes.

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 12172
  • Debugger - SynEdit - and more
    • wiki
Re: Mixed "procedure" and "procedure of object" callback
« Reply #8 on: April 19, 2020, 10:21:01 pm »
I can't think of anything radical different.

Instead of your overloads you can have operators
Code: Pascal  [Select][+][-]
  1. operator := (m: TMethodCalback): TCallback;
  2. begin
  3.   Callback.IsMethod := True;
  4.   Callback.MethodlCallback := OnFileFound;
  5.  

Using an advanced record, you can add procedures, to call either the method or plain proc.


Otherwise, no idea.

For info, though not really useful....

The method can be cast into a TMethod. This gives you separate access to the code-pointer, and the data-pointer (i.e. self)

You can store any pointer in the code pointer (and have the data set to nil).
But you can not invoke the call without the correct typecast.

And the plain proc can't be called via the method typed pointer. Because the method has a hidden argument for self.

That is why the method and plain proc are not the same. The parameter count differs (even if you pass nil for self, that still is a parameter)



lucamar

  • Hero Member
  • *****
  • Posts: 4217
Re: Mixed "procedure" and "procedure of object" callback
« Reply #9 on: April 20, 2020, 09:42:24 am »
Instead of your overloads you can have operators

I didn't though of operators, thanks Martin. Just one thing: would that allow me to have just a single function FindAllFiles(..., const Callback: TCallback) and call it passing either a normal proc or a method param? I mean, will the compiler select the correct operator for each to build the record?

I've to confess I'm not that much conversant with operator overloads ... :-[
Turbo Pascal 3 CP/M - Amstrad PCW 8256 (512 KB !!!) :P
Lazarus/FPC 2.0.8/3.0.4 & 2.0.12/3.2.0 - 32/64 bits on:
(K|L|X)Ubuntu 12..18, Windows XP, 7, 10 and various DOSes.

Thaddy

  • Hero Member
  • *****
  • Posts: 18764
  • To Europe: simply sell USA bonds: dollar collapses
Re: Mixed "procedure" and "procedure of object" callback
« Reply #10 on: April 20, 2020, 09:54:58 am »
In addition to Martin's remark:
a static class procedure has no hidden self and is compatible with a normal procedure or function.
If Europe sells their USA bonds the USD will collapse. Europe can affort that given average state debts. The USA can't affort that. Just an advice...

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 12705
  • FPC developer.
Re: Mixed "procedure" and "procedure of object" callback
« Reply #11 on: April 20, 2020, 10:05:30 am »
In addition to Martin's remark:
a static class procedure has no hidden self and is compatible with a normal procedure or function.

But unfortunately pointless for that exact same reason, since the callback wouldn't get any SELF. You might as well just allow a procedure then.

Thaddy

  • Hero Member
  • *****
  • Posts: 18764
  • To Europe: simply sell USA bonds: dollar collapses
Re: Mixed "procedure" and "procedure of object" callback
« Reply #12 on: April 20, 2020, 10:37:22 am »
In addition to Martin's remark:
a static class procedure has no hidden self and is compatible with a normal procedure or function.

But unfortunately pointless for that exact same reason, since the callback wouldn't get any SELF. You might as well just allow a procedure then.
Not really pointless, as you can:
- use a var parameter to simulate self if needed. (similar how advanced record operators work)
- it is still information hiding from and over globals. E.g. callbacks can be better defined in the class where they belong.
E.g.:
Code: Pascal  [Select][+][-]
  1.  Class procedure TFilesearcher.MyCallback(var value:TFileSearcher;const FileToSearch:string);static;
  2.  // basically the same as a global procedure like:
  3.  procedure MyCallback(var value:TFileSearcher;const FileToSearch:string);

Modify to your liking. Add salt and pepper to taste. This has the same signature as a global procedure, but is tied to the class and has no hidden self.
I think this is basically what lucamar wants?
« Last Edit: April 20, 2020, 11:21:17 am by Thaddy »
If Europe sells their USA bonds the USD will collapse. Europe can affort that given average state debts. The USA can't affort that. Just an advice...

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 12705
  • FPC developer.
Re: Mixed "procedure" and "procedure of object" callback
« Reply #13 on: April 20, 2020, 11:23:18 am »
In addition to Martin's remark:
a static class procedure has no hidden self and is compatible with a normal procedure or function.

But unfortunately pointless for that exact same reason, since the callback wouldn't get any SELF. You might as well just allow a procedure then.
Not really pointless, as you can:
- use a var parameter to simulate self if needed. (similar how advanced record operators work)

So you can with a procedure.

Quote
- it is still information hiding from and over globals. E.g. callbacks can be better defined in the class where they belong.

They only belong there as they really do something with it.

Mr.Madguy

  • Hero Member
  • *****
  • Posts: 881
Re: Mixed "procedure" and "procedure of object" callback
« Reply #14 on: April 20, 2020, 11:43:05 am »
Something like this:
Code: Pascal  [Select][+][-]
  1. type
  2.   TCallbackRecord = record
  3.     Callback:Pointer;
  4.     CallbackObject:TObject;
  5.   end;
  6.  
  7. function MakeCallback(MyProc:Pointer):TNotifyEvent;
  8. begin
  9.   TCallbackRecord(Result).Callback := MyProc;
  10.   TCallbackRecord(Result).CallbackObject := nil;
  11. end;
  12.  
  13. procedure MyOnClick(
  14.   {Self:TObject; - not sure if it's needed, depends on calling conversion}
  15.   {Wrapped can be needed}
  16.   Sender:TObject
  17. );
  18. begin
  19.   ShowMessage('It works!');
  20. end;
  21.  
  22. {$R *.dfm}
  23.  
  24. procedure TForm1.FormCreate(Sender: TObject);
  25. begin
  26.   OnClick := MakeCallback(@MyOnClick);
  27. end;
  28.  
Bad thing - parameters may be messed in this case. If I remember it correctly, Delphi creates wrapper, if ordinal procedure is used as closure. FPC can do something similar, if it will be implemented.

Yeah. Closures are actually the best way to do it:
Code: Pascal  [Select][+][-]
  1. function StaticCallback:Boolean;
  2. begin
  3.   ShowMessage('Static callback!');
  4.   Result := True;
  5. end;
  6.  
  7. function TForm1.ObjectCallback:Boolean;
  8. begin
  9.   ShowMessage('Object callback!');
  10.   Result := True;
  11. end;
  12.  
  13. type
  14.   TCallback = reference to function:Boolean;
  15.  
  16. procedure MyProcedure(Callback:TCallback);
  17. begin
  18.   if Callback then ShowMessage('It''s true');
  19. end;
  20.  
  21. procedure TForm1.FormCreate(Sender: TObject);
  22. begin
  23.   MyProcedure(StaticCallback);
  24.   MyProcedure(ObjectCallback);
  25.   MyProcedure(
  26.     function:Boolean
  27.     begin
  28.       ShowMessage('Closure callback!');
  29.       Result := True;
  30.     end
  31.   );
  32. end;
  33.  
« Last Edit: April 20, 2020, 12:01:56 pm by Mr.Madguy »
Is it healthy for project not to have regular stable releases?
Just for fun: Code::Blocks, GCC 13 and DOS - is it possible?

 

TinyPortal © 2005-2018