Recent

Author Topic: [SOLVED] Troubles with function declaration.  (Read 1896 times)

CM630

  • Hero Member
  • *****
  • Posts: 1299
  • Не съм сигурен, че те разбирам.
    • http://sourceforge.net/u/cm630/profile/
Re: Troubles with function declaration.
« Reply #15 on: February 17, 2025, 04:08:42 pm »
1. class function VisaEventHandler(vi: ViSession; eventType: ViEventType; event: ViEvent; userHandle: ViAddr): ViStatus; static; stdcall; external;
results in
unit1.pas(24,140) Error: Fields cannot appear after a method or property definition, start a new visibility section first


2. class function VisaEventHandler(vi: ViSession; eventType: ViEventType; event: ViEvent; userHandle: ViAddr): ViStatus; static; stdcall;
compiles.

But the controls of TForm1 are still not visible from inside (without Form1....)
And the app still crashes (still not sure if this is related to the visibility issue).

Code: Pascal  [Select][+][-]
  1. class function TForm1.VisaEventHandler(vi: ViSession; eventType: ViEventType; event: ViEvent; userHandle: ViAddr): ViStatus; static; stdcall; //external;
  2. var
  3.   WaveformData: AnsiString = '';
  4. begin
  5.   Label1.Caption := 'oops';    
  6. end;
  7.  
unit1.pas(92,11) Error: Only class methods, class properties and class variables can be accessed in class methods
« Last Edit: February 17, 2025, 04:14:42 pm by CM630 »
Лазар 4,0RC2 32 bit (sometimes 64 bit); FPC3,2,2

Khrys

  • Full Member
  • ***
  • Posts: 177
Re: Troubles with function declaration.
« Reply #16 on: February 17, 2025, 04:27:34 pm »
But the controls of TForm1 are still not visible from inside (without Form1....)

@Remy Lebeau  already gave you an excellent answer as to why this is the case and how to resolve it:

The API in question expects a standalone function for the event handler. So, you cannot use a non-static class method, as its signature won't match due to the hidden Self parameter that holds the object pointer. You can use a static class method instead, but you will lose access to the Self parameter, so you will have to pass the object pointer into the event handler in another way. Presumably via the event handler's event or userHandle parameter (if the API provides a way to pass user-defined data into the event handler). But if not, then you'll have to use a global/thread-local variable, or a thunk, or other similar approach to pass your object pointer into the event handler externally.

alpine

  • Hero Member
  • *****
  • Posts: 1374
Re: Troubles with function declaration.
« Reply #17 on: February 17, 2025, 04:34:20 pm »
1. class function VisaEventHandler(vi: ViSession; eventType: ViEventType; event: ViEvent; userHandle: ViAddr): ViStatus; static; stdcall; external;
results in
unit1.pas(24,140) Error: Fields cannot appear after a method or property definition, start a new visibility section first
external?! RTFM.

2. class function VisaEventHandler(vi: ViSession; eventType: ViEventType; event: ViEvent; userHandle: ViAddr): ViStatus; static; stdcall;
compiles.

But the controls of TForm1 are still not visible from inside (without Form1....)
And the app still crashes (still not sure if this is related to the visibility issue).

Code: Pascal  [Select][+][-]
  1. class function TForm1.VisaEventHandler(vi: ViSession; eventType: ViEventType; event: ViEvent; userHandle: ViAddr): ViStatus; static; stdcall; //external;
  2. var
  3.   WaveformData: AnsiString = '';
  4. begin
  5.   Label1.Caption := 'oops';    
  6. end;
  7.  
unit1.pas(92,11) Error: Only class methods, class properties and class variables can be accessed in class methods
Yes, what could you expect? It is class+static, doesn't have a Self attached. Which Label1 to address?

 If you have just one auto-created Form1, you can use it:
Code: Pascal  [Select][+][-]
  1. class function TForm1.VisaEventHandler(vi: ViSession; eventType: ViEventType; event: ViEvent; userHandle: ViAddr): ViStatus; static; stdcall; //external;
  2. var
  3.   WaveformData: AnsiString = '';
  4. begin
  5.   Form1.Label1.Caption := 'oops';    
  6. end;

