Recent

Author Topic: lost pointer  (Read 630 times)

tòfol

  • Newbie
  • Posts: 3
lost pointer
« on: March 17, 2023, 12:57:38 pm »
Hello,

I'm porting a small test app from Rust to Free Pascal, but I have a problem with the 'loss' of a pointer. I have a unit called mqtt.pas with the following:

Code: Pascal  [Select][+][-]
  1. unit mqtt;
  2.  
  3. interface
  4.  
  5. uses classes;
  6.  
  7. type
  8.    
  9.     TMqttMessage = class
  10.     public
  11.         Payload: string;
  12.         Topic: string;
  13.  
  14.         class function GetMessage(): TMqttMessage;
  15.     end;
  16.  
  17.     TMqttClient = class
  18.     public
  19.         AppMessage: ^TMqttApplicationMessage;
  20.         GetData: TList;
  21.         constructor Create;
  22.         procedure ReceivedMessage(msg: TMqttMessage);  
  23.         procedure OnGetData(c:TMqttClient; e: TMqttMessage);  
  24.     end;
  25.  
  26.     TMqttApplicationMessage = class
  27.     public
  28.         GetData: ^TNamedDelegate;
  29.         procedure ApplicationMessageReceived(client: TMqttClient; msg: TMqttMessage);
  30.     end;
  31.  
  32.     //In case one wishes to assign methods of a class to a variable of procedural type, the procedural type must be _
  33.     //declared with the of object modifier.
  34.     TNamedDelegate = procedure(client: TMqttClient; msg: TMqttMessage) of object;  
  35.  
  36.    
  37. implementation
  38.  
  39. constructor TMqttClient.Create();
  40. var tmp, tmp1: TMqttApplicationMessage;
  41.     del, del1, del2: TNamedDelegate;
  42. begin
  43.    Self.GetData:=TList.Create;
  44.    tmp:=TMqttApplicationMessage.Create();
  45.    del:=TNamedDelegate(@Self.OnGetData);
  46.    tmp.GetData:= @del;
  47.    del1:=tmp.GetData^;
  48.    Self.AppMessage:=@tmp;
  49.    tmp1:=Self.AppMessage^;
  50.    del2:=tmp1.GetData^;
  51. end;
  52.  
  53. procedure TMqttClient.OnGetData(c:TMqttClient; e: TMqttMessage);
  54. var n: ^TNamedDelegate;
  55. begin
  56.     for n in Self.GetData do
  57.       n^(c, e);
  58. end;
  59.  
  60. procedure TMqttClient.ReceivedMessage(msg: TMqttMessage);
  61. var appMsg: TMqttApplicationMessage;
  62.     del: TNamedDelegate;
  63. begin
  64.     appMsg:=Self.AppMessage^;
  65.     del:=appMsg.GetData^;
  66.     appMsg.ApplicationMessageReceived(Self, msg);
  67. end;
  68.  
  69. class function TMqttMessage.GetMessage(): TMqttMessage;
  70. begin
  71.     result:=TMqttMessage.Create;
  72.     result.Payload:='A Payload';
  73.     result.Topic:='A Topic';
  74. end;
  75.  
  76. procedure TMqttApplicationMessage.ApplicationMessageReceived(client: TMqttClient; msg: TMqttMessage);
  77. var del: TNamedDelegate;
  78. begin
  79.     del:=Self.GetData^;
  80.     del(client, msg);
  81. end;
  82.  
  83. end.
  84.  

And I call this code from hello.pas:

Code: Pascal  [Select][+][-]
  1. program Hello;
  2. {$TYPEDADDRESS ON}
  3.  
  4. uses mqtt;
  5.  
  6. var
  7.   MqttClient: TMqttClient;
  8.   Msg: TMqttMessage;
  9.  
  10. procedure GetData(client: TMqttClient; msg: TMqttMessage);
  11. begin
  12.     writeln ('Message Payload on Data: ' + msg.Payload + ' Topic: ' + msg.Topic);
  13. end;
  14.  
  15. procedure GetData1(client: TMqttClient; msg: TMqttMessage);
  16. begin
  17.     writeln ('Message Payload on Data1: ' + msg.Payload + ' Topic: ' + msg.Topic);
  18. end;
  19.  
  20. begin
  21.   MqttClient:=TMqttClient.Create();
  22.   Msg:=TMqttMessage.GetMessage();
  23.  
  24.  
  25.   MqttClient.GetData.Add(@GetData);
  26.   MqttClient.GetData.Add(@GetData1);
  27.  
  28.   MqttClient.ReceivedMessage(Msg);
  29.  
  30.   ReadLn;
  31. end.
  32.  

