zoukankan      html  css  js  c++  java
  • c#获取状态栏图标并-模拟鼠标点击-模拟鼠标点击窗体的某些按钮

    1.需求

    有这么一个需求,有一个声卡插件:ASIO4ALL v2.1,用户有一个程序,是通过这个插件来检验声音硬件,有时候有多个硬件要同时测试,想做个小程序来自动切换ASIO的当前项。如下图所示:

    因此需求大概有如下:

    • 能够自动切换ASIO插件对应的当前项

    了解需求之后,感觉不算太难,安排。

    2.分析

    由于是小程序,所以代码尽量简单,逻辑尽量简单,有两个思路:

    思路1:通过ASIO SDK来进行控制

    思路2:找到ASIO进程的窗口,显示此窗口,点击固定位置

    思路3:模拟人工操作,双击NotifyIcon,点击某一个位置

    3.实验

    思路1:通过sdk发现,sdk是用来给当前进程使用的,用来播放声音输出的。并没有切换通道的接口。但是用户软件进程已经启动了ASIO插件,所以sdk再开启会造成用户进程挂掉。思路行不通。

    思路2:实验后发现,ASIO是没有主进程的,所以通过进程来查找窗口行不通。且窗口会被关闭,因为ASIO插件只是一个弹出窗体,主进程是用户软件,当设置完毕关闭窗口后,窗口被销毁,SPY++已经找不到此窗口了。思路行不通。

    思路3:模拟操作,需要知道system tray bar的每一个iconbutton的信息。点击后需要知道窗口里某一项的信息才能进行操作。搜索github之类的后发现,有现有方案可用(进程注入)。

    关于思路3,期间用spy++发现只能看到窗口信息。不能看到窗口详情,如下图:

    而想找到更多信息,只能通过其他方式。

     codeproject上的一篇文章写的很好(他这个能够获取道通知栏的各个图标了,但是名称读取失败):

    https://www.codeproject.com/Articles/10807/Shell-Tray-Info-Arrange-your-system-tray-icons

    4.实现和主要代码

    获取通知栏图标,并找到ASIO进程图标,并点击:

      var trayWindowHandle = FindWindow("Shell_TrayWnd", null);
                var trayNotifyHandle = FindWindowEx(trayWindowHandle, IntPtr.Zero, "TrayNotifyWnd", null);
    
                var sysBarHandle = FindWindowEx(trayNotifyHandle, IntPtr.Zero, "SysPager", null);
    
                var wind = FindWindowEx(sysBarHandle, IntPtr.Zero, "ToolbarWindow32", null);
    
                GetWindowThreadProcessId((int) wind, out var pid);
    
                //注入进程
                var process = OpenProcess(
                    (int) (ProcessAccessRights.All | ProcessAccessRights.VirtualMemoryOperation |
                           ProcessAccessRights.VirtualMemoryRead | ProcessAccessRights.VirtualMemoryWrite), false, pid);
                if (process == 0) MessageBox.Show("注入进程出错");
    
                var rMemAddress = VirtualAllocEx((IntPtr) process, IntPtr.Zero, 1024,
                    (uint) MemAllocation.MEM_COMMIT, (uint) MemProtect.PAGE_EXECUTE_READWRITE);
    
    
                uint TB_BUTTONCOUNT = 0x0400 + 24;
                uint TB_GETBUTTON = 0x0400 + 23;
                uint TB_GETBUTTONTEXT = 0x0400 + 45;
    
                var nButtonCount = SendMessage(wind, TB_BUTTONCOUNT, IntPtr.Zero, IntPtr.Zero);
                for (var i = 0; i < (int) nButtonCount - 1; i++)
                {
                    //请求button
                    var rrr = SendMessage(wind, TB_GETBUTTON, (IntPtr) i, rMemAddress);
    
                    //取得button
                    var buffer = new byte[20];
    
                    var oo = ReadProcessMemory((IntPtr) process, rMemAddress, buffer, buffer.Length,
                        out var pp);
    
                    var btnSize = Marshal.SizeOf(typeof(TBBUTTON));
                    var ptr = Marshal.AllocHGlobal(btnSize);
                    Marshal.Copy(buffer, 0, ptr, buffer.Length);
                    var bbb = Marshal.PtrToStructure<TBBUTTON>(ptr);
    
    
                    //获取button的文字
                    var rrr2 = SendMessage(wind, TB_GETBUTTONTEXT, (IntPtr) bbb.idCommand, rMemAddress);
                    var buff2 = new byte[1024];
                    var oo2 = ReadProcessMemory((IntPtr) process, rMemAddress, buff2, (int) rrr2,
                        out var mm);
                    var s = Encoding.ASCII.GetString(buff2, 0, (int) rrr2);
                    if (s.Contains(windowTrayTooltiptbx.Text))
                    {
                        // 点击
                        //  MakeWParam(GetDlgCtrlID(h1), BN_CLICKED)//diwei,gaowei
    
                        var BN_CLICKED = 0;
                        var wP = (bbb.idCommand & 0xffff) | (BN_CLICKED << 16);
    
                        uint WM_COMMAND = 0x0111;
                        var aaab = SendMessage(wind, WM_COMMAND, (IntPtr) wP, wind);
                    }
                }
    
    
                VirtualFreeEx((IntPtr) process, rMemAddress, 0, MEM_RELEASE);
    
                //释放注入进程
                CloseHandle((IntPtr) process);
    

      

    根据名称找到某一主窗口,通过C++的EnumWindows函数实现(因为FindWindow需要提供实体类名称或者窗口标题,而窗口标题可能会变):

     var wndHandle = new List<int>();
                EnumWindowsProc ewp = (hWnd, lParam) =>
                {
                    wndHandle.Add(hWnd);
                    return true;
                };
                EnumWindows(ewp, 0);
    
                var myWnd = 0;
                foreach (var w in wndHandle)
                {
                    //父窗体
                    var parent = GetParent(w);
                    //窗口标题
                    var title = new StringBuilder(256);
                    var hasText = GetWindowText((IntPtr) w, title, title.Capacity);
    
                    if (hasText > 0 && title.ToString().Contains(windowTitleTbx.Text))
                    {
                        myWnd = w;
                        break;
                    }
                }
    
                return myWnd;
    

      

    通过FindWindowEx函数找到主窗体的某一子窗体,即treeView窗体

       var treeWindowHandle =
                    FindWindowEx((IntPtr) mainWndHandle, IntPtr.Zero, "SysTreeView32", null);
    

      

    找到子窗体TreeView窗体的每个treeViewItem的文本信息(为了检索需要):

    因为是跨进程通信,所以需要进程注入:

    //注入进程 
                GetWindowThreadProcessId(mainWndHandle, out var pid);
                var process = OpenProcess(
                    (int) (ProcessAccessRights.All | ProcessAccessRights.VirtualMemoryOperation |
                           ProcessAccessRights.VirtualMemoryRead | ProcessAccessRights.VirtualMemoryWrite), false, pid);
                if (process == 0) MessageBox.Show("注入进程出错");

    申请内存

      //申请内存
                var tviPtr = VirtualAllocEx((IntPtr) process, IntPtr.Zero, 4096,
                    (uint) MemAllocation.MEM_COMMIT, (uint) MemProtect.PAGE_EXECUTE_READWRITE);

    TVM_GETNEXTITEM,获取treeView的根节点指针:

     var rootptr = SendMessage(treeWindowHandle, TVM_GETNEXTITEM, (IntPtr) TVGN_ROOT, IntPtr.Zero);

    得到文本:

     var tvi = new TVITEMA();
                    tvi.mask = TVIF_TEXT;
                    tvi.hItem = rootptr.ToInt32();
                    tvi.cchTextMax = 256;
                    tvi.pszText = (int) tviPtr + Marshal.SizeOf(typeof(TVITEMA));
                    var tviLocal = Marshal.AllocHGlobal(tviSize);
                    Marshal.StructureToPtr(tvi, tviLocal, false);
    
                    var ok = WriteProcessMemory((IntPtr) process, tviPtr, tviLocal, (IntPtr) tviSize,
                        out var p);
    
                    Marshal.FreeHGlobal(tviLocal);
                    var ok2 = SendMessage(treeWindowHandle, TVM_GETITEM, IntPtr.Zero, tviPtr);
    
                    var b = new byte[256];
                    var ok1 = ReadProcessMemory((IntPtr) process,
                        (IntPtr) ((int) tviPtr + Marshal.SizeOf(typeof(TVITEMA))), b, b.Length, out var p1);
                    //去掉空值
                    var lastOkValue = b.Length - 1;
                    while (b[lastOkValue] == 0 && lastOkValue > 0) lastOkValue = lastOkValue - 1;
                    //得到文本
                    var txt = Encoding.ASCII.GetString(b, 0, lastOkValue + 1);

    考虑到此treeView有两个同级别根节点,所以需要获取兄弟节点:

      //获取兄弟节点
                    var parentPtr =
                        SendMessage(treeWindowHandle, TVM_GETNEXTITEM, (IntPtr) TVGN_ROOT, IntPtr.Zero);
    
                    rootptr = SendMessage(treeWindowHandle, TVM_GETNEXTITEM, (IntPtr) TVGN_NEXT, parentPtr);

    释放进程注入内存

      VirtualFreeEx((IntPtr) process, tviPtr, 0, MEM_RELEASE);
                //释放注入进程
                CloseHandle((IntPtr) process);

    最后界面大致如下:

    当我点击【检索并点击---托盘图标的时候】,底部的图标会根第一个输入框的文字进行检索,找到后会点击,这时候图标对应的窗口出来了。

    当我点击第二个按钮【检索并改变选中项】,底部会根据第二个输入框的名称去查找窗体,并根据第三个输入框的内容去点击对应的treeviewItem

    至此,结束。界面的前两个输入框可以写死,最后一个因为所有的子项都被记录在字典里,所以可以作为index来传入参数。

    将这两个步骤合并后,就得到有1个参数(想要点击的Asio程序索引号或名称之类的)的控制台程序了

    参考文档(感谢这些优秀的开发者):

    Win32封装:https://github.com/soukoku/CommonWin32

    进程注入获取托盘图标(这个项目里的SysTreeView32.cs文件写的太优秀了,比我这个好一百倍,从这个里学习了很多):https://github.com/zhuzemin/OneKeyInstallSoftwares

    TB_GETBUTTON:https://docs.microsoft.com/en-us/windows/win32/controls/bumper-toolbar-control-reference-messages

    C++获取托盘图标:https://www.codeproject.com/Articles/10807/Shell-Tray-Info-Arrange-your-system-tray-icons

    进程注入:https://www.codeproject.com/Articles/5570/Stealing-Program-s-Memory

    托盘图标:https://www.codeproject.com/articles/10497/a-tool-to-order-the-window-buttons-in-your-taskbar

    获取托盘图标:https://projects.stephenklancher.com/files/Refresh_Notification_Area/RefreshNotificationAreaSource.zip

    ASIO-SDK:https://www.steinberg.net/en/company/developers.html

    Win32操作窗口:https://www.cnblogs.com/sunnylux/p/10537839.html

    还有其他参考文档,也一并表示感谢这些作者的奉献。

    最后,本实例的源码下载:https://files.cnblogs.com/files/lizhijian/2020-8-18-win32%E5%AF%BB%E6%89%BE%E5%AD%90%E8%BF%9B%E7%A8%8B%E7%AA%97%E5%8F%A3.rar

    感谢阅读

  • 相关阅读:
    Add source code and doc in maven
    Spring toturial note
    How to add local jar into maven project
    Ubuntu 12.04 下安装 Eclipse
    如何更改linux文件的拥有者及用户组(chown和chgrp)
    20非常有用的Java程序片段
    Java中的Set、List、Map的区别
    Java I/O知识点汇总
    Java I/O流整理
    hadoop2.0集群配置
  • 原文地址:https://www.cnblogs.com/congqiandehoulai/p/13521384.html
Copyright © 2011-2022 走看看