none
C#通过SendMessage方法向另外一个应用程序发送消息 RRS feed

  • 问题

  • 我现在想用C#代码通过SendMessage()方法向同花顺应用程序发送键盘输入的字母,但是在网上找了一些SendMessage()的例子,调试的时候同花顺键盘精灵一直接收不到,请问有谁知道是哪里的问题吗?我用的代码如下,向同花顺发送一个字母

    //pWnd为通过spy++获得到同花顺这个窗口的句柄

    //SendMessage中的第二个参数为发送一个字符,第三第四个参数我也不知道什么意思,在网上找的例子,第三第四个参数我看有的文章说不用传,我试着传了InPtr.Zero和null,还有就是代码中的参数,同花顺窗口还是获取不到字母。

    或者哪位老师有更好的办法让同花顺这个应用程序能获取程序向其发送的字母更好,万分感谢。正常情况下,在同花顺窗口输入字母,键盘精灵会自动触发。

    SendMessage(pWnd, 0x0104, (IntPtr)0x00000046, (IntPtr)0x20210001);

    2020年2月20日 11:45

答案

  • 先把代码贴出来

    using System;
    using System.Diagnostics;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;
    namespace ConsoleApp1
    {
        class Program
        {
            [DllImport("USER32.DLL")]
            private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
            [DllImport("USER32.DLL")]
            private static extern bool SetForegroundWindow(IntPtr hWnd);
            [DllImport("user32.dll")]
            private static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
            [DllImport("user32.dll", CharSet = CharSet.Auto)]
            private static extern int SendMessage(IntPtr hWnd, int uMsg, uint wParam, uint lParam);
            static void Main(string[] args)
            {
                var notepads = Process.GetProcessesByName("notepad");
                if (notepads != null && notepads.Length > 0)
                {
                    var window = notepads[0].MainWindowHandle;
                    if (window != null)
                    {
                        var control = FindWindowEx(window, IntPtr.Zero, "Edit", null);
                        if (control != null)
                        {
                            SendMessage(control, 0x0102, 'a', 0);
                            SendMessage(control, 0x0102, 's', 0);
                            SendMessage(control, 0x0102, 'c', 0);
                            SendMessage(control, 0x0102, (int)Keys.Enter, 0);
                            ShowWindow(window, 9);
                            SetForegroundWindow(window);
                            SendKeys.SendWait(new string(new char[] { 'a', 's', 'c', (char)Keys.Enter }));
                        }
                    }
                }
            }
        }
    }

    SendMessage 这个函数是用来发送消息的,第一个参数是句柄,第二个参数是消息类型,第三个和第四个参数取决于你要发送的是什么消息。模拟键盘需要发送WM_CHAR消息,也就是0x0102。发送这个消息时,第四个参数没有用,第一个参数必须是相关控件的句柄而不是窗口句柄,除非你确定对方是在主窗口中响应你要发送的这个输入消息的,否则需要使用实际处理输入的那个控件的句柄。上面的示例中消息被发送到记事本(notepad)的编辑控件(Edit)中。因为SendMessage直接发送消息给消息的接收方,所以不需要激活窗口和控件,但你必须知道接收方的准确句柄。当然也不是所有输入都是WM_CHAR,快捷键、组合键等等需要另行处理。

    对于不知道准确句柄的情况怎么处理?此时你需要使用SendInput函数,不过一般不直接使用这个函数,在c++中使用keybd_event,在c#中使用System.Windows.Forms.SendKeys.SendWait(非WinForm程序需要添加相应的引用),c++和c#中的这些函数本身是对Windows API SendInput的封装,而SendInput最终又会被转换为一系列的WM_KEYDOWN、WM_CHAR、WM_KEYUP消息。这里有两点需要注意,第一,窗口必须激活,通过调用ShowWindow和SetForegroundWindow函数来实现。SendInput不是向所有窗口广播,而是只发送给活动窗口中的有焦点控件,虽然MSDN中没提到具体原理,不过我猜测应该是通过GetActiveWindow和GetFocus来确定目标句柄的第二,由于处理输入的是那个有焦点的控件,因此除非你能准确控制焦点,否则就不能确定到底是谁将处理你发送的消息了,这是这种方法最大的一个缺陷,不过你直接使用键盘输入也是同样的问题。

    这里还有两个例外情况。第一,只有在MFC和WinForm程序中才有控件句柄,WPF和UWP程序是以DirectX为基础的程序,只有一个窗口句柄,内部的控件没有句柄,所以如果使用SendMessage的话只能发送消息给窗口,至于谁会处理收到的消息你无法指定和控制。第二,对于使用了DirectInput技术(也就是低延迟响应技术)的程序来说以上方法都无效,需要发送WM_INPUT消息才可以,一般这种技术用在DirectX游戏和高性能UWP程序中。

    之前诸多回复中使用了WM_SETTEXT(0x000C)这个消息,这是不对的,关于这个消息请参看这里https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-settext。对于MFC程序或WinForm程序来说,如果SendMessage中的句柄是文本编辑控件的句柄,则这个消息会让文本编辑控件改变其内容。如果那个句柄是按钮则改变按钮上的文本。如果是窗口则改变窗口标题栏上的文本。等等。也就是说这个消息的用途取决于你提供的是什么句柄,不过不管你提供什么句柄它也没有模拟键盘输入这样一个功能。

    更多内容请参考:https://docs.microsoft.com/en-us/windows/win32/inputdev/keyboard-input

    补充说明一点:我上面的示例使用的是WM_CHAR消息,但不保证你用这个消息也管用,如果不行你需要尝试WM_KEYDOWN、WM_KEYUP等等其它消息。SendMessage的问题就在于你必须找到正确的句柄,而且不得不尝试各种消息,除非你知道对方要处理那种消息。
    • 已编辑 [-] 2020年2月22日 21:34
    • 已标记为答案 占占wyz 2020年2月23日 3:11
    2020年2月22日 21:12

