3

I have a service that launches an executable into a user session with CreateProcessAsUser, specifying the desktop in the STARTUPINFO parameter. It works well.

My executable is not manifested, nor does it call any DPI-related API.

When I launch my executable manually by double-clicking or via cmd.exe, Task Manager correctly shows the DPI Awareness as "Unaware".

However, when my executable is launched by the service, Task Manager shows DPI Awareness as "Per Monitor" - and indeed, it behaves as such.

Setting the default DPI awareness for a process says:

There are two main methods to specify the default DPI awareness of a process:

  1. through an application manifest setting
  2. programmatically through an API call

I am doing neither of these things.

I confirmed that the .exe is not manifested by using mt.exe. I set function breakpoints on the following:

  • user32.dll!SetProcessDpiAwarenessContext
  • user32.dll!SetThreadDpiAwarenessContext
  • shcore.dll!SetProcessDpiAwareness

No breakpoint is hit; however when launched from the service, I can only attach my debugger once I'm already inside main - and it seems that the DPI awareness is already set at that point.

Is there anywhere else the DPI awareness can be getting set?

This is a hybrid rust / C application - there is no (for example) .NET dependency referenced.


EDIT:

Using the JIT debugger, I can break at mainCRTStartup and see the DPI Awareness is already "PerMonitor" at that point. Calling SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_UNAWARE) or SetProcessDpiAwareness(PROCESS_DPI_UNAWARE) have no effect.


EDIT:

When launched from my service with CreateProcessAsUser; the executable has this environment variable:

__COMPAT_LAYER=HighDpiAware

The environment passed to CreateProcessAsUser is created by calling:

CreateEnvironmentBlock with my user handle. The rest of the environment is as expected. Where is this coming from? There are no compatibility options set on the executable when I inspect it's properties in explorer...

TheNextman
  • 12,428
  • 2
  • 36
  • 75
  • Is the process inheriting from the parent process? Like, since a DPI-aware thread is starting the process... I guess we could run a test. create a DPI-aware app, then have it launch a DPI-unaware app and see. What happens if you call `SetProcessDpiAwareness` with `PROCESS_DPI_UNAWARE` in the child process? – Andy Sep 11 '20 at 23:44
  • @Andy https://stackoverflow.com/questions/54449681/ "*DPI awareness is not inherited from the launching process. The apps your process launch will have their DPI awareness set to whatever those apps want.*" – Remy Lebeau Sep 11 '20 at 23:56
  • @TheNextman Have you considered [making your service act as a debugger](https://docs.microsoft.com/en-us/windows/win32/debug/creating-a-basic-debugger) for the child process? Pass `DEBUG_PROCESS` to `CreateProcessAsUser()`, which will automatically attach your service as a debugger, then have the service wait for `CREATE_PROCESS_DEBUG_EVENT`, [set breakpoints](https://www.gamedev.net/forums/topic/578254-csetting-breakpoints-with-code/) on the DPI functions in the child process, resume its normal execution, and see if you receive any `EXCEPTION_DEBUG_EVENT(EXCEPTION_BREAKPOINT)` for them. – Remy Lebeau Sep 12 '20 at 00:13
  • really debug process from the first instruction not problem. if have binary for debug – RbMm Sep 12 '20 at 00:20
  • I use the JIT debugger to stop directly at `mainCRTStartup`. When executed from cmd.exe, my DPI Awareness is "Unaware". When launched from service with `CreateProcessAsUser`, my DPI Awareness is "Per Monitor". – TheNextman Sep 12 '20 at 02:53
  • Even when I call `SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_UNAWARE)` on the first line of `main` (verified by the debugger stopping at my existing function breakpoint), DPI Awareness is still "Per Monitor" when launched by `CreateProcessAsUser` – TheNextman Sep 12 '20 at 02:58
  • @TheNextman does your spawned app use any 3rd party libraries? If so maybe they are setting the DPI during initialization. Were you able to get the JIT debugger to see breakpoints being hit on the DPI functions? – Remy Lebeau Sep 12 '20 at 04:45
  • @RemyLebeau There are dependencies but everything is compiled from source. I'm confident nothing is setting the DPI - and anyway, it wouldn't explain why things "work" when launched via explorer or cmd.exe. – TheNextman Sep 12 '20 at 05:44
  • @RemyLebeau see my last edit. I call `CreateEnvironmentBlock` for my user and pass the environment to `CreateProcessAsUser`. Somewhere it gets the environment variable "__COMPAT_LAYER=HighDpiAware" which is causing this behaviour. – TheNextman Sep 12 '20 at 05:51
  • @TheNextman If [this (last comment)](https://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/5ef74b5b-bf72-45c2-be72-2724602a5671/how-to-stop-dpi-scaling-setting-inherited-to-child-processes?forum=windowsuidevelopment) is right then it seems that the environment variable is synthesized by Windows and inherited from the parent (or ancestor) process. – dxiv Sep 12 '20 at 06:25
  • *Where is this coming from?* guess that *APPHELP.DLL* loaded to your process and from there. it can set this variable under some conditions, *APPINFO.DLL* also can set inside *AiLaunchProcess* but this is used only when ShellExecute, runas - not your case – RbMm Sep 12 '20 at 10:10
  • @TheNextman OK, well now you have your answer. Regardless of WHERE `__COMPAT_LAYER` comes from, you can simply strip it off (or at least the `HighDpiAware` flag) before passing it to `CreateProcessAsUser()` – Remy Lebeau Sep 12 '20 at 15:43
  • @RemyLebeau Quite right. It's still a total mystery to me, but the letter of my question is answered. Thanks everyone for the help! – TheNextman Sep 13 '20 at 02:55

1 Answers1

2

My service is running as SYSTEM. When I call CreateProcessAsUser, in this case, the executable is also run as SYSTEM. I pass nullptr for the lpEnvironment parameter. MSDN says this:

A pointer to an environment block for the new process. If this parameter is NULL, the new process uses the environment of the calling process.

However, when I inspect the environment of my executable, I see:

__COMPAT_LAYER=HighDpiAware

This is forcing per-monitor DPI awareness. This is mysterious because, indeed - the AppCompatFlag is set for that executable in the registry for S-1-5-18 (SYSTEM), but I don't know how or where this value is coming from.

The variable is not set on my service (which also runs as SYSTEM) - presumably services don't get the AppCompat environment? But why does my child process have it, despite supposedly inheriting the environment of it's parent? I suppose these compatability flags must have special handling.

Anyway, the answer to my question is: remove HighDpiAware from the __COMPAT_LAYER environment variable.

TheNextman
  • 12,428
  • 2
  • 36
  • 75
  • 1
    Aaaaand I figured it out. On a high DPI system, if your process is not high-dpi aware, calling the DXGI function [DuplicateOutput](https://learn.microsoft.com/en-us/windows/win32/api/dxgi1_2/nf-dxgi1_2-idxgioutput1-duplicateoutput) calls `SetProcessDpiAwarenessContext` for you, and adds the compatibility flag. This seems to be undocumented. – TheNextman Sep 13 '20 at 03:32
  • Nice detective work, twice +1. Wonder whether it made any difference if the child *had* a manifest with dpiAware/ness set to false/unaware. – dxiv Sep 13 '20 at 08:11
  • 1
    @dxiv No - no difference; I tried adding a manifest and it was just overridden by the compatibility flag – TheNextman Sep 13 '20 at 19:17