0

I want to retrieve the effective DPI awareness value for a specified process ID in Windows 10 Pro 64-bit. The value I need is one of the PROCESS_DPI_AWARENESS I can get with the WinAPI GetProcessDpiAwareness function.

To implement what I need, I wrote a simple one-window C# WPF app in VS 2015. I enter the process ID I'm interested in into the TextBox txtProcessID and the result is displayed in the TextBlock txtResult when I press the txtProcessID button:

private const int S_OK = 0;
private enum PROCESS_DPI_AWARENESS
{
    PROCESS_DPI_UNAWARE = 0,
    PROCESS_SYSTEM_DPI_AWARE = 1,
    PROCESS_PER_MONITOR_DPI_AWARE = 2
}
[DllImport("Shcore.dll")]
private static extern int GetProcessDpiAwareness(IntPtr hprocess, out PROCESS_DPI_AWARENESS value);

private void btnGetDPIAwareness_Click(object sender, RoutedEventArgs e)
{
    int procIDint = int.Parse(txtProcessID.Text);
    IntPtr procID = new IntPtr(procIDint);
    PROCESS_DPI_AWARENESS value;
    int res = GetProcessDpiAwareness(procID, out value);
    if (res == S_OK)
        txtResult.Text = value.ToString();
    else
        txtResult.Text = "Error: " + res.ToString("X");
}

But calling GetProcessDpiAwareness for any process always give me an error E_INVALIDARG. What am I doing wrong?

TecMan
  • 2,743
  • 2
  • 30
  • 64
  • 4
    [GetProcessDpiAwareness](https://msdn.microsoft.com/en-us/library/windows/desktop/dn302113.aspx): *"hprocess: **Handle** of the process that is being queried."* - The API expects a handle, whereas you blindly pass an ID. To get a process handle from a process ID, call [OpenProcess](https://msdn.microsoft.com/en-us/library/windows/desktop/ms684320.aspx) for example. – IInspectable Jun 24 '16 at 09:55
  • 2
    https://msdn.microsoft.com/en-us/library/system.diagnostics.process.handle(v=vs.110).aspx – Hans Passant Jun 24 '16 at 10:53

2 Answers2

5

From the documentation for GetProcessDpiAwareness the first parameter is the process handle, not the process ID. You'll need to OpenProcess, get the information you want then CloseHandle. (When defining a p/Invoke signature, any variable name that starts with an h or lp will usually be an IntPtr in managed code.)

As in:

private const int PROCESS_QUERY_INFORMATION = 0x0400;
private const int PROCESS_VM_READ = 0x0010;

[DllImport("Kernel32.dll", SetLastError = true)]
private static extern IntPtr OpenProcess(uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwProcessId);

[DllImport("Kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CloseHandle(IntPtr handle);

private const int S_OK = 0;
private enum PROCESS_DPI_AWARENESS
{
    PROCESS_DPI_UNAWARE = 0,
    PROCESS_SYSTEM_DPI_AWARE = 1,
    PROCESS_PER_MONITOR_DPI_AWARE = 2
}
[DllImport("Shcore.dll")]
private static extern int GetProcessDpiAwareness(IntPtr hprocess, out PROCESS_DPI_AWARENESS value);

private PROCESS_DPI_AWARENESS GetDpiState(uint processId)
{
    IntPtr handle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, processId);
    if (handle != IntPtr.Zero)
    {
        PROCESS_DPI_AWARENESS value;
        int result = GetProcessDpiAwareness(handle, out value);
        if (result == S_OK)
        {
            System.Diagnostics.Debug.Print(value.ToString());
        }
        CloseHandle(handle);
        if (result != S_OK)
        {
            throw new Win32Exception(result);
        }
        return value;
    }
    throw new Win32Exception(Marshal.GetLastWin32Error());
}

Although the easier way would be to use System.Diagnostics.Process like:

System.Diagnostics.Process proc = Process.GetProcessById(processId);
PROCESS_DPI_AWARENESS value;
int res = GetProcessDpiAwareness(proc.Handle, out value);
theB
  • 6,450
  • 1
  • 28
  • 38
  • PROCESS_VM_READ is not required. – Elmue Aug 22 '16 at 18:50
  • IMPORTANT: GetProcessDpiAwareness() does not work if executed in a service (SYSTEM/NT AUTHORITY) to obtain that information from another process. It will always return E_INVALIDARG – Elmue Aug 22 '16 at 19:16
0

Yes, it was my omission. I needed to pass the process handle to GetProcessDpiAwareness. I have just written my own working code based on the first 2 comments under my question:

int procID = int.Parse(txtProcessID.Text);
Process process = Process.GetProcessById(procID, ".");
PROCESS_DPI_AWARENESS value;
int res = GetProcessDpiAwareness(process.Handle, out value);
if (res == S_OK)
    txtResult.Text = value.ToString();
else
    txtResult.Text = "Error: " + res.ToString("X");
process.Close();

One additional comment: it's better to execute this code with admin rights to avoid problems with accessing other processes.

TecMan
  • 2,743
  • 2
  • 30
  • 64
  • 4
    Arguably, it's better to not have to run this code with admin privileges. Just pass the appropriate [access right](https://msdn.microsoft.com/en-us/library/windows/desktop/ms684880.aspx) to [OpenProcess](https://msdn.microsoft.com/en-us/library/windows/desktop/ms684320.aspx). `PROCESS_QUERY_LIMITED_INFORMATION` should be sufficient. – IInspectable Jun 24 '16 at 12:41
  • @IInspectable, I use the .NET managed way to access another process. Do you know how to do this in this environment? FYI: launching my mini-tool with the admin rights is not a problem for me as all is done within my dev pc :) – TecMan Jun 24 '16 at 16:50
  • There doesn't appear to be a way to provide the requested access rights when using the [System.Diagnostics.Process](https://msdn.microsoft.com/en-us/library/system.diagnostics.process.aspx) class. You could P/Invoke `OpenProcess`, though, as explained in [theB's answer](http://stackoverflow.com/a/38012663/1889329). – IInspectable Jun 24 '16 at 19:45
  • By default the handle returned by the `.Handle` property is opened with `PROCESS_ALL_ACCESS`. There isn't any way provided to set the process access level. See the [reference source](http://referencesource.microsoft.com/#System/services/monitoring/system/diagnosticts/Process.cs,150) – theB Jun 25 '16 at 11:54