NamoRamana 3 Posted February 27, 2023 I got an unusual problem about the Windows Notification.. I implemented a basic Windows Notification in Exe2.. Following is the sample code: //ShowMessage(aMessage); vNotifiCenter := TNotificationCenter.Create(nil); try if vNotifiCenter.Supported then begin vNotification := vNotifiCenter.CreateNotification; try vNotification.AlertBody:= aMessage; vNotification.Title:= aTitle; vNotification.FireDate := Now; vNotification.EnableSound:= False; vNotifiCenter.PresentNotification(vNotification); finally vNotification.Free; end; end else ShowMessage('Not Supported'); finally vNotifiCenter.Free; end; When I double-click on Exe2 from File Explorer OR run in debug, it works fine... But in reality, Exe2 is started by Exe1 using CreatePorcess(). This is when I get "Not Supported" msg. Following is the code from Exe1 that starts Exe2.. function CreateProcessAndReturn(const AppName: string; ShowState: Integer): THandle; var ProcessInfo: TProcessInformation; StartupInfo: TStartupInfo; begin FillChar(StartupInfo, SizeOf(TStartupInfo), #0); with StartupInfo do begin cb := SizeOf(TStartupInfo); dwFlags := STARTF_USESHOWWINDOW; wShowWindow := ShowState; end; if CreateProcess(nil, PChar(AppName), nil, nil, False, CREATE_NEW_CONSOLE or NORMAL_PRIORITY_CLASS, nil, nil, StartupInfo, ProcessInfo) then Result := ProcessInfo.hProcess else Result := 0; end; Any ideas on how to fix it? Thank you in advance. Share this post Link to post
Der schöne Günther 316 Posted February 27, 2023 You should probably start by fixing your CreateProcessAndReturn(..): You pass the address of a constant string value into your lpCommandLine parameter, but if the parameter is passed, the memory must be writeable. Remove the const, and call UniqueString(AppName) before calling CreateProcess(..) Share this post Link to post
NamoRamana 3 Posted February 27, 2023 @Der schöne Günther - Thank you for suggestion. I tried removing the const and calling UniqueString(). Unfortunately it did not work. Share this post Link to post
Remy Lebeau 1392 Posted February 27, 2023 2 hours ago, Der schöne Günther said: You should probably start by fixing your CreateProcessAndReturn(..): The function is also leaking the ProcessInfo.hThread handle. Since that handle is not being returned to the caller, it needs to be closed before the function exits. Share this post Link to post
Remy Lebeau 1392 Posted February 27, 2023 (edited) 3 hours ago, NamoRamana said: When I double-click on Exe2 from File Explorer OR run in debug, it works fine... But in reality, Exe2 is started by Exe1 using CreatePorcess(). This is when I get "Not Supported" msg. Is Exe2 a console app or a GUI app? Does it initialize the COM library before creating the TNotificationCenter object? Internally, your Exe2 code is basically doing the following: The TNotificationCenter.Supported property returns False when the TCustomNotificationCenter.FPlatformNotificationCenter member is nil. TCustomNotificationCenter.FPlatformNotificationCenter is initialized in the TCustomNotificationCenter constructor using a call to TBaseNotificationCenter.InternalGetInstance(). On Windows, TBaseNotificationCenter.InternalGetInstance() calls System.Win.Notification.TPlatformNotificationCenter.GetInstance(). TPlatformNotificationCenter.GetInstance() returns the TNotificationCenterWinRT.NotificationCenter property. The NotificationCenterWinRT.NotificationCenter property getter attempts to create a TNotificationCenterWinRT singleton object, returning nil if it fails to be created. TNotificationCenterWinRT's constructor calls TToastNotificationManager.Statics.CreateToastNotifier(). The TToastNotificationManager.Statics property getter imports WinRT's Windows.UI.Notifications.ToastNotificationManager class, raising an exception if it fails. Then calls ToastNotificationManager.CreateToastNotifier() So, it sounds like Exe2 is unable to import WinRT's ToastNotificationManager class, which ultimately causes TNotificationCenter.Supported to return False. The WinRT API returns HRESULT error codes when things fail, unfortunately the RTL does not capture those error codes when bubbling WinRT failures up the call chain. However, the exceptions do have error messages on them, so if you attach the IDE's debugger to the Exe2 process before it creates the TNotificationCenter object, then you should be able to see those error messages (as well as step into the RTL's source code directly). For instance, have Exe1 pass a command-line argument to Exe2, such as '/debug', and then have Exe2's startup code check for that argument and if present then run a short wait loop that breaks once the Win32 IsDebuggerPresent() function returns True. That will give you an opportunity to debug Exe2 when it is launched by Exe1. Edited February 27, 2023 by Remy Lebeau Share this post Link to post
NamoRamana 3 Posted February 27, 2023 (edited) @Remy Lebeau Thank you for your pointers.. Here is what I found after attaching the Exe2 to the IDE debugger.. TOSVersion.Check() returns falls. Looks like its looking for Windows 8 and debugger shows 'Windows 7' Here is what I get when I run Exe2 from within Delphi IDE: Edited February 27, 2023 by NamoRamana Share this post Link to post
Remy Lebeau 1392 Posted February 27, 2023 (edited) 3 hours ago, NamoRamana said: TOSVersion.Check() returns falls. Looks like its looking for Windows 8 and debugger shows 'Windows 7' TPlatformNotificationCenter is looking for Windows Windows 8 and higher, yes. Does Exe2 have an app manifest that specifies your code supports Windows 8 and higher? TOSVersion uses the Win32 GetVersionEx() function internally when initializing its Major and Minor properties. GetVersionEx() provides accurate values regardless of app manifest only up to Windows 8.0, but from Windows 8.1 onward, it lies if you do not manifest your app those versions, per Microsoft's documentation: Quote With the release of Windows 8.1, the behavior of the GetVersionEx API has changed in the value it will return for the operating system version. The value returned by the GetVersionEx function now depends on how the application is manifested. Applications not manifested for Windows 8.1 or Windows 10 will return the Windows 8 OS version value (6.2). Once an application is manifested for a given operating system version, GetVersionEx will always return the version that the application is manifested for in future releases. To manifest your applications for Windows 8.1 or Windows 10, refer to Targeting your application for Windows. Your screenshots are suggesting that you are running your app on Windows 10 or higher, and your app is manifested only up to Windows 7, not for Windows 8 or higher. When running Exe2 directly in Explorer, or via CreateProcess() in Exe1, GetVersionEx() should be reporting what your app is actually manifested for. But, when launching Exe2 directly from the IDE, its would seem that GetVersionEx() is picking up the IDE's manifest rather than your app's manifest, which is odd. In any case, if you want to use TNotificationCenter, make sure you specify support for Windows 8 in your app manifest. Edited February 27, 2023 by Remy Lebeau Share this post Link to post
KodeZwerg 54 Posted February 28, 2023 or refactor source to always use real version like exemplary here shown: type TTrueVersion = packed record Major, Minor, Build: DWORD; end; procedure RtlGetNtVersionNumbers(out MajorVersion, MinorVersion, BuildNumber: DWORD); stdcall; external 'Ntdll.dll'; function GetTrueVersion: TTrueVersion; var vMaj, vMin, vBuild: DWORD; begin RtlGetNtVersionNumbers(vMaj, vMin, vBuild); Result.Major := vMaj; Result.Minor := vMin; Result.Build := Lo(vBuild); end; procedure TForm1.FormCreate(Sender: TObject); var ver: TTrueVersion; begin ver := GetTrueVersion; Label1.Caption := Format('Running Windows %d.%d Build: %d', [ver.Major, ver.Minor, ver.Build]); end; 1 Share this post Link to post
NamoRamana 3 Posted February 28, 2023 (edited) @KodeZwerg - I'm not planning to change the VCL code. Thank you for your contribution. @Remy Lebeau - I suspected GetVersionEx() as I was looking TOSVersion constructor. Thank you for your detailed reply. I do not have manifests for both Exe1 and Exe2. I will create and include (as a resource to the executable) the manifest file with "requireAdministrator" privilege in Exe2. Couple of questions - (1). Do I need to create manifest with "requireAdministrator" privilege for Exe1 as well? (2). Could you please take a look at following link that has <supportedOS> tag? As I understand, I will remove tags for Windows 7 and Windows Vista from it. https://stackoverflow.com/questions/50818954/manifest-and-getversionex Edited February 28, 2023 by NamoRamana Share this post Link to post
programmerdelphi2k 237 Posted February 28, 2023 I think the "A manifest definition" is already include in exe by default... you can see it using Notepad++ for example, not? Share this post Link to post
Remy Lebeau 1392 Posted February 28, 2023 2 hours ago, NamoRamana said: I do not have manifests for both Exe1 and Exe2. Then you will run into problems running your code on Windows 8.1 and later, since GetVersionEx() will report as Windows 8.0 in them. 2 hours ago, NamoRamana said: I will create and include (as a resource to the executable) the manifest file with "requireAdministrator" privilege in Exe2. Modern Delphi versions can handle that privilege for you, as you can enable and configure the manifest in the Project Options: https://docwiki.embarcadero.com/RADStudio/en/Application_Options#Manifest_File_.28Windows_Only.29 But, why do you think you need "requireAdministrator"? You don't need admin rights to send notifications. Does Exe2 do other things that require admin rights? However, you will need a custom manifest in order to specify the Windows versions that your code supports, as the IDE does not currently provide options for configuring that list: https://learn.microsoft.com/en-us/windows/win32/sysinfo/targeting-your-application-at-windows-8-1 2 hours ago, NamoRamana said: Do I need to create manifest with "requireAdministrator" privilege for Exe1 as well? Only if Exe1 is doing other things that need admin rights. You do not need admin rights to launch Exe2 from Exe1. 2 hours ago, NamoRamana said: Could you please take a look at following link that has <supportedOS> tag? What about it, exactly, are you asking about? 2 hours ago, NamoRamana said: As I understand, I will remove tags for Windows 7 and Windows Vista from it. Why? If your code was already supporting them, there is no reason to remove them. Share this post Link to post
KodeZwerg 54 Posted February 28, 2023 3 hours ago, NamoRamana said: @KodeZwerg - I'm not planning to change the VCL code. Thank you for your contribution. You are welcomed, I just tried to show you a way that always work, no matter what manifest (or none at all) you have, since the normal ways are able to not tell you the truth. Share this post Link to post
NamoRamana 3 Posted February 28, 2023 Quote But, why do you think you need "requireAdministrator"? You don't need admin rights to send notifications. Does Exe2 do other things that require admin rights? Yes, Exe2 requires admin rights, as it registers some COM libraries (not every single time, but only when a new library is introduced or existing COM signatures change). Share this post Link to post
KodeZwerg 54 Posted February 28, 2023 You should not use requireAdministrator for that reason, it is better to just elevate when needed. You can find a very useful unit that provide such mechanism on StackOverflow. Share this post Link to post