david_navigator 12 Posted May 30, 2022 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
TiGü 21 Posted May 31, 2022 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
david_navigator 12 Posted May 31, 2022 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
TiGü 21 Posted May 31, 2022 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
david_navigator 12 Posted May 31, 2022 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
david_navigator 12 Posted June 1, 2022 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
TiGü 21 Posted June 1, 2022 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
Rollo62 536 Posted June 1, 2022 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