I'm running
Quote
Free Pascal Compiler version 3.2.2 [2022/07/21] for x86_64

The problem I have is on line 65 of mqtt.pas:
Code: Pascal  [Select][+][-]
  1. del:=appMsg.GetData^;
Where I obtain an access violation. What's strange to me is that this pointer is okay when I test it in the constructor of TMqttClient, but when I attempt to access it from hello.pas here:
Code: Pascal  [Select][+][-]
  1. MqttClient.ReceivedMessage(Msg);

I get the problem. Any pointers to help me understand this issue would be greatly appreciated  :)

PascalDragon

  • Hero Member
  • *****
  • Posts: 5481
  • Compiler Developer
Re: lost pointer
« Reply #1 on: March 17, 2023, 03:18:07 pm »
Any pointers to help me understand this issue would be greatly appreciated  :)

First of get rid of the pointers for TMqttClient.TMqttApplicationMessage and TMqttApplicationMessage.GetData. Object Pascal style classes already are pointers and function/method variables are pointer types as well.

Then best use a generic list for TMqttClient.GetData (and I personally wouldn't name that field GetData, cause with the Get prefix I'd assume it to be a method). For example by using unit Generics.Collections and using specialize TList<TNamedDelegate> instead of TList (you'll also have to move the declaration of TNamedDelegate further up - after the declaration of TMqttMessage - and add a forward declaration for TMqttClient before TNamedDelegate (TMqttClient = class;)).

Then inside your TMqttClient.Create you don't need to use TNamedDelegate(@Self.OnGetData). @Self.OnGetData or even @OnGetData is enough (also I wouldn't name that method OnGetData, cause the On prefix is usually used for event properties (properties of type method pointer). And you don't need to use the dereferentiation operator as well nor the @tmp or @del.

So first clean this up, post the cleaned up code and then we can check what is really going on if that doesn't already fix your issues.

Blaazen

  • Hero Member
  • *****
  • Posts: 3237
  • POKE 54296,15
    • Eye-Candy Controls
Re: lost pointer
« Reply #2 on: March 17, 2023, 03:27:51 pm »
Yes, replace
Code: Pascal  [Select][+][-]
  1. AppMessage: ^TMqttApplicationMessage;
with just
Code: Pascal  [Select][+][-]
  1. AppMessage: TMqttApplicationMessage;
And then you will need forward declalaration.

I noticed another issue: you mix methods and procedures.

Code: Pascal  [Select][+][-]
  1. TNamedDelegate = procedure(client: TMqttClient; msg: TMqttMessage) of object;
is method (size 16 bytes) and you feed it to TList which holds just pointers (8 bytes).

This procedure from hello.lpr:
Code: Pascal  [Select][+][-]
  1. procedure GetData(client: TMqttClient; msg: TMqttMessage);
  2. begin
  3.     writeln ('Message Payload on Data: ' + msg.Payload + ' Topic: ' + msg.Topic);
  4. end;
is not type TNamedDelegate, since it is not a method.
Lazarus 2.3.0 (rev main-2_3-2863...) FPC 3.3.1 x86_64-linux-qt Chakra, Qt 4.8.7/5.13.2, Plasma 5.17.3
Lazarus 1.8.2 r57369 FPC 3.0.4 i386-win32-win32/win64 Wine 3.21

Try Eye-Candy Controls: https://sourceforge.net/projects/eccontrols/files/

tòfol

  • Newbie
  • Posts: 3
Re: lost pointer
« Reply #3 on: March 17, 2023, 05:02:52 pm »
Thank you very much @PascalDragon and @Blaazen for your replies.

The forward declaration of class has been very helpful. However, I still don't fully understand what's happening with the procedural type. My mqtt.pas compiles, and looks like this:

Code: Pascal  [Select][+][-]
  1. unit mqtt;
  2.  
  3. interface
  4.  
  5. uses Classes, Generics.Collections;
  6.  
  7. type
  8.     TMqttClient = class;
  9.     TMqttMessage = class;
  10.     TMqttApplicationMessage = class;
  11.  
  12.     //In case one wishes to assign methods of a class to a variable of procedural type, the procedural type must be _
  13.     //declared with the of object modifier.
  14.     TNamedDelegate = procedure(client: TMqttClient; msg: TMqttMessage) of object;
  15.  
  16.     TMqttMessage = class
  17.     public
  18.         Payload: string;
  19.         Topic: string;
  20.  
  21.         class function GetMessage(): TMqttMessage;
  22.     end;
  23.  
  24.     TMqttClient = class
  25.     public
  26.         AppMessage: TMqttApplicationMessage;
  27.         Data: array of TNamedDelegate;
  28.         //Data: TList<TNamedDelegate>;
  29.         constructor Create;
  30.         procedure ReceivedMessage(msg: TMqttMessage);  
  31.         procedure GetData(c:TMqttClient; e: TMqttMessage);  
  32.     end;
  33.  
  34.     TMqttApplicationMessage = class
  35.     public
  36.         Data: TNamedDelegate;
  37.         constructor Create(func: TNamedDelegate);
  38.         procedure ApplicationMessageReceived(client: TMqttClient; msg: TMqttMessage);
  39.     end;
  40.  
  41.    
  42. implementation
  43.  
  44. constructor TMqttClient.Create();
  45. begin
  46.     SetLength(Data, 2);
  47.     Self.AppMessage:=TMqttApplicationMessage.Create(@GetData);
  48. end;
  49.  
  50. procedure TMqttClient.GetData(c:TMqttClient; e: TMqttMessage);
  51. var n: TNamedDelegate;
  52. begin
  53.     for n in Self.Data do
  54.       n(c, e);
  55. end;
  56.  
  57. procedure TMqttClient.ReceivedMessage(msg: TMqttMessage);
  58. begin
  59.     AppMessage.ApplicationMessageReceived(Self, msg);
  60. end;
  61.  
  62. class function TMqttMessage.GetMessage(): TMqttMessage;
  63. begin
  64.     result:=TMqttMessage.Create;
  65.     result.Payload:='A Payload';
  66.     result.Topic:='A Topic';
  67. end;
  68.  
  69. constructor TMqttApplicationMessage.Create(func: TNamedDelegate);
  70. begin
  71.     Data:=func;
  72. end;
  73.  
  74. procedure TMqttApplicationMessage.ApplicationMessageReceived(client: TMqttClient; msg: TMqttMessage);
  75. var del: TNamedDelegate;
  76. begin
  77.     Data(client, msg);
  78. end;
  79.  
  80. end.
  81.  

For some reason I can't use the TList<T>, I'm told (but this is an incidental issue):
Code: Pascal  [Select][+][-]
  1. Syntax error, ";" expected but "<" found

I note here that if I remove of object from TNamedDelegate I get a compilation error:
Code: Pascal  [Select][+][-]
  1. Incompatible type for arg no. 1: Got "<procedure variable type of procedure(TMqttClient;TMqttMessage) of object;Register>", expected "<procedure variable type of procedure(TMqttClient;TMqttMessage);Register>"
Which points to this line in the TMqttClient constructor:
Code: Pascal  [Select][+][-]
  1. Self.AppMessage:=TMqttApplicationMessage.Create(@GetData);


With mqtt.pas compiling, I now try the following in hello.pas:
Code: Pascal  [Select][+][-]
  1. {$MODE objfpc}{$H+}
  2. {$TYPEDADDRESS ON}
  3.  
  4. program Hello;
  5.  
  6. uses mqtt;
  7.  
  8. var
  9.   MqttClient: TMqttClient;
  10.   Msg: TMqttMessage;
  11.  
  12. procedure GetData(client: TMqttClient; msg: TMqttMessage);
  13. begin
  14.     writeln ('Message Payload on Data: ' + msg.Payload + ' Topic: ' + msg.Topic);
  15. end;
  16.  
  17. procedure GetData1(client: TMqttClient; msg: TMqttMessage);
  18. begin
  19.     writeln ('Message Payload on Data1: ' + msg.Payload + ' Topic: ' + msg.Topic);
  20. end;
  21.  
  22. begin
  23.   MqttClient:=TMqttClient.Create();
  24.   Msg:=TMqttMessage.GetMessage();
  25.  
  26.  
  27.   MqttClient.Data[0]:= @GetData;
  28.   //MqttClient.Data[1]:= TNamedDelegate(@GetData1);
  29.   //MqttClient.ReceivedMessage(Msg);
  30.  
  31.   //ReadLn;
  32. end.
  33.  

But this gives me:
Code: Pascal  [Select][+][-]
  1. Incompatible types: got "<address of procedure(TMqttClient;TMqttMessage);Register>" expected "<procedure variable type of procedure(TMqttClient;TMqttMessage) of object;Register>"

There's something I'm missing here clearly in the semantics of the procedural type. Thanks again.
« Last Edit: March 17, 2023, 05:05:01 pm by tòfol »

Blaazen

  • Hero Member
  • *****
  • Posts: 3237
  • POKE 54296,15
    • Eye-Candy Controls
Re: lost pointer
« Reply #4 on: March 17, 2023, 06:24:25 pm »
unit mqtt
Code: Pascal  [Select][+][-]
  1. unit mqtt;
  2.  
  3. interface
  4.  
  5. uses Classes, FGL;
  6.  
  7. type
  8.     TMqttClient = class;
  9.     TMqttMessage = class;
  10.     TMqttApplicationMessage = class;
  11.  
  12.     //In case one wishes to assign methods of a class to a variable of procedural type, the procedural type must be _
  13.     //declared with the of object modifier.
  14.     TNamedDelegate = procedure(client: TMqttClient; msg: TMqttMessage);
  15.     TNamedDelegateMethod = procedure(client: TMqttClient; msg: TMqttMessage) of object;
  16.  
  17.     TMqttMessage = class
  18.     public
  19.         Payload: string;
  20.         Topic: string;
  21.  
  22.         class function GetMessage(): TMqttMessage;
  23.     end;
  24.  
  25.     TGList = specialize TFPGList<TNamedDelegate>;
  26.  
  27.     { TMqttClient }
  28.  
  29.     TMqttClient = class
  30.     public
  31.         AppMessage: TMqttApplicationMessage;
  32.         //Data: array of TNamedDelegate;
  33.         Data: TGList;
  34.         constructor Create;
  35.         destructor Destroy; override;
  36.         procedure ReceivedMessage(msg: TMqttMessage);
  37.         procedure GetData(c:TMqttClient; e: TMqttMessage);
  38.     end;
  39.  
  40.     TMqttApplicationMessage = class
  41.     public
  42.         Data: TNamedDelegateMethod;
  43.         constructor Create(func: TNamedDelegateMethod);
  44.         procedure ApplicationMessageReceived(client: TMqttClient; msg: TMqttMessage);
  45.     end;
  46.  
  47.  
  48. implementation
  49.  
  50. constructor TMqttClient.Create();
  51. begin
  52.     Data:=TGList.Create;
  53.     //SetLength(Data, 2);
  54.     Self.AppMessage:=TMqttApplicationMessage.Create(@GetData);
  55. end;
  56.  
  57. destructor TMqttClient.Destroy;
  58. begin
  59.   AppMessage.Free;
  60.   Data.Free;
  61.   inherited Destroy;
  62. end;
  63.  
  64. procedure TMqttClient.GetData(c:TMqttClient; e: TMqttMessage);
  65. var n: TNamedDelegate;
  66. begin
  67.     for n in Self.Data do
  68.       n(c, e);
  69. end;
  70.  
  71. procedure TMqttClient.ReceivedMessage(msg: TMqttMessage);
  72. begin
  73.     AppMessage.ApplicationMessageReceived(Self, msg);
  74. end;
  75.  
  76. class function TMqttMessage.GetMessage(): TMqttMessage;
  77. begin
  78.     result:=TMqttMessage.Create;
  79.     result.Payload:='A Payload';
  80.     result.Topic:='A Topic';
  81. end;
  82.  
  83. constructor TMqttApplicationMessage.Create(func: TNamedDelegateMethod);
  84. begin
  85.     Data:=func;
  86. end;
  87.  
  88. procedure TMqttApplicationMessage.ApplicationMessageReceived(client: TMqttClient; msg: TMqttMessage);
  89. var del: TNamedDelegate;
  90. begin
  91.     Data(client, msg);
  92. end;
  93.  
  94. end.

program
Code: Pascal  [Select][+][-]
  1. {$MODE objfpc}{$H+}
  2. {$TYPEDADDRESS ON}
  3.  
  4. program Hello;
  5.  
  6. uses mqtt;
  7.  
  8. var
  9.   MqttClient: TMqttClient;
  10.   Msg: TMqttMessage;
  11.  
  12. procedure GetData(client: TMqttClient; msg: TMqttMessage);
  13. begin
  14.     writeln ('Message Payload on Data: ' + msg.Payload + ' Topic: ' + msg.Topic);
  15. end;
  16.  
  17. procedure GetData1(client: TMqttClient; msg: TMqttMessage);
  18. begin
  19.     writeln ('Message Payload on Data1: ' + msg.Payload + ' Topic: ' + msg.Topic);
  20. end;
  21.  
  22. begin
  23.   MqttClient:=TMqttClient.Create();
  24.   Msg:=TMqttMessage.GetMessage();
  25.  
  26.   MqttClient.Data.Add(@GetData);
  27.   MqttClient.Data.Add(@GetData1);
  28.   MqttClient.ReceivedMessage(Msg);
  29.  
  30.   MqttClient.Free;
  31.   Msg.Free;
  32.   ReadLn;
  33. end.

I used FGL for list and I separated TNamedDelegate and TNamedDelegateMethod.
Lazarus 2.3.0 (rev main-2_3-2863...) FPC 3.3.1 x86_64-linux-qt Chakra, Qt 4.8.7/5.13.2, Plasma 5.17.3
Lazarus 1.8.2 r57369 FPC 3.0.4 i386-win32-win32/win64 Wine 3.21

Try Eye-Candy Controls: https://sourceforge.net/projects/eccontrols/files/

tòfol

  • Newbie
  • Posts: 3
Re: lost pointer
« Reply #5 on: March 17, 2023, 06:41:09 pm »
Great stuff @Blaazen, thank you very much.

So essentially the issue was that I couldn't use the same procedural variable/delegate for class-bound procedures and the 'free' procedures—can I finally ask you if you know of anything I can read to understand this difference? Has it got something to do with the mutability of the 'hidden' Self reference which is passed to class-bound procedures, I wonder?

Anyhow, thanks again.

Blaazen

  • Hero Member
  • *****
  • Posts: 3237
  • POKE 54296,15
    • Eye-Candy Controls
Re: lost pointer
« Reply #6 on: March 17, 2023, 07:17:17 pm »
Basically, procedural variables are just pointers (8B on 64-bit machine). Methods (... of object;) have the hidden parameter, self, therefore they are 16B.

https://www.freepascal.org/docs-html/ref/refsu22.html. Sure, there are others, for FPC or for Delphi.
Lazarus 2.3.0 (rev main-2_3-2863...) FPC 3.3.1 x86_64-linux-qt Chakra, Qt 4.8.7/5.13.2, Plasma 5.17.3
Lazarus 1.8.2 r57369 FPC 3.0.4 i386-win32-win32/win64 Wine 3.21

Try Eye-Candy Controls: https://sourceforge.net/projects/eccontrols/files/

 

TinyPortal © 2005-2018