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

 

感谢阅读

 

posted @ 2020-08-18 08:46  灰主流  阅读(1277)  评论(3编辑  收藏  举报