草庐IT

C++/C#通过进程ID查找对应窗口句柄

还在水里游 2023-04-03 原文

新任务,要打开设置页面,并移动到指定位置,并设定窗口大小。

打开设置和移动窗口是非常简单,直接用ShellExecute和MoveWindow就可以了,上代码:

using System;
using System.Runtime.InteropServices;

namespace OpenWindows
{
    class Program
    {
        static void Main(string[] args)
        {
            ShellExecute(IntPtr.Zero, "open", "ms-settings:storagesense", "", null, 9);
            IntPtr handle = (IntPtr)0x00030E7E;
            MoveWindow(handle, 960, 0, 960, 1050, true);
        }
        [DllImport("Shell32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern IntPtr ShellExecute(IntPtr hwnd, string lpOperation, string lpFile, string lpParameters, string lpDirectory, int nShowCmd);
        [DllImport("User32.dll")]
        static extern bool MoveWindow(IntPtr handle, int x, int y, int width, int height, bool redraw);
    }
}

一切顺利,现在来看,只要获取设置窗口的句柄就可以了,本以为是个小问题,但是却花费了大量的时间。

首先,获取设置窗口的句柄,肯定先想着通过setting的进程ID来获取他对应的窗口句柄就行了,先拿进程id,这里的函数为:GetProcessesByName,拿到后发现有一个MainWindowHandle属性,nice,看样子一下子就解决了,以下是代码:

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace OpenWindows
{
    class Program
    {
        static void Main(string[] args)
        {
            ShellExecute(IntPtr.Zero, "open", "ms-settings:storagesense", "", null, 9);
            var setingProcesses = Process.GetProcessesByName("SYSTEMSETTINGS");
            //var setingId = setingProcesses[0].Id;
            var setingMainWindowHandle = setingProcesses[0].MainWindowHandle;
            MoveWindow(setingMainWindowHandle, 960, 0, 960, 1050, true);
        }
        [DllImport("Shell32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern IntPtr ShellExecute(IntPtr hwnd, string lpOperation, string lpFile, string lpParameters, string lpDirectory, int nShowCmd);
        [DllImport("User32.dll")]
        static extern bool MoveWindow(IntPtr handle, int x, int y, int width, int height, bool redraw);
    }
}

然后在实际使用过程中却发现,实际上并没有move成功,打开vs上的spi++查看窗口发现,原来找到的只是子窗口,如下图:

 

 必须找到父窗口才行,获取父窗口的方法为:GetParent,但是在实际中发现,直接调用GetParent得到的值为0,如下图:

 

 搜索了一通才发现,原来是要等新打开的窗口注册为的子窗口后才能获取到父窗口,于是加了等待逻辑:

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;

namespace OpenWindows
{
    class Program
    {
        static void Main(string[] args)
        {
            ShellExecute(IntPtr.Zero, "open", "ms-settings:storagesense", "", null, 9);
            var setingProcesses = Process.GetProcessesByName("SYSTEMSETTINGS");
            //var setingId = setingProcesses[0].Id;
            IntPtr setingMainWindowHandle = setingProcesses[0].MainWindowHandle;
            IntPtr setingParentWindowHandle;
            int i = 0;
            while (i < 100)
            {
                setingParentWindowHandle = GetParent(setingMainWindowHandle);
                if (setingParentWindowHandle != IntPtr.Zero)
                {
                    MoveWindow(setingMainWindowHandle, 960, 0, 960, 1050, true);
                }
                Thread.Sleep(100);
                i++;
            }
        }
        [DllImport("Shell32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern IntPtr ShellExecute(IntPtr hwnd, string lpOperation, string lpFile, string lpParameters, string lpDirectory, int nShowCmd);
        [DllImport("User32.dll")]
        static extern bool MoveWindow(IntPtr handle, int x, int y, int width, int height, bool redraw);
        [DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
        public static extern IntPtr GetParent(IntPtr hWnd);
    }
}

试下了可行,于是提交代码以为是完成任务了,但后来测了几遍发现,有时候会失效,又测了几遍发现是在人为打开设置页面后,过一会儿再执行代码就不行了,setting进程的MainWindowHandle为0,如下图:

具体是什么原因不是很清楚, 个人猜测是setting进程起的窗口,已经注册成了子窗口,而这个函数是不搜索子窗口的,故这个MainWindowHandle值为空,那如果这样就难搞了,因为Windows找窗口的api无论是FindWindow还是EnumWindows都只会找顶层窗口,如下图:

 没办法只能转换思路,查看spi++发现,我只要找这个顶层窗口就可以了,不用管那个setting进程起的子窗口:

于是用 FindWindow来搜窗口名:

看起来是可行,但是被上级否决,理由是通过文字搜索不可靠,而且用户切换系统语言,适配起来比较麻烦。想想也是,那这条路是不可行的,只能继续找方案。

 既然window提供的api只能找顶层窗口,而我也只关注顶层窗口,那直接通过其他方案来搜顶层窗口就可以了:

 通过spi++发现,窗口的类名为:ApplicationFrameWindow,那直接通过类名搜索好了。由于FindWindow一次只能查找一个,所以用了EnumWindows。

跑下代码发现,虽然这个设置窗口在结果里,但是这个类名的顶层窗口有多个,并不能作为唯一的判断依据,这下就有点难办了。

看spi++,设置进程起的窗口是他的子窗口,那是不是可以通过搜索顶层窗口的子窗口,查看子窗口的进程id来实现呢?说干就干,一番折腾发现还真可行:

但这种方法也是有局限性,就是刚打开的设置窗口还未注册成子窗口,通过遍历子窗口是不行的,所以还要结合之前的方法,做一次判断才行,下面是完整的代码:

 

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;

namespace OpenWindows
{
    class Program
    {
        private static bool IsChildWindowsByProcessesId(IntPtr handle, int processesId)
        {
            uint iProcessId;
            IntPtr childHandle = IntPtr.Zero;
            while (true)
            {
                childHandle = FindWindowEx(handle, childHandle, "Windows.UI.Core.CoreWindow", null);
                if (childHandle == IntPtr.Zero) break;
                GetWindowThreadProcessId(childHandle, out iProcessId);
                if (processesId == iProcessId)
                    return true;
            }
            return false;
        }
        public static IReadOnlyList<IntPtr> FindWindowByClassName(string className)
        {
            var windowList = new List<IntPtr>();
            EnumWindows(OnWindowEnum, (IntPtr)0);
            return windowList;

            bool OnWindowEnum(IntPtr hwnd, IntPtr lparam)
            {
                var lpString = new StringBuilder(512);
                GetClassName(hwnd, lpString, lpString.Capacity);
                if (lpString.ToString().Equals(className, StringComparison.InvariantCultureIgnoreCase))
                    windowList.Add(hwnd);

                return true;
            }
        }
        static void Main(string[] args)
        {
            ShellExecute(IntPtr.Zero, "open", "ms-settings:storagesense", "", null, 9);

            IntPtr settingHandle = IntPtr.Zero;
            var setingProcesses = Process.GetProcessesByName("SYSTEMSETTINGS");
            IntPtr setingMainWindowHandle = setingProcesses[0].MainWindowHandle;
            if (setingMainWindowHandle != IntPtr.Zero)
            {
                int i = 0;
                while (i < 100)
                {
                    settingHandle = GetParent(setingMainWindowHandle);
                    if (settingHandle != IntPtr.Zero)
                    {
                        break;
                    }
                    Thread.Sleep(100);
                    i++;
                }
            }
            else
            {
                var applicationFrameWindowHandle = FindWindowByClassName("ApplicationFrameWindow");
                var setingId = setingProcesses[0].Id;
                foreach (var handle in applicationFrameWindowHandle)
                {
                    if (IsChildWindowsByProcessesId(handle, setingId))
                    {
                        settingHandle = handle;
                    }
                }

            }
            MoveWindow(settingHandle, 960, 0, 960, 1050, true);
        }

        public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
        [DllImport("Shell32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern IntPtr ShellExecute(IntPtr hwnd, string lpOperation, string lpFile, string lpParameters, string lpDirectory, int nShowCmd);
        [DllImport("User32.dll")]
        static extern bool MoveWindow(IntPtr handle, int x, int y, int width, int height, bool redraw);
        [DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
        public static extern IntPtr GetParent(IntPtr hWnd);
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
        [DllImport("user32.dll")]
        private static extern int GetClassName(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
        [DllImport("user32.dll", SetLastError = true)]
        public static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr hWndChildAfter, string className, string windowTitle);
        [DllImport("user32.dll", SetLastError = true)]
        static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
    }
}

思路说起来顺利,但实际过程曲折,还是得多动手才行啊。

有关C++/C#通过进程ID查找对应窗口句柄的更多相关文章

  1. ruby - 通过 rvm 升级 ruby​​gems 的问题 - 2

    尝试通过RVM将RubyGems升级到版本1.8.10并出现此错误:$rvmrubygemslatestRemovingoldRubygemsfiles...Installingrubygems-1.8.10forruby-1.9.2-p180...ERROR:Errorrunning'GEM_PATH="/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/ruby-1.9.2-p180@global:/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/rub

  2. ruby - 在 jRuby 中使用 'fork' 生成进程的替代方案? - 2

    在MRIRuby中我可以这样做:deftransferinternal_server=self.init_serverpid=forkdointernal_server.runend#Maketheserverprocessrunindependently.Process.detach(pid)internal_client=self.init_client#Dootherstuffwithconnectingtointernal_server...internal_client.post('somedata')ensure#KillserverProcess.kill('KILL',

  3. ruby - 通过 erb 模板输出 ruby​​ 数组 - 2

    我正在使用puppet为ruby​​程序提供一组常量。我需要提供一组主机名,我的程序将对其进行迭代。在我之前使用的bash脚本中,我只是将它作为一个puppet变量hosts=>"host1,host2"我将其提供给bash脚本作为HOSTS=显然这对ruby​​不太适用——我需要它的格式hosts=["host1","host2"]自从phosts和putsmy_array.inspect提供输出["host1","host2"]我希望使用其中之一。不幸的是,我终其一生都无法弄清楚如何让它发挥作用。我尝试了以下各项:我发现某处他们指出我需要在函数调用前放置“function_”……这

  4. ruby - 通过 ruby​​ 进程共享变量 - 2

    我正在编写一个gem,我必须在其中fork两个启动两个webrick服务器的进程。我想通过基类的类方法启动这个服务器,因为应该只有这两个服务器在运行,而不是多个。在运行时,我想调用这两个服务器上的一些方法来更改变量。我的问题是,我无法通过基类的类方法访问fork的实例变量。此外,我不能在我的基类中使用线程,因为在幕后我正在使用另一个不是线程安全的库。所以我必须将每个服务器派生到它自己的进程。我用类变量试过了,比如@@server。但是当我试图通过基类访问这个变量时,它是nil。我读到在Ruby中不可能在分支之间共享类变量,对吗?那么,还有其他解决办法吗?我考虑过使用单例,但我不确定这是

  5. ruby - 通过 RVM (OSX Mountain Lion) 安装 Ruby 2.0.0-p247 时遇到问题 - 2

    我的最终目标是安装当前版本的RubyonRails。我在OSXMountainLion上运行。到目前为止,这是我的过程:已安装的RVM$\curl-Lhttps://get.rvm.io|bash-sstable检查已知(我假设已批准)安装$rvmlistknown我看到当前的稳定版本可用[ruby-]2.0.0[-p247]输入命令安装$rvminstall2.0.0-p247注意:我也试过这些安装命令$rvminstallruby-2.0.0-p247$rvminstallruby=2.0.0-p247我很快就无处可去了。结果:$rvminstall2.0.0-p247Search

  6. ruby-on-rails - Enumerator.new 如何处理已通过的 block ? - 2

    我在理解Enumerator.new方法的工作原理时遇到了一些困难。假设文档中的示例:fib=Enumerator.newdo|y|a=b=1loopdoy[1,1,2,3,5,8,13,21,34,55]循环中断条件在哪里,它如何知道循环应该迭代多少次(因为它没有任何明确的中断条件并且看起来像无限循环)? 最佳答案 Enumerator使用Fibers在内部。您的示例等效于:require'fiber'fiber=Fiber.newdoa=b=1loopdoFiber.yieldaa,b=b,a+bendend10.times.m

  7. ruby - 当使用::指定模块时,为什么 Ruby 不在更高范围内查找类? - 2

    我刚刚被困在这个问题上一段时间了。以这个基地为例:moduleTopclassTestendmoduleFooendend稍后,我可以通过这样做在Foo中定义扩展Test的类:moduleTopmoduleFooclassSomeTest但是,如果我尝试通过使用::指定模块来最小化缩进:moduleTop::FooclassFailure这失败了:NameError:uninitializedconstantTop::Foo::Test这是一个错误,还是仅仅是Ruby解析变量名的方式的逻辑结果? 最佳答案 Isthisabug,or

  8. ruby - 寻找通过阅读代码确定编程语言的ruby gem? - 2

    几个月前,我读了一篇关于ruby​​gem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:

  9. 通过 MacPorts 的 RubyGems 是个好主意吗? - 2

    从MB升级到新的MBP后,Apple的迁移助手没有移动我的gem。我这次是通过macports安装ruby​​gems,希望在下次升级时避免这种情况。有什么我应该注意的陷阱吗? 最佳答案 如果你想把你的gems安装在你的主目录中(在传输过程中应该复制过来,作为一个附带的好处,会让你以你自己的身份运行geminstall,而不是root),将gemhome:键设置为您在~/.gemrc中的主目录中的路径. 关于通过MacPorts的RubyGems是个好主意吗?,我们在StackOverf

  10. ruby - 查找字符串中的内容类型(数字、日期、时间、字符串等) - 2

    我正在尝试解析一个CSV文件并使用SQL命令自动为其创建一个表。CSV中的第一行给出了列标题。但我需要推断每个列的类型。Ruby中是否有任何函数可以找到每个字段中内容的类型。例如,CSV行:"12012","Test","1233.22","12:21:22","10/10/2009"应该产生像这样的类型['integer','string','float','time','date']谢谢! 最佳答案 require'time'defto_something(str)if(num=Integer(str)rescueFloat(s

随机推荐