Jump to content
felixeckert

Global Variable value "resets"

Recommended Posts

Hey,

Im currently working on a Program in Delphi and have a "uLogAbstractor" unit tot handle logging across threads, this unit essentially just writes the log message and level to a global variable

and the sends a Windows Message using the Winapi to the Main Form which has the logging components. Now I have noticed that regardless of the thread, the global variable containing the

message seems to reset to its previous value after ive called PostMessage. Weirdly this doesn't occur if i output it using ShowMessage before calling PostMessage. Here is the log abstractor

unit uLogAbstractor;

interface
  uses SysUtils, Winapi.Windows, uConsts, Vcl.Dialogs;

  procedure Log(const lvl: Integer; const msg: String);
  procedure LogF(const lvl: Integer; const msg: String; const _format: array of const);

  var
    msghandle: Cardinal = 0;
    logmsg: String;
    loglvl: Integer;

implementation

  procedure Log(const lvl: Integer; const msg: String);
  begin
    if msghandle = 0 then exit;

    loglvl := lvl;
    logmsg := msg;
//    ShowMessage(logmsg);
    PostMessage(msghandle, WM_LOG_MESSAGE, 0, 0);
  end;

  procedure LogF(const lvl: Integer; const msg: String; const _format: array of const);
  begin
    if msghandle = 0 then exit;

    loglvl := lvl;
    logmsg := Format(msg, _format);
    PostMessage(msghandle, WM_LOG_MESSAGE, 0, 0);
  end;
end.

And here is the procedure handling the message in the main form

procedure TfrmMain.WMLogMessage(var msg: string);
begin
  FLogger.Log(uLogAbstractor.loglvl, uLogAbstractor.logmsg);
  if uLogAbstractor.loglvl = LOG_LVL_DBG then
  begin
    if FLogger.FLogDebug then
      memoLog.Lines.Add(FLogger.FLastMsg);
  end else
    memoLog.Lines.Add(FLogger.FLastMsg);
end;

Does anyone know the reason for this behaviour? I'd be grateful for any help!

Share this post


Link to post

First, the use of global variables/objects does not guarantee that their value is the same as the one sent by the sender, as many senders may change the expected value, especially in an asynchronous (or not) sending/receiving. Here, you will certainly have a conflict of interest between the various calls involved.

 

To try to guarantee receipt (expected), you would have to use some kind of lock to the variable/object with the expected value.
Creating a kind of "access queue" to it! However, this still does not guarantee receipt in the order sent, especially if the sending is asynchronous.

 

Perhaps, if you use a way to "enumerate" the sent messages, for example, in a pair: ("index", "message"). Then, on the other side, you could sort in order of arrival, and thus have your queue sorted! But if order of arrival doesn't matter, forget about this part!

 

See about the use of means of blocking simultaneous access to information, such as: TCriticalSection, TMonitor, Semaphores, etc...

 

When using "ShowMessage()" everything seems to work, precisely because it "stops" the process until the user clicks "OK" to continue! It's just a false positive! or a positive-false!

Edited by programmerdelphi2k

Share this post


Link to post

maybe, "Observer Pattern" (or similar) can replace your "PostMessage", and allow add more params with help you, for example using "pair" values!

Form1 subscribe a list (global but protected on access), and the threads notify this list!

The "list", can be a object TList or similar, to register all "subscribers" (for example, Form1).

Then, the threads look this list and send the notifications for any one interested on the message!

basically, the same behaviour than "PostMessage" do it! not necessary!!!

Share this post


Link to post

Thanks for the reply! An Object Queue where I would enqueue Records holding the message data would work fine? Im not too experienced with threading

Share this post


Link to post

For sure, I dont know! Because each case is a case!

It would be +/- :

 

uses
  System.Generics.Collections;

type
  TMyObjList = TList<string>; // example using "strings", but can be any others types, including other objects
  // TMyObjList = TList<MyType>;

var
  MyObjList: TMyObjList;

