前两天,我正在开开心心地撸着我的python,“领导”(没有其他合适的称谓了 )突然找到我说,他需要一个dll(动态链接库),功能是实现鼠标在指定的位置实现单击、双击、右击以及键盘输入,因为人家的软件是成品,没有可提供的接口使用。然后他就走了,剩下一脸懵逼的我。
跑起来
windows编程什么的我从来都没有接触过,确切的说,我连VS都没有用过。硬着头皮上,好在windows官方的支持文档十分充足,比如<a href="https://docs.microsoft.com/en-us/dotnet/framework/winforms/how-to-simulate-mouse-and-keyboard-events-in-code">这个</a>。最开始的时候,就算有完整的代码,我都不知道怎么让它跑起来,最后还求助了我<a href="http://hp.stuhome.net/">室友</a>,要不说我就服我的室友呢。果然很快就解决了“跑起来”的问题。在VS新建VC++桌面应用程序,然后代码粘贴,关键的是我们需要在“解决方案资源管理器”右键->属性->常规->公共语言运行时支持->添加公共语言运行时支持。然后我们就能在“引用”中的框架下顺利添加我们需要的依赖框架。然后程序就能顺利的跑起来了。如果要跑出效果,我们还要祭出spy++大杀器:在“工具”->spy++->搜索->查找窗口,我们就通过移动那个“瞄准器”来获得我们关心的窗口的“类名”和“名称”了,这方便我们在编程针对关心的窗口进行编程,比如针对FindWindow()这种函数,spy++灰常好用。
跑向目的地
我们已经通过官方的example知道怎么跑起来了,现在我们要让程序跑出我们需要的效果。但其实本小节和上小节没什么太大的关系。因为上一节的example是VC++的,这一节我们用的C#。
怎么说呢?c#这东西很像c艹,又很像java,刚用起来的时候有点怪怪的感觉。windows中c#编程,我们需要先指明运用的命名空间,也就是通过指明框架中各种命名空间来达到引用的效果,有点类似于c++中的include效果。当需要使用链接库中函数的时候,我们需要先声明,但声明的形式应与链接库中函数的相同。具体如下
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
private static extern int SetCursorPos(int x, int y);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
private static extern void mouse_event(int dwFlags, int dx, int dy, int dwData, int dwExtraInfo);
[DllImport("User32.dll")]
private static extern IntPtr FindWindowEx(IntPtr hwndParent,IntPtr hwndChildAfter,string lpszClass,string lpszWindows);
这里的private是指定函数为私有成员。这里的关键就是SetCursorPos和mouse_event两个函数,一个是设置鼠标的位置,一个是产生鼠标事件。网上有很多光于两个函数的释义,这里就不过多说明了。然后,我这里给出整个有效的代码,其中包含了一些其他的窗口函数,将来对windows下窗口编程也许用得着,就mark一下吧:
using System;
using System.Threading;
using System.Runtime.InteropServices;
using System.Drawing;
using System.Windows.Forms;
namespace SimulateKeyPress
{
class Form1 : Form
{
const int MOUSEEVENTF_MOVE = 0x0001;
const int MOUSEEVENTF_LEFTDOWN = 0x0002;
const int MOUSEEVENTF_LEFTUP = 0x0004;
const int MOUSEEVENTF_RIGHTDOWN = 0x0008;
const int MOUSEEVENTF_RIGHTUP = 0x0010;
const int MOUSEEVENTF_MIDDLEDOWN = 0x0020;
const int MOUSEEVENTF_MIDDLEUP = 0x0040;
const int MOUSEEVENTF_ABSOLUTE = 0x8000;
private Button button1 = new Button();
private Button button2 = new Button();
private TextBox textbox1 = new TextBox();
private TextBox textbox2 = new TextBox();
private Cursor cursor = new Cursor(Cursor.Current.Handle);
private Point ptold;
private const int WM_SETTEXT = 0x000C;
[STAThread]
public static void Main()
{
Application.EnableVisualStyles();
Application.Run(new Form1());
}
public Form1()
{
button1.Location = new Point(10, 10);
button1.TabIndex = 0;
button1.Text = "button1";
button1.AutoSize = true;
button1.MouseUp += new MouseEventHandler(button1_Click);
button2.Location = new Point(10, 40);
button2.TabIndex = 1;
button2.Text = "button2";
button2.AutoSize = true;
button2.MouseUp += new MouseEventHandler(button2_Click);
textbox1.Location = new Point(10, 70);
textbox1.TabIndex = 3; //set the tabindex greater than the textbox2
textbox1.Text = "textbox1";
textbox1.AutoSize = true;
textbox2.Location = new Point(10, 100);
textbox2.TabIndex = 2;
textbox2.Text = "textbox2";
textbox2.AutoSize = true;
//textbox2.MouseUp += new MouseEventHandler(button2_Click);
this.DoubleClick += new EventHandler(Form1_DoubleClick);
this.Controls.Add(button1);
this.Controls.Add(button2);
this.Controls.Add(textbox1);
this.Controls.Add(textbox2);
}
// Get a handle to an application window.
[DllImport("USER32.DLL", CharSet = CharSet.Unicode)]
public static extern IntPtr FindWindow(string lpClassName,string lpWindowName);
[DllImport("USER32.DLL", CharSet = CharSet.Unicode)]
public static extern IntPtr GetWindow(IntPtr hwndParent, int uCmd);
//[DllImport("USER32.DLL")]
//public static extern bool EnumChildWindows(IntPtr hWndParent, bool lpEnumFunc, string lParam);
// Activate an application window.
[DllImport("User32.dll")]
private static extern IntPtr FindWindowEx(IntPtr hwndParent,IntPtr hwndChildAfter,string lpszClass,string lpszWindows);
[DllImport("User32.dll")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("USER32.DLL", CharSet = CharSet.Auto, SetLastError = false)]
public static extern IntPtr SendMessage(HandleRef hWnd, uint Msg, IntPtr wParam, string lParam);
[DllImport("USER32.DLL")]
public static extern Int32 SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, string lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int SetCursorPos(int x, int y);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
static extern void mouse_event(int dwFlags, int dx, int dy, int dwData, int dwExtraInfo);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern bool GetCursorPos(Point lpPoint);
// Send a series of key presses to the Calculator application.
private void button1_Click(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
MessageBox.Show("button1_Left");
SetCursorPos(100, 120);
mouse_event(MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
mouse_event(MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
Thread.Sleep(1000);
SendKeys.SendWait("123");
}
else if (e.Button == MouseButtons.Right)
MessageBox.Show("button1_Right");
/*
IntPtr myHwnd = FindWindow("WindowsForms10.Window.8.app.0.141b42a_r9_ad1", null);
if (myHwnd != IntPtr.Zero)
{
IntPtr myEditHwnd = FindWindowEx(myHwnd, IntPtr.Zero,null, "button1");
if (myEditHwnd != IntPtr.Zero)
{
//SendMessage(myEditHwnd, 0x0021, IntPtr.Zero, null);
//IntPtr myEditHwnd2 = GetWindow(myEditHwnd, 2);//the number reprent the Cmd GW_HWNDNEXT
//if(myEditHwnd2 != IntPtr.Zero)
//{
//SendMessage(myEditHwnd2, 0x0021, IntPtr.Zero, null);
//}
}
else MessageBox.Show("not find the button");
}
else MessageBox.Show("not find the window");
// Get a handle to the Calculator application. The window class
// and window name were obtained using the Spy++ tool.
IntPtr windowHandl = FindWindow("CalcFrame", "Calculator");
// Verify that Calculator is a running process.
if (windowHandl == IntPtr.Zero)
{
//MessageBox.Show("Calculator is not running.");
return;
}
// Make Calculator the foreground application and send it
// a set of calculations.
SetForegroundWindow(windowHandl);
SendKeys.SendWait("123");
*/
}
MouseEventArgs fake_event;
private void button2_Click(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
fake_event = new MouseEventArgs(MouseButtons.Left, 1, 0, 0, 0);
else if (e.Button == MouseButtons.Right)
fake_event = new MouseEventArgs(MouseButtons.Right, 1, 0, 0, 0);
//IntPtr myTestHandle = new IntPtr(0x004506F8);
//HandleRef hrefHwndTarget = new HandleRef(null, myHwnd);
//SendMessage(hrefHwndTarget, WM_SETTEXT, IntPtr.Zero, "mmp");
IntPtr myHwnd = FindWindow("WindowsForms10.Window.8.app.0.141b42a_r9_ad1", null);
if (myHwnd != IntPtr.Zero)
{
IntPtr myEditHwnd = FindWindowEx(myHwnd, IntPtr.Zero, "WindowsForms10.EDIT.app.0.141b42a_r9_ad1", null);
if (myEditHwnd != IntPtr.Zero)
{
SendMessage(myEditHwnd, WM_SETTEXT, IntPtr.Zero, "mmp");
IntPtr myEditHwnd2 = GetWindow(myEditHwnd, 2);//the number reprent the Cmd GW_HWNDNEXT
if(myEditHwnd2 != IntPtr.Zero)
{
SendMessage(myEditHwnd2, WM_SETTEXT, IntPtr.Zero, "mmp");
}
}
else MessageBox.Show("not find the textbox");
}
else MessageBox.Show("not find the window");
button1_Click(this, fake_event);
}
// Send a key to the button when the user double-clicks anywhere
// on the form.
private void Form1_DoubleClick(object sender, EventArgs e)
{
// Send the enter key to the button, which raises the click
// event for the button. This works because the tab stop of
// the button is 0.
SendKeys.Send("{ENTER}");
}
}
}
单击button1,将给出提示信息,其后,鼠标自动移至(100,120)的位置,然后双击。然后睡眠100ms,最后输入123。如果我们在这个位置是一个txt文件,那么这个demo就会打开txt,然后输入123。到这里,我们就基本完成了功能。
LabVIEW调用
虽然在窗口编程下,我们达到了效果。但是要实现最最最开始的业务需求,我们应该编译成dll,并在LabVIEW中调用成功才算数。VS->文件->新建->项目->.NET类库,输入下面的代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace CTRL
{
public class Ms_ctrl
{
const int MOUSEEVENTF_MOVE = 0x0001;
const int MOUSEEVENTF_LEFTDOWN = 0x0002;
const int MOUSEEVENTF_LEFTUP = 0x0004;
const int MOUSEEVENTF_RIGHTDOWN = 0x0008;
const int MOUSEEVENTF_RIGHTUP = 0x0010;
const int MOUSEEVENTF_MIDDLEDOWN = 0x0020;
const int MOUSEEVENTF_MIDDLEUP = 0x0040;
const int MOUSEEVENTF_ABSOLUTE = 0x8000;
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int SetCursorPos(int x, int y);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
static extern void mouse_event(int dwFlags, int dx, int dy, int dwData, int dwExtraInfo);
public int Input_xyc(int x, int y, string c)
{
SetCursorPos(x, y);
mouse_event(MOUSEEVENTF_LEFTUP | MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
mouse_event(MOUSEEVENTF_LEFTUP | MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
Thread.Sleep(1000);
SendKeys.SendWait(c);
SendKeys.SendWait("{ENTER}");
return (x+y);
}
}
}
这里我们的return类型故意写成int类型,是为了检验效果,最好的应该是bool类型。然后生成->重新生成解决方案。我们就在相应的文件下生成了dll。
然后我们打开LabVIEW->创建项目->VI->前面板->“添加两个数字输入控件,一个字符串输入控件,一个数字显示控件”->后面板->互连接口-> .NET->构造器节点->浏览到我们刚才生成的dll->确定->右键->创建->***类的方法->找到我们需要的函数(在这里是<code class="">Input_xyc(int x, int y, string c)</code>)->将两者的引用关系连线->数字输入、显示控件和字符串控件连线,然后我们输入合适的数字和字符串,点击运行,就能看到效果了。
program_dialog.png当然还是在相应的坐标出双击,然后输入字符串。最后显示控件为(x+y)。
<h1>吐槽</h1>
windows的编程还真是博大精深,分为API和MSDN两大类,c++API是更底层的借口函数,更灵活,MSDN是他们的高级封装,使用更简便。但往往很多时候,我找到了一个好用的函数,却发现他是另一个框架下的,我的内心是是跟拒绝的。
刚开始的,我开始浏览文档的时候,一会儿c++一会儿C#一会儿API一会儿MSDN,脑壳都给我整痛了。不过说到底,还是自己没有理顺理好。其实,这个解决方案我最早还用VC++的API写了一下,,发现并不是很方便。有些控件的handle是不容易获得的,在windows的消息机制下就不好处理了。
最后的最后,以上的所有文字都是我看windows编程两三天得出的心得感受,好不夸张地说,其中存在非常非常多的错误,望各位读至此的朋友不吝指正,万望海涵。
网友评论