How to get the “Application Name” from hWnd for Windows 10 Store Apps (e.g. Edge)

前端 未结 5 1690
清歌不尽
清歌不尽 2020-12-31 13:28

I\'m trying to get an understandable \"Process Name\" for Windows 10 apps. Currently, all of them use ApplicationFrameHost, so I thought I could use either the

相关标签:
5条回答
  • 2020-12-31 13:38

    You can use GetPackageId() and then PackageFullNameFromId().

    E.g.:

    HANDLE hProcess = OpenProcess(
        PROCESS_QUERY_LIMITED_INFORMATION,
        false,
        pe32.th32ProcessID);
    
    UINT32 bufferLength = 0;
    
    LONG result = GetPackageId(hProcess, &bufferLength, nullptr);
    
    BYTE* buffer = (PBYTE) malloc(bufferLength);
    result = GetPackageId(hProcess, &bufferLength, buffer);
    
    PACKAGE_ID* packageId = reinterpret_cast<PACKAGE_ID*>(buffer);
    wprintf(L"Name: %s\n", packageId->name);
    
    0 讨论(0)
  • 2020-12-31 13:41

    UWP apps are wrapped into an other app/process. If this has focus, then try and find the child UWP process.

    You will need some P/Invoke methods. Take a look at this class, which provide all the code you need to do the job:

    using System;
    using System.IO;
    using System.Runtime.InteropServices;
    using System.Text;
    
    namespace Stackoverflow
    {
        internal struct WINDOWINFO
        {
            public uint ownerpid;
            public uint childpid;
        }
    
        public class UwpUtils
        {
            #region User32
            [DllImport("user32.dll")]
            public static extern IntPtr GetForegroundWindow();
            [DllImport("user32.dll", SetLastError = true)]
            public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
            // When you don't want the ProcessId, use this overload and pass IntPtr.Zero for the second parameter
            [DllImport("user32.dll", SetLastError = true)]
            public static extern IntPtr GetWindowThreadProcessId(IntPtr hWnd, IntPtr ProcessId);
            /// <summary>
            /// Delegate for the EnumChildWindows method
            /// </summary>
            /// <param name="hWnd">Window handle</param>
            /// <param name="parameter">Caller-defined variable; we use it for a pointer to our list</param>
            /// <returns>True to continue enumerating, false to bail.</returns>
            public delegate bool EnumWindowProc(IntPtr hWnd, IntPtr parameter);
    
            [DllImport("user32", SetLastError = true)]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool EnumChildWindows(IntPtr hWndParent, EnumWindowProc lpEnumFunc, IntPtr lParam);
            #endregion
    
            #region Kernel32
            public const UInt32 PROCESS_QUERY_INFORMATION = 0x400;
            public const UInt32 PROCESS_VM_READ = 0x010;
    
            [DllImport("kernel32.dll", SetLastError = true)]
            public static extern bool QueryFullProcessImageName([In]IntPtr hProcess, [In]int dwFlags, [Out]StringBuilder lpExeName, ref int lpdwSize);
    
            [DllImport("kernel32.dll", SetLastError = true)]
            public static extern IntPtr OpenProcess(
                UInt32 dwDesiredAccess,
                [MarshalAs(UnmanagedType.Bool)]
                Boolean bInheritHandle,
                Int32 dwProcessId
            );
            #endregion
    
            public static string GetProcessName(IntPtr hWnd)
            {
                string processName = null;
    
                hWnd = GetForegroundWindow();
    
                if (hWnd == IntPtr.Zero)
                    return null;
    
                uint pID;
                GetWindowThreadProcessId(hWnd, out pID);
    
                IntPtr proc;
                if ((proc = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, (int)pID)) == IntPtr.Zero)
                    return null;
    
                int capacity = 2000;
                StringBuilder sb = new StringBuilder(capacity);
                QueryFullProcessImageName(proc, 0, sb, ref capacity);
    
                processName = sb.ToString(0, capacity);
    
                // UWP apps are wrapped in another app called, if this has focus then try and find the child UWP process
                if (Path.GetFileName(processName).Equals("ApplicationFrameHost.exe"))
                {
                    processName = UWP_AppName(hWnd, pID);
                }
    
                return processName;
            }
    
            #region Get UWP Application Name
    
            /// <summary>
            /// Find child process for uwp apps, edge, mail, etc.
            /// </summary>
            /// <param name="hWnd">hWnd</param>
            /// <param name="pID">pID</param>
            /// <returns>The application name of the UWP.</returns>
            private static string UWP_AppName(IntPtr hWnd, uint pID)
            {
                WINDOWINFO windowinfo = new WINDOWINFO();
                windowinfo.ownerpid = pID;
                windowinfo.childpid = windowinfo.ownerpid;
    
                IntPtr pWindowinfo = Marshal.AllocHGlobal(Marshal.SizeOf(windowinfo));
    
                Marshal.StructureToPtr(windowinfo, pWindowinfo, false);
    
                EnumWindowProc lpEnumFunc = new EnumWindowProc(EnumChildWindowsCallback);
                EnumChildWindows(hWnd, lpEnumFunc, pWindowinfo);
    
                windowinfo = (WINDOWINFO)Marshal.PtrToStructure(pWindowinfo, typeof(WINDOWINFO));
    
                IntPtr proc;
                if ((proc = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, (int)windowinfo.childpid)) == IntPtr.Zero)
                    return null;
    
                int capacity = 2000;
                StringBuilder sb = new StringBuilder(capacity);
                QueryFullProcessImageName(proc, 0, sb, ref capacity);
    
                Marshal.FreeHGlobal(pWindowinfo);
    
                return sb.ToString(0, capacity);
            }
    
            /// <summary>
            /// Callback for enumerating the child windows.
            /// </summary>
            /// <param name="hWnd">hWnd</param>
            /// <param name="lParam">lParam</param>
            /// <returns>always <c>true</c>.</returns>
            private static bool EnumChildWindowsCallback(IntPtr hWnd, IntPtr lParam)
            {
                WINDOWINFO info = (WINDOWINFO)Marshal.PtrToStructure(lParam, typeof(WINDOWINFO));
    
                uint pID;
                GetWindowThreadProcessId(hWnd, out pID);
    
                if (pID != info.ownerpid)
                    info.childpid = pID;
    
                Marshal.StructureToPtr(info, lParam, true);
    
                return true;
            }
            #endregion
        }
    }
    

    Now, get a handle to the current foreground window using another P/Invoke method

    [DllImport("user32.dll")]
    public static extern IntPtr GetForegroundWindow();
    

    Use the return value and call the GetProcessName method from the code above. You should receive the correct name/path to the process.

    Here is a simple Form to test the code:

    using System;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;
    using StackOverflow;
    
    namespace Stackoverflow.Test
    {
        public partial class TestForm : Form
        {
            WinEventDelegate dele = null;
            delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);
    
            [DllImport("user32.dll")]
            static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);
    
            private const uint WINEVENT_OUTOFCONTEXT = 0;
            private const uint EVENT_SYSTEM_FOREGROUND = 3;
    
            [DllImport("user32.dll")]
            public static extern IntPtr GetForegroundWindow();
            public TestForm()
            {
                InitializeComponent();
    
                dele = new WinEventDelegate(WinEventProc);
                IntPtr m_hhook = SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, IntPtr.Zero, dele, 0, 0, WINEVENT_OUTOFCONTEXT);
            }
    
            public void WinEventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
            {
                textBox1.AppendText(GetActiveWindowTitle() + "\n");
            }
    
            private string GetActiveWindowTitle()
            {
                return UwpUtils.GetProcessName(GetForegroundWindow());
            }
        }
    }
    

    You can download the full code, including the example/test on GitHub.

    0 讨论(0)
  • 2020-12-31 13:43

    GetPackageFullName/FamilyName/Id(hprocess,...) etc return APPMODEL_ERROR_NO_PACKAGE if the process has no package identity. Ditto GetApplicationUserModelId(hprocess...) returns APPMODEL_ERROR_NO_APPLICATION because likewise the process has no application identity.

    Sounds like you have an HWND for a process that does work on behalf of the application, but is not the application. This is quite common - RuntimeBroker and other processes run as 'Desktop apps' (i.e. process w/o package or application identity) as brokers to do things for application processes which they can't do for themselves.

    To your original question, "I'm getting the process ID from the hWnd (the window handle), so I think my problem is actually how to get the "real" process ID from a window handle" this is a fundamentally flawed approach. You have a pid from an HWND, but if the process is a broker it can do work on behalf of multiple applications - the broker process has no identity; it knows *per request/WinRT API call/etc who its caller is and scopes its work to that identity. You can't discover that at the process level.

    0 讨论(0)
  • 2020-12-31 13:43

    So first of all there is a thing called AppUserModelID, it's ID of window that is used by taskbar to group windows. Because all WinRT windows are from same process but they aren't grouped, it means that each app has own UserModelID.

    To get UserModelID from HWND you can use method from this answer.

    #include "Propsys.h"
    #include <propkey.h>
    
    #pragma comment (lib, "Shell32.lib")
    
    //.........
    
    IPropertyStore* propStore;
    
    auto weatherWnd = FindWindow(L"ApplicationFrameWindow", L"Weather");
    SHGetPropertyStoreForWindow(weatherWnd, IID_IPropertyStore, (void**)&propStore);
    
    PROPVARIANT prop;
    propStore->GetValue(PKEY_AppUserModel_ID, &prop);
    

    And prop will contain value LPWSTR = 0x00838f68 L"Microsoft.BingWeather_8wekyb3d8bbwe!App". This is full entry point name in format <FullPackageFamilyName>!<EntryPoint>. Entry point for launched apps usually called App. Entry points are defined in app manifest.

    Also interesting thing - child window that is owned by app is not destroyed, but is moved away from app frame host into desktop window. I don't know why it happens, but you must be careful because FindWindow(nullptr, L"Weather") returned child app window and not appframehost window.

    P.S. AppUserModelID is just a string and it's format is not documented, so this method is not exactly the most reliable one.

    P.P.S. Also I noticed that you want to have icon and name, you can use PackageManager for that, it requires you to reference winmd assembly, how to do this look here

    0 讨论(0)
  • 2020-12-31 13:53

    Below is a similar one for getting the actual process name, Name of process for active window in Windows 8/10

    With Spy++ utility, confirmed that Windows.Core.UI.CoreWindow is a child window of Weather and it is the one that we are interested in. (Verified on Win10 10563)

    0 讨论(0)
提交回复
热议问题