Recent

Author Topic: Event handler set to function in object in constructor? possible?  (Read 13956 times)

mijen67

  • Full Member
  • ***
  • Posts: 130
  • It's hard to beat the bandwidth of a flying DVD
Newbie: Trying to set event handler to a function in an object in the contructor fails.

Code: Pascal  [Select][+][-]
  1. constructor TAppData.Create(aUseLocal, TryCreate: boolean; aVendorName, anAppName: string);
  2. begin
  3.   inherited Create; // Calls TObject.Create
  4.   VendorName:=PrepName(aVendorName);
  5.   AppName:= PrepName(anAppName);
  6.   OnGetApplicationName := @GetAppName;
  7.   OnGetVendorName := @GetVendorName;
  8.   GetDirectories();
  9. end;
  10.  

Fails with the following message:
 
uappdata.pas(49,27) Error: Incompatible types: got "<procedure variable type of function:AnsiString of object;Register>" expected "<procedure variable type of function:AnsiString;Register>"

There is only a small (but important) difference between received and expected type.

What am I doing wrong?

See attached if you need to see project.
(Please note idea for project highly inspired by munair submission in forum "third party" regarding class for handling application information)

Eugene Loza

  • Hero Member
  • *****
  • Posts: 674
    • My games in Pascal
Re: Event handler set to function in object in constructor? possible?
« Reply #1 on: October 15, 2017, 06:16:42 am »
uappdata.pas(49,27) Error: Incompatible types: got "<procedure variable type of function:AnsiString of object;Register>" expected "<procedure variable type of function:AnsiString;Register>"
What am I doing wrong?
The fact is that procedure/function/method pointers are different for "normal procedures" and "class methods" (as in "of object"). The error says that the compiler is expecting a "normal procedure" pointer, and you provide it with a "class method".
i.e. it shouldn't be
Code: Pascal  [Select][+][-]
  1. function TAppData.GetAppName(): string;
  2. begin
  3.   result := AppName;
  4. end;
  5.  
  6. function TAppData.GetVendorName(): string;
  7. begin
  8.   result := VendorName;
  9. end;
but it should be:
Code: Pascal  [Select][+][-]
  1. function GetAppName: String;
  2. begin
  3.   Return := AppData.AppName;
  4. end;
  5.  
  6. function GetVendorName: String;
  7. begin
  8.   Return := AppData.VendorName;
  9. end;
« Last Edit: October 15, 2017, 06:18:52 am by Eugene Loza »
My FOSS games in FreePascal&CastleGameEngine: https://decoherence.itch.io/ (Sources: https://gitlab.com/EugeneLoza)

mijen67

  • Full Member
  • ***
  • Posts: 130
  • It's hard to beat the bandwidth of a flying DVD
Re: Event handler set to function in object in constructor? possible?
« Reply #2 on: October 15, 2017, 01:43:11 pm »
The fact is that procedure/function/method pointers are different for "normal procedures" and "class methods" (as in "of object"). The error says that the compiler is expecting a "normal procedure" pointer, and you provide it with a "class method".
end;

I understand what you are saying, but as a newbie I have trouble changing the code to working code, please see attached. 

Also, as far as I understand it then I'll have to move functionality out of the constructor. Is there a way to change the event handlers inside the constructor?

Eugene Loza

  • Hero Member
  • *****
  • Posts: 674
    • My games in Pascal
Re: Event handler set to function in object in constructor? possible?
« Reply #3 on: October 15, 2017, 02:47:55 pm »
I'll have to move functionality out of the constructor. Is there a way to change the event handlers inside the constructor?
No, you don't have to. It'll work just fine if you move
Code: Pascal  [Select][+][-]
  1. function GetAppName(): string;
  2. function GetVendorName(): string;