...
  // to start jobs, you would create a list
  MyObjList := TMyObjList.Create;
  // ....
  //
  // accessing items you would have protect it before any read/write to avoid conflicts
  // using TMonitor, TCriticalSection, etc...
  //
  xxxx.Lock( MyObjList );
  //
  MyObjList.Add('message...');
  MyObjList.Items[ 0 ];
  MyObjList.Remove('message...');
  MyObjList[ 0 ] := 'message...';
  // after read/write you release the object
  xxxx.UnLock( MyObjList );
  //....
  //
  // at end "all" (for example, end app) you can destroy the list
  MyObjList.Free;

better would be have yourself "class" to easy acess to your list 

Edited by programmerdelphi2k

Share this post


Link to post
1 hour ago, felixeckert said:

An Object Queue where I would enqueue Records holding the message data would work fine?

Only if you use a critical section to protect the queues from multiple access by the main thread and the worker thread. Use a custom message to signal the main thread that a message has been queued.

 

Another approach is to use only Windows messaging. The sender will LParam to store what you name Lvl and use LParam to store a pointer to an memory block that you allocate once per message and where you copy the message, including a nul character to give end of message. The message handler MUST free that memory block after getting the message out of it.

Share this post


Link to post

I'm always using a "subscriber model" when it comes to logging. I have a singleton class, with 2 important methods: Subscribe and Log. You can subscribe with a

TLogProc = Procedure(Const inLineToLog: String) Of Object;

for example, which is only adding the method pointer to a TArray / TList.

 

Logging uses a critical section and loops through all subscribed loggers, like:

Procedure TMyLogger.Log(const inLineToLog: String);
Var
 logrefproc: TLogProc;
 s: String;
Begin
 TMonitor.Enter(Self);
 Try
  s := '[' + DateTimeToStr(Now)+ '] ' + inLineToLog.Trim;

  For logrefproc In _logrefprocs Do
   Try
    logrefproc(s);
   Except
    // Swallow logger exceptions.
   End;
 Finally
  TMonitor.Exit(Self);
 End;
End;

This way you can call MyLogger.Log from any thread and the message will be distributed to all subscribed loggers. This way, printing to the console window, appending to a TMemo, saving it to a local file and pusing it up to the cloud can all happen - if you wish. As you already seem having some kind of a logger object, moving it to a separate unit and using this logic should suit your needs well. Just make sure the logging methods aren't taking long AND are synchronized, as they will be called by the thread's context! When you have lots of entries I found it easier to add them to a TList<String> object and using a TTimer to dump the contents and empty the list every second or so:

Procedure TLogFrame.InternalLog(const inLogWhat: String);
Begin
 System.TMonitor.Enter(_linestolog);
 Try
  _linestolog.Append(inLogWhat + sLineBreak);
 Finally
  System.TMonitor.Exit(_linestolog);
 End;
End;

Procedure TLogFrame.LogTimerTimer(Sender: TObject);
Begin
 LogTimer.Enabled := False;
 Try
  System.TMonitor.Enter(_linestolog);
  Try
   If _linestolog.Length > 0 Then
   Begin
    LogMemo.Lines.BeginUpdate;
    Try
     LogMemo.Text := LogMemo.Text + _linestolog.ToString;

     CursorToEnd;
     _linestolog.Clear;
    Finally
     LogMemo.Lines.EndUpdate;
    End;
   End;
  Finally
   System.TMonitor.Exit(_linestolog);
  End;
 Finally
  LogTimer.Enabled := True;
 End;
End;

 

And my OCD forces me to tell you, that

On 12/6/2022 at 2:28 PM, felixeckert said:

 


  if uLogAbstractor.loglvl = LOG_LVL_DBG then
  begin
    if FLogger.FLogDebug then
      memoLog.Lines.Add(FLogger.FLastMsg);
  end else
    memoLog.Lines.Add(FLogger.FLastMsg);

 

can be simplified to:

  if (uLogAbstractor.loglvl <> LOG_LVL_DBG) Or FLogger.FLogDebug then
      memoLog.Lines.Add(FLogger.FLastMsg);

 

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×