如何从 hWnd 获取 Windows 10 应用商店应用程序的“应用程序名称”(例如 Edge)

Posted

技术标签:

【中文标题】如何从 hWnd 获取 Windows 10 应用商店应用程序的“应用程序名称”(例如 Edge)【英文标题】:How to get the "Application Name" from hWnd for Windows 10 Store Apps (e.g. Edge) 【发布时间】:2015-11-07 05:04:35 【问题描述】:

我正在尝试为 Windows 10 应用程序获取一个易于理解的“进程名称”。目前,他们都使用ApplicationFrameHost,所以我想我可以使用ModelIdPackageName,但似乎是Windows 10商店应用程序(我尝试使用MailStoreEdge)不适用于Package query API

使用kernel32.dllGetApplicationUserModelId 返回APPMODEL_ERROR_NO_APPLICATIONGetPackageId 返回APPMODEL_ERROR_NO_PACKAGE

如何获取 Windows 10 应用商店应用的标识符,以便我可以唯一标识,例如 Edge 以及任何其他 Windows 10 应用商店应用?


更新

我从hWnd(窗口句柄)获取进程ID,所以我认为我的问题实际上是如何从窗口句柄中获取“真实”进程ID。从那里开始,使用这些方法可能会奏效。

【问题讨论】:

也许我错过了一些东西:msdn.microsoft.com/en-us/library/windows/apps/br211377.aspx - 我需要进一步挖掘...... 您可能对我的问题的答案感兴趣:***.com/questions/32360149/… 使用 EnumWindows 或 UIAutomation API。 @TimBeaudet 感谢您的参考。此解决方案仅在窗口最小化时才有效,这对于 alt+tab 程序来说是个问题 :) 您的技术适用于非最小化的情况,因此它至少对某些用例有帮助!谢谢,虽然这还没有完全解决。 我认为最好的办法是:1. 列出所有窗口,2. 列出所有进程,3. 识别 Windows 10 应用程序进程(例如,通过它们的路径), 4.找到所有没有打开窗口的进程并显示它们,否则显示它们的窗口......这变得复杂...... 我的问题有点晚了,但我知道:ApplicationFrameHost 拥有每个应用程序的父(根)窗口,但在此窗口内它有由应用。您可以在 Spy++ 中看到这一点。您可以获取 AppFrameHost 窗口的子窗口并从其进程中获取名称。 【参考方案1】:

您可以使用GetPackageId(),然后使用PackageFullNameFromId()。

例如:

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

【讨论】:

这似乎不起作用。即使做像GetPackageId(hProcess, ref len, IntPtr.Zero) 这样简单的事情也会在我尝试过的所有应用程序上产生APPMODEL_ERROR_NO_PACKAGE(Windows 10) 请注意,我可能弄错了进程...记住我正在加载的进程是ApplicationFrameHost,所以也许我需要找到“实际”进程? 尝试使用GetProcessHandleFromHwnd 获取进程句柄,然后使用GetProcessId 获取进程ID。您可以通过打开任务管理器并查看详细信息选项卡中的 PID 列来验证您拥有的进程 ID 是否代表正在运行的进程。 问题指定了C#,你的答案应该匹配。 @Andy 即使您在技术上是正确的,但我认为@kiewic 的回答不值得-1,因为我的问题与kernel32.dll 有关,因此本地电话......我建议改进而不是惩罚答案。【参考方案2】:

GetPackageFullName/FamilyName/Id(hprocess,...) 等如果进程没有包标识,则返回 APPMODEL_ERROR_NO_PACKAGE。同上 GetApplicationUserModelId(hprocess...) 返回 APPMODEL_ERROR_NO_APPLICATION 因为同样该进程没有应用程序身份。

听起来您有一个用于代表应用程序工作但不是应用程序的进程的 HWND。这很常见 - RuntimeBroker 和其他进程作为“桌面应用程序”(即没有包或应用程序身份的进程)作为代理运行,为应用程序进程做他们自己无法做的事情。

对于您最初的问题,“我从 hWnd(窗口句柄)获取进程 ID,所以我认为我的问题实际上是如何从窗口句柄中获取“真实”进程 ID”这是一个根本有缺陷的方法。您有一个来自 HWND 的 pid,但如果进程是代理,它可以代表多个应用程序工作 - 代理进程没有身份;它知道 *per request/WinRT API call/etc 的调用者是谁,并将其工作范围限定为该身份。您无法在流程级别发现这一点。