But if you have many dynamically created forms of that type you should provide a way for the VisaEventHandler to find it's corresponding form. Perhaps with the help of userHandle?

Edit: Remy Lebeau already answered that as Khrys mentioned, apologies.
« Last Edit: February 17, 2025, 04:53:59 pm by alpine »
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

PascalDragon

  • Hero Member
  • *****
  • Posts: 5906
  • Compiler Developer
Re: Troubles with function declaration.
« Reply #18 on: February 17, 2025, 08:43:54 pm »
I am not sure if I am clear:

Code: Pascal  [Select][+][-]
  1. procedure procA;
  2. begin
  3.   procB;  //this will not compile, unless ProcB is declared;
  4. end;
  5.  
  6. procedure procB;
  7. begin
  8.   procA;  //this will compile, no matter if ProcA is declared
  9. end;

Just to explain how this specific situation would be solved: Add the following above the definition of procA:

Code: Pascal  [Select][+][-]
  1. procedure procB; forward;

Or if the functions should be accessible from outside the unit you simply declare them in the interface-section which implicitly act like forward declarations for the implementation-section.

CM630

  • Hero Member
  • *****
  • Posts: 1299
  • Не съм сигурен, че те разбирам.
    • http://sourceforge.net/u/cm630/profile/
Re: Troubles with function declaration.
« Reply #19 on: February 17, 2025, 09:41:08 pm »
Proc A and Proc B are just examples to make sure that I have understood reply #6, which unfortunately does not solve my problem.
But indeed I was not aware of the forward; statement, it seems comfortable.

Thanks to everyone for the assistance, reply #4 exceeds my capacity, the only thing I understood of it that something named userHandle might solve the issue, I will check if I can make some progress in that direction. Giving me more answers sounds like wasting time, today's moto is „сигурен съм, че не ви разбирам“.
Лазар 4,0RC2 32 bit (sometimes 64 bit); FPC3,2,2

alpine

  • Hero Member
  • *****
  • Posts: 1374
Re: Troubles with function declaration.
« Reply #20 on: February 18, 2025, 09:39:07 am »
Proc A and Proc B are just examples to make sure that I have understood reply #6, which unfortunately does not solve my problem.
But indeed I was not aware of the forward; statement, it seems comfortable.
Putting a procedure/function header into the unit interface section is the way to make them visible. The forward declaration is a remnant from TP3.0 days where no such thing as a unit was available.

Thanks to everyone for the assistance, reply #4 exceeds my capacity, the only thing I understood of it that something named userHandle might solve the issue, I will check if I can make some progress in that direction. Giving me more answers sounds like wasting time, today's moto is „сигурен съм, че не ви разбирам“.
When you give some callback to be attached it is a common practice to supply an additional parameter (handle, token, you name it) to be passed at the time of the callback invocation in order to determine the exact point of attachment. For example in your case if you use VisaEventHandler for multiple forms:
Code: Pascal  [Select][+][-]
  1. var
  2.   Forms: array of TForm1 = (Nil, Nil, Nil, ...);
  3.  
  4. ...
  5.  
  6.     Forms[0] := Form1; // give 0 for the user handle into viInstallHandler
  7.     Forms[1] := Form2; // give 1 for the user handle into viInstallHandler
  8.  
  9.     class function TForm1.VisaEventHandler(vi: ViSession; eventType: ViEventType; event: ViEvent; userHandle: ViAddr): ViStatus; static; stdcall; //external;
  10.     var
  11.       WaveformData: AnsiString = '';
  12.     begin
  13.       Forms[userHandle].Label1.Caption := 'oops';   // use the appropriate form based on the handle
  14.     end;
     

OTOH I can see you have TVisaSession objects and may be the more correct way is to keep track of created sessions and choose from them instead of forms.

Дали разбра?
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

CM630

  • Hero Member
  • *****
  • Posts: 1299
  • Не съм сигурен, че те разбирам.
    • http://sourceforge.net/u/cm630/profile/