outside of TAppData class. I.e. place them not with the class, but just in code. You may even not publish them outside the unit (in interface section), they're solely for internal purposes of the unit.
HOWEVER, as far as I see, you'll face an important consequence:
You will have to create a unique var AppData: TAppData; and the user will have to use it instead of creating his own instances of TAppData, because hardcoded procedures will need to reference it. You may even automatically create it in initialization section and automatically free it in finalization section. However, that means it will be only one instance (which is actually wise).
My FOSS games in FreePascal&CastleGameEngine: https://decoherence.itch.io/ (Sources: https://gitlab.com/EugeneLoza)

molly

  • Hero Member
  • *****
  • Posts: 2330
Re: Event handler set to function in object in constructor? possible?
« Reply #4 on: October 15, 2017, 03:12:30 pm »
Also, as far as I understand it then I'll have to move functionality out of the constructor. Is there a way to change the event handlers inside the constructor?
Unfortunately that does not seem to be the only thing that is a bit awkward in your code.

Please do not take offense but i show some fundamental basics using simple approach. Your head seems a bit clouded by all that you've read  ;D

First, you need to instantiate your class and free it after use:
Code: Pascal  [Select][+][-]
  1. program test1;
  2.  
  3. {$MODE OBJFPC}{$H+}
  4.  
  5. Type
  6.   TAppData = class
  7.   end;
  8.  
  9. var
  10.   AppData: TAppData;
  11.  
  12. begin
  13.   // Create a new instance of your TAppData class and keep it for safe-keeping in variable AppData
  14.   AppData := TAppData.Create;
  15.  
  16.   // do stuff, also with AppData
  17.  
  18.   // Free the instantiated TAppdata class
  19.   AppData.Free;
  20. end.
  21.  

Now, let's add a vendorname variable and show it:
Code: Pascal  [Select][+][-]
  1. program test2;
  2.  
  3. {$MODE OBJFPC}{$H+}
  4.  
  5. Type
  6.   TAppData = class
  7.    public
  8.     VendorName : String;
  9.   end;
  10.  
  11. var
  12.   AppData: TAppData;
  13.  
  14. begin
  15.   // Create a new instance of your TAppData class and keep it for safe-keeping in variable AppData
  16.   AppData := TAppData.Create;
  17.  
  18.   // do stuff, also with AppData
  19.   AppData.VendorName := 'Hello world';
  20.  
  21.   WriteLn('The name of the vendor is ', AppData.VendorName);
  22.  
  23.   // Free the instantiated TAppdata class
  24.   AppData.Free;
  25. end.
  26.  

But, we do not like the above. We want to have the vendorname initialized with construction:
Code: Pascal  [Select][+][-]
  1. program test3;
  2.  
  3. {$MODE OBJFPC}{$H+}
  4.  
  5. Type
  6.   TAppData = class
  7.    public
  8.     VendorName : String;
  9.     Constructor Create(aVendor: String);
  10.   end;
  11.  
  12.  
  13. constructor TAppData.Create(aVendor: String);
  14. begin
  15.   inherited Create;
  16.   VendorName := aVendor;
  17. end;
  18.  
  19.  
  20. var
  21.   AppData: TAppData;
  22.  
  23. begin
  24.   // Create a new instance of your TAppData class and keep it for safe-keeping in variable AppData
  25.   AppData := TAppData.Create('Hello world');
  26.  
  27.   // do stuff, also with AppData
  28.   WriteLn('The name of the vendor is ', AppData.VendorName);
  29.  
  30.   // Free the instantiated TAppdata class
  31.   AppData.Free;
  32. end.
  33.  

The public variable allows for change during runtime by user, and we like to prevent that so we make it a private variable and access it by introducing a property:
Code: Pascal  [Select][+][-]
  1. program test4;
  2.  
  3. {$MODE OBJFPC}{$H+}
  4.  
  5. Type
  6.   TAppData = class
  7.    private
  8.     fVendorName: String;
  9.    public
  10.     Constructor Create(aVendor: String);
  11.     property VendorName : String read fVendorName;
  12.   end;
  13.  
  14.  
  15. constructor TAppData.Create(aVendor: String);
  16. begin
  17.   inherited Create;
  18.   fVendorName := aVendor;
  19. end;
  20.  
  21.  
  22. var
  23.   AppData: TAppData;
  24.  
  25. begin
  26.   // Create a new instance of your TAppData class and keep it for safe-keeping in variable AppData
  27.   AppData := TAppData.Create('Hello world');
  28.  
  29.   // do stuff, also with AppData
  30.   WriteLn('The name of the vendor is ', AppData.VendorName);
  31.  
  32.   // Free the instantiated TAppdata class
  33.   AppData.Free;
  34. end.
  35.  