【讨论】:

你是说我使用了一种根本有缺陷的方法;对于 Windows 10 应用程序,所有窗口(邮件、日历、商店的主窗口和 Edge 的所有窗口)都托管在 ApplicationFrameHost 下。我的应用程序的目标是显示所有打开的窗口,对于每个窗口,我都需要进程和图标。而且我看到 Windows 确实能够获取该信息,如果您尝试WinTab,以及运行任务管理器,您会看到该图标确实与窗口相关联。那么我怎样才能达到相同的结果呢? (解决方法?)【参考方案3】:

首先有一个叫做AppUserModelID的东西,它是任务栏用来分组窗口的窗口ID。因为所有 WinRT 窗口都来自同一个进程但它们没有分组,这意味着每个应用程序都有自己的 UserModelID。

要从 HWND 获取 UserModelID,您可以使用来自 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);

prop 将包含值LPWSTR = 0x00838f68 L"Microsoft.BingWeather_8wekyb3d8bbwe!App"。这是格式为&lt;FullPackageFamilyName&gt;!&lt;EntryPoint&gt; 的完整入口点名称。已启动应用程序的入口点通常称为App。入口点在应用清单中定义。

还有一个有趣的事情——应用程序拥有的子窗口没有被破坏,而是从应用程序框架主机移到桌面窗口。我不知道为什么会这样,但你必须小心,因为FindWindow(nullptr, L"Weather") 返回了子应用程序窗口而不是 appframehost 窗口。

附: AppUserModelID 只是一个字符串,它的格式没有记录,所以这种方法并不是最可靠的。

附言另外我注意到你想要有图标和名称,你可以使用PackageManager,它需要你参考winmd程序集,如何做到这一点看here

【讨论】:

仅供参考,“AppUserModelID”以两种方式令人困惑地存在: 1. Win7:发明为悬挂在 HWND 上的字符串。由 User32 管理。被 glomming 等(UI 主义)使用 2. Win8:作为字符串投资在进程的令牌中。由内核管理(不仅仅是 UI 的东西)。由 appmodel 用于指示进程具有应用程序标识。 Packge 的进程在进程创建时将其 ApplicationUserModelID* 刻录到其进程令牌中。这是他们 DNA 的一部分,无法更改,因此它是可靠的,并且被 appmodel 用于识别应用程序。与 Win7 User32 glomming 技巧签约,该技巧只是一个与窗口关联的字符串,因此它位于用户模式内存中;由于它可能是可修改的,因此不应将其用于信任或安全决策。因此我们**将 AUMID 添加到进程令牌的原因。甚至还有基于它的专利*** :-) * ApplicationUserModelID aka AppUserModelID aka AUIMD aka AppId ** 免责声明:我在 Microsoft 工作。看看我的简历(提示:我在 AppX 上工作过...... :-) *** 普遍包标识符 -- google.com/patents/US20130062401【参考方案4】:

下面是获取实际进程名称的类似方法, Name of process for active window in Windows 8/10

使用 Spy++ 实用程序,确认 Windows.Core.UI.CoreWindow 是 Weather 的子窗口,并且是我们感兴趣的窗口。(在 Win10 10563 上验证)

【讨论】:

【参考方案5】:

UWP 应用程序被包装到另一个应用程序/进程中。如果这有焦点,则尝试查找子 UWP 进程。

您将需要一些 P/Invoke 方法。看看这个类,它提供了完成这项工作所需的所有代码:

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;

namespace ***

    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
    

现在,使用另一个 P/Invoke 方法获取当前前景窗口的句柄

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

使用返回值并从上面的代码中调用GetProcessName 方法。您应该会收到正确的进程名称/路径。

这是一个简单的表格来测试代码:

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using ***;

namespace ***.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());
        
    

您可以下载完整的代码,包括GitHub 上的示例/测试。

【讨论】:

以上是关于如何从 hWnd 获取 Windows 10 应用商店应用程序的“应用程序名称”(例如 Edge)的主要内容,如果未能解决你的问题,请参考以下文章

从 hWnd 获取(资源)ID

从外部窗口的 GetWindowRect 获取 DPI 感知正确的 RECT

获取调用程序/被调用程序的 HWND

在 Windows 环境中,如何获取鼠标点击的坐标(相对于窗口)

如何在 HWND 中包含 Windows Word 文档?

MFC-通过HWND获取HANDLE