Re: Troubles with function declaration.
« Reply #21 on: February 18, 2025, 10:57:15 am »
OTOH I can see you have TVisaSession objects and may be the more correct way is to keep track of created sessions and choose from them instead of forms.
This is my primary idea, still not clear if it is doable.

What I understood so far is:
1. I cannot define the function as a part of a Form.
2. I shall find another way to address the Form and its components.
3. When using Form1.... from inside VisaEventHandler the GUI freaks out. I suppose that it Forms[userHandle].... will be the same.
First I was trying to draw data in a TAChart, it did not work, so I tried to add the data in a TStringGrid, but it works neither.
My data seems fine - if I store the raw data in a file and load it in another app and process it with the same routines, everything is fine.

I do not know if the reason for this misbehaviour is the inability to define VisaEventHandler in the form. I had several other suspects, but I could not find any of them guilty.
« Last Edit: February 18, 2025, 11:00:19 am by CM630 »
Лазар 4,0RC2 32 bit (sometimes 64 bit); FPC3,2,2

alpine

  • Hero Member
  • *****
  • Posts: 1374
Re: Troubles with function declaration.
« Reply #22 on: February 18, 2025, 11:30:15 am »
OTOH I can see you have TVisaSession objects and may be the more correct way is to keep track of created sessions and choose from them instead of forms.
This is my primary idea, still not clear if it is doable.

What I understood so far is:
1. I cannot define the function as a part of a Form.
Because if it is in the form it will have a different signature than the one required from the DLL.
2. I shall find another way to address the Form and its components.
That's not so hard to implement.
3. When using Form1.... from inside VisaEventHandler the GUI freaks out. I suppose that it Forms[userHandle].... will be the same.
First I was trying to draw data in a TAChart, it did not work, so I tried to add the data in a TStringGrid, but it works neither.
My data seems fine - if I store the raw data in a file and load it in another app and process it with the same routines, everything is fine.

I do not know if the reason for this misbehaviour is the inability to define VisaEventHandler in the form. I had several other suspects, but I could not find any of them guilty.
It can be caused by various factors, is it possible that the callback was called from a different thread?
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

CM630

  • Hero Member
  • *****
  • Posts: 1299
  • Не съм сигурен, че те разбирам.
    • http://sourceforge.net/u/cm630/profile/
Re: Troubles with function declaration.
« Reply #23 on: February 18, 2025, 01:30:07 pm »
I am not using multithreading, I found no additional threads in VisaSession.
Лазар 4,0RC2 32 bit (sometimes 64 bit); FPC3,2,2

alpine

  • Hero Member
  • *****
  • Posts: 1374
Re: Troubles with function declaration.
« Reply #24 on: February 18, 2025, 02:16:24 pm »
I am not using multithreading, I found no additional threads in VisaSession.
BTW, looking at code in https://forum.lazarus.freepascal.org/index.php/topic,33447.msg547519.html#msg547519 probably you should.
But using Application.ProcessMessages can have weird side effects too.
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

CM630

  • Hero Member
  • *****
  • Posts: 1299
  • Не съм сигурен, че те разбирам.
    • http://sourceforge.net/u/cm630/profile/