Now we introduce a property getter for vendorname.
Code: Pascal  [Select][+][-]
  1. program test5;
  2.  
  3. {$MODE OBJFPC}{$H+}
  4.  
  5. Type
  6.   TAppData = class
  7.    private
  8.     fVendorName: String;
  9.    protected
  10.     function GetVendorName: String;
  11.    public
  12.     Constructor Create(aVendor: String);
  13.     property VendorName : String read GetVendorName;
  14.   end;
  15.  
  16.  
  17. constructor TAppData.Create(aVendor: String);
  18. begin
  19.   inherited Create;
  20.   fVendorName := aVendor;
  21. end;
  22.  
  23. function TAppData.GetVendorName: String;
  24. begin
  25.   Result := fVendorName;
  26. end;
  27.  
  28. var
  29.   AppData: TAppData;
  30.  
  31. begin
  32.   // Create a new instance of your TAppData class and keep it for safe-keeping in variable AppData
  33.   AppData := TAppData.Create('Hello world');
  34.  
  35.   // do stuff, also with AppData
  36.   WriteLn('The name of the vendor is ', AppData.VendorName);
  37.  
  38.   // Free the instantiated TAppdata class
  39.   AppData.Free;
  40. end.
  41.  
That allows us to change behaviour for this property in future, if there ever is a need for it, and without user knowing things changed (on the outside things still work the same).

Last step: introduction of event for getting the vendor name. This allows the user of your class to interfere/override return value
Code: Pascal  [Select][+][-]
  1. program test6;
  2.  
  3. {$MODE OBJFPC}{$H+}
  4.  
  5. Type
  6.   TOnGetVendorNameproc = function: String;
  7.    
  8.   TAppData = class
  9.    private
  10.     fVendorName: String;
  11.     fOnGetVendorNameProc : TOnGetVendorNameproc;
  12.    protected
  13.     function GetVendorName: String;
  14.    public
  15.     Constructor Create(aVendor: String);
  16.     property VendorName : String read GetVendorName;
  17.     property OnGetVendorName: TOnGetVendorNameproc read fOnGetVendorNameProc write fOnGetVendorNameProc;
  18.   end;
  19.  
  20.  
  21. constructor TAppData.Create(aVendor: String);
  22. begin
  23.   inherited Create;
  24.   fVendorName := aVendor;
  25. end;
  26.  
  27. function TAppData.GetVendorName: String;
  28. begin
  29.   if Assigned(fOnGetVendorNameProc)
  30.     then Result := fOnGetVendorNameProc()
  31.       else Result := fVendorName;
  32. end;
  33.  
  34.  
  35. function MyGetVendorName: String;
  36. begin
  37.   Result := 'Another world';
  38. end;
  39.  
  40. var
  41.   AppData: TAppData;
  42.  
  43. begin
  44.   // Create a new instance of your TAppData class and keep it for safe-keeping in variable AppData
  45.   AppData := TAppData.Create('Hello world');
  46.  
  47.   // do stuff, also with AppData
  48.   WriteLn('The name of the vendor (1) is ', AppData.VendorName);
  49.  
  50.   AppData.OnGetVendorName := @MyGetVendorName;
  51.   WriteLn('The name of the vendor (2) is ', AppData.VendorName);
  52.  
  53.   // Free the instantiated TAppdata class
  54.   AppData.Free;
  55. end.
  56.  

That still doesn't answer your direct question, but hopefully shows what goes wrong inside your code so that you're able to fix it.

munair

  • Hero Member
  • *****
  • Posts: 798
  • compiler developer @SharpBASIC
    • SharpBASIC
