Jump to content
david_navigator

Help debugging TNotificationCenter

Recommended Posts

I am getting an Access Violation in the VCL TNotificationCenter, however how it occurs is strange and I can't seem to debug far enough in to the delphi source to work out why.

 

I have a delphi form with an editbox.

The edit box has an OnKeyDown event.

Within that OnKeyDown event we call a routine to check if the key was VK_RETURN and if it was, we then process the content of the edit box (Method ProcessBarcodeScan) and in certain circumstances we create a Windows 10 (Toast) notification.

(The notification makes no reference to the edit box or any of it's properties.)

 

Now, the odd bit, if I type in to the edit box, then everything works as it should and there are no errors and the "toast" pops up.

 

If I populate the Edit box via a SendMessage thus 

SendMessage(BarcodeEdit.Handle, WM_CHAR, lKeyCode, 0);

and then call the ProcessBarcodeScan method via a PostMessage, thus 

PostMessage(handle, pm_ProcessBarcodeScan, WPARAM(lKeyCode), LPARAM(Ord(TRUE)))

again all works correctly.

However, if I populate the edit box via the SendMessage as above and then call ProcessBarcodeScan via a SendMessage thus

          SendMessage(handle, pm_ProcessBarcodeScan, WPARAM(lKeyCode), LPARAM(Ord(TRUE)));
          application.ProcessMessages;


I get an Access violation, which stepping through the code gets me to 
 

class function TToastTemplateGenerator.GetXMLDoc(const ANotification: TNotification): Xml_Dom_IXmlDocument;

...

...

  Result := TToastNotificationManager.Statics.GetTemplateContent(LTemplateType);

 

but the only place I can find GetTemplateContent is in Winapi.UI.Notifications class function TToastNotificationManager which won't accept a break point

 

This is my Notification code 

procedure TDMNexusConnect.SendWin10Notification(aTitle, aAlertBody: string);
var
  lNotification: TNotification;
begin

  lNotification := NotificationCenter.CreateNotification;

  try
    lNotification.Name := format('Windows10Notification%d', [GetTickcount]);
    lNotification.Title := aTitle;
    lNotification.AlertBody := aAlertBody;
    NotificationCenter.PresentNotification(lNotification);
  finally
    lNotification.Free;
  end;

end;

and this is the Access Violation.

exception class    : EAccessViolation
exception message  : Access violation at address 03C4EBE9 in module 'HireTrackNX.exe'. Read of address 00000000.

thread $2c24:
03c4ebe9 +015 HireTrackNX.exe System.Win.Notification           TNotificationWinRT.Destroy
77e64ee1 +021 ntdll.dll                                         KiUserExceptionDispatcher
00407984 +004 HireTrackNX.exe System                    34   +0 @FreeMem
0040cc24 +01c HireTrackNX.exe System                    34   +0 @UStrClr
03c4eaa6 +046 HireTrackNX.exe System.Win.Notification           TNotificationWinRT.Create
03c4deb3 +02f HireTrackNX.exe System.Win.Notification           TNotificationCenterWinRT.DoPresentNotification
03c59b40 +018 HireTrackNX.exe System.Notification               TCustomNotificationCenter.DoPresentNotification
03c59c52 +002 HireTrackNX.exe System.Notification               TCustomNotificationCenter.PresentNotification
03de9d23 +0a7 HireTrackNX.exe DMNexus                 2440   +7 TDMNexusConnect.SendWin10Notification

 

Quote

 

Quote

 

Share this post


Link to post
14 hours ago, david_navigator said:

but the only place I can find GetTemplateContent is in Winapi.UI.Notifications class function TToastNotificationManager which won't accept a break point

The "Debug DCU" option is checked in the project options?

Only with this you can debug in VCL and RTL source code (System, Winapi, etc.).

 

Anyway, the use of Application.ProcessMessages looks like a code flaw and is the wrong approach in most cases.

Share this post


Link to post

Yes, Debug DCU's is checked.

I can step through a lot of the source, until I get to 

Result := TToastNotificationManager.Statics.GetTemplateContent(LTemplateType);

where the debugged just goes in to some interface assembler and no further.

 

Quote

Anyway, the use of Application.ProcessMessages looks like a code flaw and is the wrong approach in most cases.

     In this case, it's part of the design so that serial port data gets dealt with in the background whilst a dialog is visible    

        if ScansDismissPromptsCheckbox.Checked then
          PostMessage(handle, pm_ProcessBarcodeScan, WPARAM(lKeyCode), LPARAM(Ord(TRUE))) // see bug tracker 3503 , need to be a PostMessage
        else
        begin
          SendMessage(handle, pm_ProcessBarcodeScan, WPARAM(lKeyCode), LPARAM(Ord(TRUE))); // see bug tracker 3009 , need to be a SendMessage
          application.ProcessMessages;

        end;

 

Share this post


Link to post

Check TNotificationWinRT.Create.

I assume your lNotification is freed before it used here.

 

constructor TNotificationWinRT.Create(const ANotificationCenter: TNotificationCenterWinRT;
  const ANotification: TNotification);
var
  DeleateActivate: TNotificationCenterDelegateActivated;
  DelegateDismiss: TNotificationCenterDelegateDismiss;
  DelegateFailed: TNotificationCenterDelegateFailed;
begin
  FToast := TToastNotification.Factory.CreateToastNotification(TToastTemplateGenerator.GetXMLDoc(ANotification));
  DeleateActivate := TNotificationCenterDelegateActivated.Create(ANotification);
  FDelegateActivatedToken := FToast.add_Activated(DeleateActivate);

  DelegateDismiss := TNotificationCenterDelegateDismiss.Create(ANotificationCenter, ANotification.Name);
  FDelegateDismissToken := FToast.add_Dismissed(DelegateDismiss);

  DelegateFailed := TNotificationCenterDelegateFailed.Create(ANotificationCenter, ANotification.Name);
  FDelegateFailedToken := FToast.add_Failed(DelegateFailed);
end;

 

Share this post


Link to post
Quote

Check TNotificationWinRT.Create.

I assume your lNotification is freed before it used here.

I think it must be something like that.

Interestingly if I change the code so that rather than a local variable (which might go out of scope ?) the Notification is a class field, I get an additional error "An outgoing call cannot be made since the application is dispatching an input-synchronous call", then I get an AV !!

Share this post


Link to post

Problem solved.

If the SendMessage originates from a different thread to that Presenting the Notification, then Windows raises the error "An outgoing call cannot be made since the application is dispatching an input synchronous call."

As this is raised in the constructor of TNotificationWinRT, the destructor is called. The destructure doesn't check if FToast is assigned (which it's not 'cos the constructor fails) and thus raises an Access violation. I'll report this via QC.

 

Meanwhile the fix to the original problem seems to be as simple as introducing a timer (which I'm guessing moves the processing to the main thread - is there a better way to do this ?).

 

So the code becomes something like this (from my test app)

 

procedure TForm62.comportclient1Receive(Sender: TObject; InQue: Integer);
begin
    SendMessage(handle, pm_ProcessBarcodeScan, WPARAM(0), LPARAM(0));
    application.processmessages;
end;

procedure TForm62.ProcessBarcodeScan(var Message: TMessage);
begin
  inherited;
   SendWin10Notification('Hello World', 'Sent by Message');
end;

procedure TForm62.SendWin10Notification(aTitle, aAlertBody: string);
begin
    FNotification.Name := format('Windows10Notification%d', [GetTickcount]);
    FNotification.Title := aTitle;
    FNotification.AlertBody := aAlertBody;
    if not insendmessage then
    NotificationCenter1.PresentNotification(FNotification)
    else
    Timer1.enabled := true;

end;

procedure TForm62.Timer1Timer(Sender: TObject);
begin
    if not insendmessage then
    begin
    NotificationCenter1.PresentNotification(FNotification);
    timer1.enabled := false;
    end;
end;

 

 

 

Share this post


Link to post

First, maybe that helps: https://stackoverflow.com/a/6616121

 

Do you use this?

https://github.com/maerlyn/old-delphi-codes/blob/master/_KOMPONENSEK/Comms.pas 

 

Check for Mainthread with a construct like 

 

procedure TForm62.comportclient1Receive(Sender: TObject; InQue: Integer);
begin
    if TThread.Current.ThreadID = MainThreadID then
    begin
        SendMessage(handle, pm_ProcessBarcodeScan, WPARAM(0), LPARAM(0));
        Application.ProcessMessages;
    end;
end;

 

 

Share this post


Link to post

I'm not sure how Win10 Notification behaves, but what happens if you try to decouple this from the incomin ComPort thread ?

 

procedure TForm62.comportclient1Receive(Sender: TObject; InQue: Integer);
begin
    SendMessage(handle, pm_ProcessBarcodeScan, WPARAM(0), LPARAM(0));
//<= Better avoid any ProcessMessages    application.processmessages;
end;

procedure TForm62.ProcessBarcodeScan(var Message: TMessage);
begin
  inherited;
   SendWin10Notification('Hello World', 'Sent by Message');
end;

procedure TForm62.SendWin10Notification(aTitle, aAlertBody: string);
begin
    TThread.ForceQueue(   //<= This ensure the call inside main thread, and should avoid to use a Timer IMHO
        nil,
        procedure
        var
            lNotification : TNotification;
        begin
            lNotification := NotificationCenter.CreateNotification;

            // Processed in Main UI thread here
            lNotification.Name := format('Windows10Notification%d', [GetTickcount]);
            lNotification.Title := aTitle;
            lNotification.AlertBody := aAlertBody;
            lotificationCenter1.PresentNotification( lNotification )
            
            //insendmessage  <== This doesn't help, if you call this several times.
            // Where is FNotification be created, probably only once at start ?
            // I think you could have to create a local FNotification here, if you don't have the need to touch this variable again.
        end );
end;

//<= This is probably not necessary at all
procedure TForm62.Timer1Timer(Sender: TObject);

 

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

×