Re: Troubles with function declaration.
« Reply #25 on: February 18, 2025, 03:42:00 pm »
This is the new code, I replaced TAChart with TStringGrid.
And I replaced the previous wait (using Application.ProcessMessage) with sleep.
Still the same.
There is a sleep(1); in the loop. The app works with it (extremely slowly), without it the StringGrid is ... odd.

Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Forms, Controls, Dialogs, StdCtrls, Grids,
  9.   StrUtils, visa_dyn, session, visatype;
  10.  
  11. type
  12.  
  13.   { TForm1 }
  14.  
  15.   TForm1 = class(TForm)
  16.     Button1: TButton;
  17.     Label1: TLabel;
  18.     StringGrid1: TStringGrid;
  19.     VisaSession: TVisaSession;
  20.     procedure Button1Click(Sender: TObject);
  21.     procedure FormCreate(Sender: TObject);
  22.   private
  23.  
  24.   public
  25.  
  26.   end;
  27.  
  28. var
  29.   Form1: TForm1;
  30.   WaitWaveform: boolean;
  31.   function VisaEventHandler(vi: ViSession; eventType: ViEventType; event: ViEvent; userHandle: ViAddr): ViStatus; stdcall;
  32.  
  33. implementation
  34.  
  35. {$R *.lfm}
  36.  
  37. { TForm1 }
  38.  
  39. procedure PlotWaveForm(WaveformData: string);
  40. var
  41.   i: integer;
  42.   plotdata: array of double;
  43.   dt : double = 10/1000;
  44.   plotindex: integer = -1;
  45.   numstring: string ='';
  46.   DataOffset: integer = 0;
  47. begin
  48.   with Form1 do
  49.   begin
  50.     Label1.Caption := format('Plotting the waveform...%d samples received',[Length(WaveformData)]);
  51.     if LeftStr(WaveformData,1) <> '#' then
  52.     begin
  53.       form1.Label1.Caption := 'Received data is invalid';
  54.       exit;
  55.     end;
  56.     DataOffset := StrToInt(midstr(WaveformData,2,1)) + 3;
  57.     SetLength(plotdata,Length(WaveformData)-DataOffset);
  58.     plotindex := -1;
  59. //    StringGrid1.BeginUpdate; //This causes no improvement
  60.     StringGrid1.RowCount := high(WaveformData);
  61.     for i:=DataOffset to high(WaveformData)-1 do //Last value is always #10 and it is not a part of the data.
  62.         begin
  63.          inc(plotindex);
  64.           plotdata[plotindex] := Int8(WaveformData[i]);
  65.   //        form1.Chart1LineSeries1.AddXY(plotindex,plotdata[plotindex]);
  66.           StringGrid1.Cells[0,plotindex] := inttostr(plotindex);
  67.           StringGrid1.Cells[1,plotindex] := floatToStr(plotdata[plotindex]);
  68.           form1.Label1.Caption := IntToStr(i);
  69.           sleep(1);
  70.           numstring := numstring + FloatToStr(plotdata[plotindex]) +',0,'+ #13#10;
  71.       end; //for
  72.     //StringGrid1.EndUpdate;
  73.     form1.Label1.Caption := 'Ready';
  74.     Button1.Enabled := true;
  75.   end; //with
  76. end;
  77.  
  78. function VisaEventHandler(vi: ViSession; eventType: ViEventType; event: ViEvent; userHandle: ViAddr): ViStatus; stdcall;
  79. var
  80.   WaveformData: AnsiString = '';
  81. begin
  82.   if (WaitWaveform = true) then
  83.   begin
  84.     WaitWaveform := false;
  85.     WaveformData := Form1.VisaSession.query('CURV?');
  86.     PlotWaveForm(WaveformData);
  87.   end;
  88. end;
  89.  
  90. procedure TForm1.FormCreate(Sender: TObject);
  91. begin
  92.   WaitWaveform := false;
  93.   StringGrid1.FixedCols :=0;
  94.   StringGrid1.FixedRows :=0;
  95.   VisaSession.Active := true;
  96.   VisaSession.EnableEventHandler(@VisaEventHandler);
  97. end;
  98.  
  99. procedure TForm1.Button1Click(Sender: TObject);
  100. var
  101.   myfs: TFormatSettings;
  102.   dt : double = 10/1000;
  103. begin
  104.   Label1.Caption := '';
  105.   Button1.Enabled := false;
  106.   WaitWaveform:= false;
  107.   myfs.DecimalSeparator := '.';
  108.   VisaSession.Command('*RST;');
  109.   VisaSession.Command('*CLS;:HEADER OFF;:WFMO:ENC BIN');   //Default setup
  110. //  VisaSession.Command('CH1:POS -2.500000'); //Vertical offset
  111.   VisaSession.Command(':SEL:CH1 ON;:CH1:PRO:GAIN 1.00000;:CH1:COUP DC;SCA 0.500000;OFFS 0.000000;BAN TWE;DESK 0.000000;');
  112. //                                            probe 1X                  v/div
  113.   VisaSession.Command(':ACQ:STOPA SEQ'); //':ACQ:STOPA RUNST'   //Disable/enable continuous acquisition
  114.   VisaSession.Command(':ACQ:MOD SAM;'); //Configures the common properties of the acquisition subsystem.
  115.   //                        //# samples
  116.   VisaSession.Command(':HOR:RECO 20000;:HOR:SEC ' + FormatFloat('0.000000',dt,myfs) + ';:HOR:DEL:MOD OFF;POS 2.000000');  //Configures the record properties of the acquisition subsystem.
  117.  
  118.   VisaSession.Command(':TRIG:A:EDGE:SOU CH1;:TRIG:A:LEV 0.500000;:TRIG:A:EDGE:SLO RISE;:TRIG:A:EDGE:COUP DC;'); //Set the trigger and wait until it is triggerred
  119.   VisaSession.Command(':DATA:SOU CH1;:DATA:START 1.000000;:DATA:STOP 5000000.000000;'); //probably this sets the buffer size
  120.   VisaSession.Command('ACQUIRE:STATE OFF;ACQuire:STOPAfter SEQuence;SELECT:CH1 ON');
  121.   //
  122.   VisaSession.Command('DESE 1'); //Set registers to await an Operation Complete (OPC) event (bit 1) in the event queue. This event is summarized in the Event Status Bit(ESB) of the Status Byte Register.
  123.   VisaSession.Command('*ESE 1'); //Set the Event Status Bit (bit 5) to await a Service Request (SRQ)
  124.   VisaSession.Command('*SRE 0'); //Clear the event registers
  125.   VisaSession.Command('ACQUIRE:STATE ON');
  126.   sleep(50);//Wait(50);
  127.   VisaSession.Command('*SRE 16');
  128.   VisaSession.Command('*STB?'); // Handler should be called now since output buffer gets filled
  129.   sleep(50);//Wait(50);
  130.   Label1.Caption := 'Waiting for a waveform...';
  131.   VisaSession.Command('*OPC');
  132.   sleep(50);//  Wait(50);
  133.   WaitWaveform:= True;
  134.   VisaSession.Command('*OPC?'); //Waits for the trigger
  135. end;
  136.  
  137. end.