全部回复

  • 如果是winform可以使用

    sendkeys.sendwait

    也可使用其他win32api,具体参考可以看MSDN

    keybd_event

    postmessage

    sendmessage



    2020年2月20日 14:49
  • Hi

    我尝试了以下代码能够把消息从控制台发送到文本文档中,你可以看看是否对你有用。

        class Program
        {
    
            [DllImport("user32.dll", EntryPoint = "FindWindowEx")]
            public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
            [DllImport("User32.dll")]
            public static extern int SendMessage(IntPtr hWnd, int uMsg, int wParam, string lParam);
            private const int WM_SETTEXT = 0x000C;
            static void Main(string[] args)
            {
                Process[] notepads = Process.GetProcessesByName("notepad");
                if (notepads.Length == 0) return;
                if (notepads[0] != null)
                {
                    IntPtr child = FindWindowEx(notepads[0].MainWindowHandle, new IntPtr(0), "Edit", null);
                    SendMessage(child, 0x000C, 0, "test");
                }
            }
        }

    Best Regards,

    Jack


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    2020年2月21日 6:48
    版主
  • 您好,感谢您的帮助,能否提供一下向应用程序通过SendMessage方法发送“ASC回车”这四个字符的代码。比如:

    SendMessage(pWnd, param1, param2, param3);//发送字母A
    SendMessage(pWnd, param1, param2, param3);//发送字母S
    SendMessage(pWnd, param1, param2, param3);//发送字母C
    SendMessage(pWnd, param1, param2, param3);//发送Enter键

    我放到我的代码中看应用程序能否接收成功。SendMessage方法的具体用法您是看MSDN上的吗?我看MSDN上的说明好复杂,而且没有找到SendMessage方法各个参数具体代表什么意思以及示例代码。

    2020年2月21日 12:04
  • 您好,我又单独试了一下您给的代码,下面这段代码是起作用的:

    SendMessage(child, 0x000C, 0, "test");

    这是代码执行之前的窗口标题:

    这是代码执行之后的标题:

    这说明SendMessage是可以对窗口起作用的,我觉得是参数传得有问题。能否提供一下上次给您的回复说的代码,谢谢。

    2020年2月22日 2:33
  • 参数说明:

    [DllImport("user32.dll", EntryPoint = "SendMessage")]
            public static extern int SendMessage(
                IntPtr hWnd,   // Windows窗体句柄
                int Msg,    // 要发送的消息(发送的方式,如0X0000C表示清楚屏幕)
                int wParam, // 发送的文本内容(双字节)
                int lParam // 发送的文本内容
            );
    

    可以参照:https://docs.microsoft.com/zh-cn/windows/win32/inputdev/virtual-key-codes?redirectedfrom=MSDN

    SendMessage(child,0x000C,0,"A");//发送字母A
    SendMessage(child,0x000C, 0, Environment.NewLine);//发送Enter键


    Reproduce your quesions with ScreenToGif is your choice. 
    For IIS: IIS Forum
    For WebSite of .NET: ASP.NET Forum
    For others: StackExchange
    For spam-sender or forum urgent issues, Send your Email at:  forumsfeedback@microsoft.com

    2020年2月22日 8:09
    版主
  • 您好,刚试了您给的代码,作用是修改窗口左上角的标题。我想实现通过代码在特定窗口在键盘上按下字母键的效果。可能我没有表达清楚,不好意思。
    2020年2月22日 12:12
  • 这样的效果?

        class Program
        {
            [DllImport("user32.dll")]
            public static extern int SendMessage(IntPtr hwnd, int wMsg, IntPtr wParam, string lParam);
    
            static void Main(string[] args)
            { 
    
                Console.WriteLine("INPUT SOME TEXT:");
                LOOP();
                Console.ReadKey();
            }
            static void LOOP()
            {
                bool B=true;
                while(B)
                {
                    var TEXT = Console.ReadLine();
                    //提前输入好了.
                    SendMessage(new IntPtr(Convert.ToInt32("0011081C", 16)), 0x0C, IntPtr.Zero, TEXT);
                    Console.WriteLine("DONE...");
                    Console.WriteLine("CONTINUE? Y/N");
                    var i = Console.ReadKey();
                    Console.WriteLine();
                    if (i.Key != ConsoleKey.Y)
                        break;
                    else
                        Console.WriteLine("INPUT SOME TEXT:");
                   
                }
            }
        }

    2020年2月22日 16:47
  • 先把代码贴出来

    using System;
    using System.Diagnostics;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;
    namespace ConsoleApp1
    {
        class Program
        {
            [DllImport("USER32.DLL")]
            private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
            [DllImport("USER32.DLL")]
            private static extern bool SetForegroundWindow(IntPtr hWnd);
            [DllImport("user32.dll")]
            private static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
            [DllImport("user32.dll", CharSet = CharSet.Auto)]
            private static extern int SendMessage(IntPtr hWnd, int uMsg, uint wParam, uint lParam);
            static void Main(string[] args)
            {
                var notepads = Process.GetProcessesByName("notepad");
                if (notepads != null && notepads.Length > 0)
                {
                    var window = notepads[0].MainWindowHandle;
                    if (window != null)
                    {
                        var control = FindWindowEx(window, IntPtr.Zero, "Edit", null);
                        if (control != null)
                        {
                            SendMessage(control, 0x0102, 'a', 0);
                            SendMessage(control, 0x0102, 's', 0);
                            SendMessage(control, 0x0102, 'c', 0);
                            SendMessage(control, 0x0102, (int)Keys.Enter, 0);
                            ShowWindow(window, 9);
                            SetForegroundWindow(window);
                            SendKeys.SendWait(new string(new char[] { 'a', 's', 'c', (char)Keys.Enter }));
                        }
                    }
                }
            }
        }
    }

    SendMessage 这个函数是用来发送消息的,第一个参数是句柄,第二个参数是消息类型,第三个和第四个参数取决于你要发送的是什么消息。模拟键盘需要发送WM_CHAR消息,也就是0x0102。发送这个消息时,第四个参数没有用,第一个参数必须是相关控件的句柄而不是窗口句柄,除非你确定对方是在主窗口中响应你要发送的这个输入消息的,否则需要使用实际处理输入的那个控件的句柄。上面的示例中消息被发送到记事本(notepad)的编辑控件(Edit)中。因为SendMessage直接发送消息给消息的接收方,所以不需要激活窗口和控件,但你必须知道接收方的准确句柄。当然也不是所有输入都是WM_CHAR,快捷键、组合键等等需要另行处理。

    对于不知道准确句柄的情况怎么处理?此时你需要使用SendInput函数,不过一般不直接使用这个函数,在c++中使用keybd_event,在c#中使用System.Windows.Forms.SendKeys.SendWait(非WinForm程序需要添加相应的引用),c++和c#中的这些函数本身是对Windows API SendInput的封装,而SendInput最终又会被转换为一系列的WM_KEYDOWN、WM_CHAR、WM_KEYUP消息。这里有两点需要注意,第一,窗口必须激活,通过调用ShowWindow和SetForegroundWindow函数来实现。SendInput不是向所有窗口广播,而是只发送给活动窗口中的有焦点控件,虽然MSDN中没提到具体原理,不过我猜测应该是通过GetActiveWindow和GetFocus来确定目标句柄的第二,由于处理输入的是那个有焦点的控件,因此除非你能准确控制焦点,否则就不能确定到底是谁将处理你发送的消息了,这是这种方法最大的一个缺陷,不过你直接使用键盘输入也是同样的问题。

    这里还有两个例外情况。第一,只有在MFC和WinForm程序中才有控件句柄,WPF和UWP程序是以DirectX为基础的程序,只有一个窗口句柄,内部的控件没有句柄,所以如果使用SendMessage的话只能发送消息给窗口,至于谁会处理收到的消息你无法指定和控制。第二,对于使用了DirectInput技术(也就是低延迟响应技术)的程序来说以上方法都无效,需要发送WM_INPUT消息才可以,一般这种技术用在DirectX游戏和高性能UWP程序中。

    之前诸多回复中使用了WM_SETTEXT(0x000C)这个消息,这是不对的,关于这个消息请参看这里https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-settext。对于MFC程序或WinForm程序来说,如果SendMessage中的句柄是文本编辑控件的句柄,则这个消息会让文本编辑控件改变其内容。如果那个句柄是按钮则改变按钮上的文本。如果是窗口则改变窗口标题栏上的文本。等等。也就是说这个消息的用途取决于你提供的是什么句柄,不过不管你提供什么句柄它也没有模拟键盘输入这样一个功能。

    更多内容请参考:https://docs.microsoft.com/en-us/windows/win32/inputdev/keyboard-input

    补充说明一点:我上面的示例使用的是WM_CHAR消息,但不保证你用这个消息也管用,如果不行你需要尝试WM_KEYDOWN、WM_KEYUP等等其它消息。SendMessage的问题就在于你必须找到正确的句柄,而且不得不尝试各种消息,除非你知道对方要处理那种消息。
    • 已编辑 [-] 2020年2月22日 21:34
    • 已标记为答案 占占wyz 2020年2月23日 3:11
    2020年2月22日 21:12
  • 我是想用代码实现这样的效果:给窗口传递字母,右下角同花顺键盘精灵会接收输入的字母,相当于在这个窗口按下字母键。

    2020年2月23日 0:50
  • 我用鼠标点击不论是窗体最上面的标题,还是点击窗体内部某个位置,在键盘按下字母键的时候均能触发窗体的键盘精灵。所以我感觉给窗体发送消息,只需要获取主窗体的句柄就可以了。我试了一下WM_KEYDOWN、WM_KEYUP,还是不行,不知道代码是否有问题,代码如下:

     SendMessage(pWnd, 0x0104, 0x00000046, 0);//模拟键盘按下F键
     SendMessage(pWnd, 0x0105, 0x00000046, 0);//模拟键盘松开F键

    2020年2月23日 1:37
  • 如果是这样可以尝试使用

    wmsg为key_down

    也就是

    public static extern int SendMessage(IntPtr hwnd, int wMsg, IntPtr wParam, string lParam);
    
    sendmessage(句柄,0x0100,发送的按键,null);



    具体按键可以在https://docs.microsoft.com/zh-cn/windows/win32/inputdev/virtual-key-codes查询

    我再上一个回答中的代码,可以直接使用

    SendMessage(WindowHandle, 0x0100, new IntPtr(0x0D),null);//发送回车。是可以出发winfrom键盘按下事件。


    或者

    2020年2月23日 1:45
  • 您好,我试了这段代码是可以的,能够达到我想要的效果

     SendKeys.SendWait(new string(new char[] { 'a', 's', 'c', (char)Keys.Enter }));

    但是,SendMessage还是不行,谢谢您的帮助,基本解决了问题。

    2020年2月23日 3:02
  • 您好,我试了这段代码是可以的,能够达到我想要的效果

     SendKeys.SendWait(new string(new char[] { 'a', 's', 'c', (char)Keys.Enter }));

    但是,SendMessage还是不行,谢谢您的帮助,基本解决了问题。

    之前您给我提供过SendKeys.SendWait方法,当时调试的时候估计窗口没有获取焦点,没有成功。

    2020年2月23日 3:09