Re: Event handler set to function in object in constructor? possible?
« Reply #5 on: October 15, 2017, 03:26:26 pm »
That is why in my code (that you are modifying) the functions were placed outside the class:

Code: Pascal  [Select][+][-]
  1. function _GetName(): string;
  2. begin
  3.   result := StringReplace(App.Name, chr(32), chr(45), [rfReplaceAll]);
  4. end;
  5.  
  6. function _GetVendor(): string;
  7. begin
  8.   result := StringReplace(App.Vendor, chr(32), chr(45), [rfReplaceAll]);
  9. end;
  10.  
  11. procedure UseApp();
  12. begin
  13.   // instantiate
  14.   App := TApp.Create;
  15.   // setup handlers
  16.   OnGetApplicationName := @_GetName;
  17.   OnGetVendorName := @_GetVendor;
  18. end;
  19.  
You tried to wrap it all up into a single class.
keep it simple

mijen67

  • Full Member
  • ***
  • Posts: 130
  • It's hard to beat the bandwidth of a flying DVD
Re: Event handler set to function in object in constructor? possible?
« Reply #6 on: October 15, 2017, 03:29:35 pm »
I'll have to move functionality out of the constructor. Is there a way to change the event handlers inside the constructor?
No, you don't have to. It'll work just fine if you move

Thanks!

Attached is my attempt, it compiles now, however, I get
An unhandled exception occurred at ...
EAccessViolation: Access violation at ...
line 10 of project1.lpr
line 83 of uappdata.pas
line 57 of uappdata.pas
line 21 of project1.lpr


My head is, like molly would say, clouded on what the issue is  :D. Maybe you guys can shred light on this?

mijen67

  • Full Member
  • ***
  • Posts: 130
  • It's hard to beat the bandwidth of a flying DVD
Re: Event handler set to function in object in constructor? possible?
« Reply #7 on: October 15, 2017, 03:37:05 pm »
That is why in my code (that you are modifying) the functions were placed outside the class:

You tried to wrap it all up into a single class.

Correct, I learned a number of tricks from your example, however I didn't want to put the same amount of code into the .lpr file; hence I tried to put as much code as possible into the unit and/or class. It is a learning experience for me to try to write code with a functionality tailored more in direction of what I want to do - from scratch. Without this process I wouldn't have made the errors I'm now, hopefully, learning from.

mijen67

  • Full Member
  • ***
  • Posts: 130
  • It's hard to beat the bandwidth of a flying DVD
Re: Event handler set to function in object in constructor? possible?
« Reply #8 on: October 15, 2017, 03:44:27 pm »
Also, as far as I understand it then I'll have to move functionality out of the constructor. Is there a way to change the event handlers inside the constructor?
Unfortunately that does not seem to be the only thing that is a bit awkward in your code.

Please do not take offense but i show some fundamental basics using simple approach. Your head seems a bit clouded by all that you've read  ;D


No offense taken.

Great tutorial on class, visibility, properties and more! I like your approach with small steps! I'll definitely consult this post whenever I have to create a new class / object. IMHO this tutorial should be part of a must-read before coding in object pascal, and it should be posted on an lazarus/free pascal tutorial site.

Thanks!

molly

  • Hero Member
  • *****
  • Posts: 2330
Re: Event handler set to function in object in constructor? possible?
« Reply #9 on: October 15, 2017, 03:50:30 pm »
Attached is my attempt, it compiles now, however, I get
An unhandled exception occurred at ...
EAccessViolation: Access violation at ...
line 10 of project1.lpr
line 83 of uappdata.pas
line 57 of uappdata.pas
line 21 of project1.lpr

It goes wrong inside getdirectories. fwiw: Much better approach on your second try  8-)

I'm not that familiar with sysutils onxxx routines, but it looks like getdirectories is trying to invoke GetAppName during creation of your class. If you change GetAppName into returning a static string then your code runs without issues. Very strange as you've already initialized the routines correctly, but perhaps your class isn't "ready" yet at that point in time ?