Maybe some race conditions occur, maybe, as you say some additional thread might help. I have some minimal experience with multiple threads from +10 years ago, debugging was really hard.

« Last Edit: February 18, 2025, 03:44:53 pm by CM630 »
Лазар 4,0RC2 32 bit (sometimes 64 bit); FPC3,2,2

alpine

  • Hero Member
  • *****
  • Posts: 1374
Re: Troubles with function declaration.
« Reply #26 on: February 18, 2025, 04:46:48 pm »
Quote
The data seems valid - if I write the raw string received by the oscilloscope to a file and then load it and process it, there is no issue with TAChart, everything is fine.
Could you move the:
Code: Pascal  [Select][+][-]
  1.     WaveformData := Form1.VisaSession.query('CURV?');
  2.     PlotWaveForm(WaveformData);

from the VisaEventHandler into a separate method:

Code: Pascal  [Select][+][-]
  1. function VisaEventHandler(vi: ViSession; eventType: ViEventType; event: ViEvent; userHandle: ViAddr): ViStatus; stdcall;
  2. begin
  3.   if (WaitWaveform = true) then
  4.   begin
  5.     WaitWaveform := false;
  6.     Application.QueueAsyncCall(@Form1.PlotMethod);
  7.   end;
  8. end;
  9.  
  10. procedure TForm1.PlotMethod(Data: PtrInt); // <-- Must declare it into the TForm1 also
  11. var
  12.   WaveformData: AnsiString = '';
  13. begin
  14.     WaveformData := VisaSession.query('CURV?');
  15.     PlotWaveForm(WaveformData);
  16. end;
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

Remy Lebeau

  • Hero Member
  • *****
  • Posts: 1485
    • Lebeau Software
Re: Troubles with function declaration.
« Reply #27 on: February 18, 2025, 06:01:08 pm »
I was finally able to lookup the documentation for the API in question, and indeed it does allow a user-defined pointer to be passed into the callback via the userHandle parameter.

