Jump to content
NamoRamana

Windows Notification in Exe2 when Exe2 started from Exe1

Recommended Posts

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

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
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
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 by Remy Lebeau

Share this post


Link to post

@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'

 

image.thumb.png.201050e05b2f214fe11d3b8a37221e22.png

 

Here is what I get when I run Exe2 from within Delphi IDE:

 

image.png.f4bf48628814fbf790cc1192851d4f53.png

Edited by NamoRamana

Share this post


Link to post
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 by Remy Lebeau

Share this post


Link to post

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;

 

  • Like 1

Share this post


Link to post

@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 by NamoRamana

Share this post


Link to post
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
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
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

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

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

×