mijen67

  • Full Member
  • ***
  • Posts: 130
  • It's hard to beat the bandwidth of a flying DVD
Re: Event handler set to function in object in constructor? possible?
« Reply #10 on: October 15, 2017, 03:52:09 pm »
I'll have to move functionality out of the constructor. Is there a way to change the event handlers inside the constructor?
No, you don't have to. It'll work just fine if you move
Code: Pascal  [Select][+][-]
  1. function GetAppName(): string;
  2. function GetVendorName(): string;
outside of TAppData class. I.e. place them not with the class, but just in code. You may even not publish them outside the unit (in interface section), they're solely for internal purposes of the unit.

That would be great! However, I'm not sure how to implement this. Would you create the object in the unit then?

HOWEVER, as far as I see, you'll face an important consequence:
You will have to create a unique var AppData: TAppData; and the user will have to use it instead of creating his own instances of TAppData, because hardcoded procedures will need to reference it. You may even automatically create it in initialization section and automatically free it in finalization section. However, that means it will be only one instance (which is actually wise).

Where can I read about initialization and finalization sections? (or feel free to modify my code to do this and thereby illustrate the point  ;D )

munair

  • Hero Member
  • *****
  • Posts: 798
  • compiler developer @SharpBASIC
    • SharpBASIC
Re: Event handler set to function in object in constructor? possible?
« Reply #11 on: October 15, 2017, 03:57:33 pm »
That is why in my code (that you are modifying) the functions were placed outside the class:

You tried to wrap it all up into a single class.

Correct, I learned a number of tricks from your example, however I didn't want to put the same amount of code into the .lpr file; hence I tried to put as much code as possible into the unit and/or class. It is a learning experience for me to try to write code with a functionality tailored more in direction of what I want to do - from scratch. Without this process I wouldn't have made the errors I'm now, hopefully, learning from.
I understand. You simply could have taken the example code in the lpr-file and put it in the Create event of your project (if it's GUI). I asked you about it in the thread concerned. If you continued the discussion there, it would have kept things in perspective. ;)
keep it simple

molly

  • Hero Member
  • *****
  • Posts: 2330
Re: Event handler set to function in object in constructor? possible?
« Reply #12 on: October 15, 2017, 03:59:48 pm »
Where can I read about initialization and finalization sections? (or feel free to modify my code to do this and thereby illustrate the point  ;D )
Quickest link is was able to find here.

molly

  • Hero Member
  • *****
  • Posts: 2330
Re: Event handler set to function in object in constructor? possible?
« Reply #13 on: October 15, 2017, 04:04:32 pm »
Great tutorial on class, visibility, properties and more! I like your approach with small steps!
Thank you. Not much my doing rather the inventors that created the language  :)

Quote
I'll definitely consult this post whenever I have to create a new class / object. IMHO this tutorial should be part of a must-read before coding in object pascal, and it should be posted on an lazarus/free pascal tutorial site.
Isn't it listed somewhere already ? I'll try if i'm able to dig up something (it must be out there somewhere).

Thanks for the feedback.

mijen67

  • Full Member
  • ***
  • Posts: 130
  • It's hard to beat the bandwidth of a flying DVD
Re: Event handler set to function in object in constructor? possible?
« Reply #14 on: October 15, 2017, 04:13:16 pm »
Attached is my attempt, it compiles now, however, I get
An unhandled exception occurred at ...
EAccessViolation: Access violation at ...
line 10 of project1.lpr
line 83 of uappdata.pas
line 57 of uappdata.pas
line 21 of project1.lpr

It goes wrong inside getdirectories. fwiw: Much better approach on your second try  8-)

I'm not that familiar with sysutils onxxx routines, but it looks like getdirectories is trying to invoke GetAppName during creation of your class. If you change GetAppName into returning a static string then your code runs without issues. Very strange as you've already initialized the routines correctly, but perhaps your class isn't "ready" yet at that point in time ?

Another newbie question. How do I change GetAppName to return a static string?

 

TinyPortal © 2005-2018