Search code examples
c#winapiprocessinteropdpi-aware

Calling GetProcessDpiAwareness from C# always returns error E_INVALIDARG


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?


Solution

  • 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);