However, as shown earlier, TVisaSession.EnableEventHandler() does not allow the caller to pass in a userHandle parameter when calling viInstallHandler().  So, that needs to be fixed, eg:

Code: Pascal  [Select][+][-]
  1. {procedure TVisaSession.EnableEventHandler(EventHandler: ViHndlr);
  2.     begin
  3.       viInstallHandler(fHandle, VI_EVENT_SERVICE_REQ, EventHandler, nil);
  4.       viEnableEvent(fHandle, VI_EVENT_SERVICE_REQ, VI_HNDLR, 0);
  5.     end;}
  6. procedure TVisaSession.EnableEventHandler(EventHandler: ViHndlr; userHandle: ViAddr);
  7. begin
  8.   viInstallHandler(fHandle, VI_EVENT_SERVICE_REQ, EventHandler, userHandle);
  9.   viEnableEvent(fHandle, VI_EVENT_SERVICE_REQ, VI_HNDLR, 0);
  10. end;

Now, something like the following should work:

Code: Pascal  [Select][+][-]
  1. type
  2.   TForm1 = class(TForm)  
  3.     procedure FormCreate(Sender: TObject);
  4.     ...
  5.     class function StaticVisaEventHandler(vi: ViSession; eventType: ViEventType; event: ViEvent; userHandle: ViAddr): ViStatus; stdcall; static;
  6.     function VisaEventHandler(vi: ViSession; eventType: ViEventType; event: ViEvent): ViStatus;
  7.     ...
  8.   end;
  9.  
  10. implementation
  11.  
  12. ...
  13.  
  14. procedure TForm1.FormCreate(Sender: TObject);
  15. begin
  16.   VisaSession.EnableEventHandler(@StaticVisaEventHandler, Self);
  17. end;
  18.  
  19. class function TForm1.StaticVisaEventHandler(vi: ViSession; eventType: ViEventType; event: ViEvent; userHandle: ViAddr): ViStatus; stdcall; static;
  20. begin
  21.   Result := TForm1(userHandle).VisaEventHandler(vi, eventType, event);
  22. end;
  23.      
  24. function TForm1.VisaEventHandler(vi: ViSession; eventType: ViEventType; event: ViEvent): ViStatus;
  25. begin
  26.   // now you have full access to the TForm1 object and its members...
  27.   Result := ...;
  28. end;
« Last Edit: February 18, 2025, 06:07:07 pm by Remy Lebeau »
Remy Lebeau
Lebeau Software - Owner, Developer
Internet Direct (Indy) - Admin, Developer (Support forum)

CM630

  • Hero Member
  • *****
  • Posts: 1299
  • Не съм сигурен, че те разбирам.
    • http://sourceforge.net/u/cm630/profile/
Re: Troubles with function declaration.
« Reply #28 on: February 19, 2025, 09:33:22 am »
...
    Application.QueueAsyncCall(@Form1.PlotMethod);
...

That is it! I just added a ,0 here:
Application.QueueAsyncCall(@Form1.PlotMethod,0);

...
Thanks, I will try later and give feedback.
Лазар 4,0RC2 32 bit (sometimes 64 bit); FPC3,2,2

alpine

  • Hero Member
  • *****
  • Posts: 1374
Re: Troubles with function declaration.
« Reply #29 on: February 19, 2025, 10:06:09 am »
...
    Application.QueueAsyncCall(@Form1.PlotMethod);
...

That is it! I just added a ,0 here:
Application.QueueAsyncCall(@Form1.PlotMethod,0);
Sorry, forgot about the Data parameter.

Solving it that way means that most probably VisaEventHandler was called in a different thread context than GUI. Don't forget to call:
Code: Pascal  [Select][+][-]
  1. Application.RemoveAsyncCalls(Self);
on form close to ensure there isn't pending async calls at the time of the form destruction.

...
Thanks, I will try later and give feedback.
Since Remy wrote that he checked the compatibility of the types, I believe he gave the most suitable method of mapping against the object (form) instance.

IMHO the StaticVisaEventHandler and VisaEventHandler are most logically methods for TVisaSession class, not TForm1.
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

 

TinyPortal © 2005-2018