关于C#调用WinApi实现剪切板操作及模拟键盘Ctrl+V的问题,请大神指点、小神围观

MikeCheers 2022-11-20 11:41:35

问题:我在做一款自动化测试工具(这不是重点),以登录为例,其中,会先从文本中读取一些数据(比如:账号、密码),试图先将账号设置到剪切板,然后模拟鼠标点击账号输入框,再模拟键盘发送Ctrl+V,密码的输入过程也一样。输入账号的过程OK,问题是输入密码的时候,粘贴的数据依然是账号数据。

尝试:
1、在模拟键盘发送Ctrl+V粘贴密码前,先从剪切板读取数据并输出,发现确实是密码数据,但模拟键盘发送Ctrl+V后,文本框中缺还是账号数据。
2、在模拟键盘发送Ctrl+V粘贴密码的位置打断点,在记事本中按Ctrl+V,发现也是正确的密码数据。

很迷惑究竟是哪里出了问题。

核心代码如下:



    public static partial class WinApi
    {
        public static void SendCtrlV(IntPtr hWnd)
        {
            SetForegroundWindow(hWnd);
            System.Threading.Thread.Sleep(10);
            keybd_event(VK.VK_CONTROL, 0, 0x00, 0);  //按下ctrl,在下面释放之前,他的状态一直还是被按下的,不信你试下找个地方按 v
            System.Threading.Thread.Sleep(10);
            keybd_event(VK.VK_V, 0, 0x00, 0);
            System.Threading.Thread.Sleep(10);
            keybd_event(VK.VK_V, 0, 0x02, 0);
            System.Threading.Thread.Sleep(10);
            keybd_event(VK.VK_CONTROL, 0, 0x02, 0);  //释放 ctrl 键
        }

        public static class ClipboardFormat
        {
            // 当然这里还有其他的值,省略了……

            /// <summary>
            /// Unicode text format. Each line ends with a carriage return/linefeed (CR-LF) combination. A null character
            /// signals the end of the data.
            /// </summary>
            public const int CF_UNICODETEXT = 13;
        }

        [DllImport("User32", EntryPoint = "OpenClipboard", ExactSpelling = true, CharSet = CharSet.Unicode)]
        internal static extern bool OpenClipboard(IntPtr hWndNewOwner);

        [DllImport("User32", EntryPoint = "CloseClipboard", ExactSpelling = true, CharSet = CharSet.Unicode)]
        internal static extern bool CloseClipboard();

        [DllImport("User32", EntryPoint = "EmptyClipboard", ExactSpelling = true, CharSet = CharSet.Unicode)]
        internal static extern bool EmptyClipboard();

        [DllImport("User32", EntryPoint = "IsClipboardFormatAvailable", ExactSpelling = true, CharSet = CharSet.Unicode)]
        internal static extern bool IsClipboardFormatAvailable(int format);

        [DllImport("User32", EntryPoint = "GetClipboardData", ExactSpelling = true, CharSet = CharSet.Unicode)]
        internal static extern IntPtr GetClipboardData(int uFormat);

        [DllImport("User32", EntryPoint = "SetClipboardData", ExactSpelling = true, CharSet = CharSet.Unicode)]
        internal static extern IntPtr SetClipboardData(int uFormat, IntPtr hMem);
    }


    public partial class WinCommon
    {
        public static void OpenClipboard(IntPtr owner)
        {
            while (!WinApi.OpenClipboard(owner)) { System.Threading.Thread.Sleep(10); }
        }
        public static void CloseClipboard()
        {
            while (!WinApi.CloseClipboard()) { System.Threading.Thread.Sleep(10); }
        }
        public static void ClearClipboard()
        {
            while (!WinApi.EmptyClipboard()) { System.Threading.Thread.Sleep(10); }
        }

        public static void SetTextToClipboard(string data)
        {
            Trace.WriteLine("WinCommon : SetTextToClipboard : "
                + WinApi.SetClipboardData(WinApi.ClipboardFormat.CF_UNICODETEXT, Marshal.StringToHGlobalUni(data)).ToString());
        }

        public static string GetTextFromClipboard()
        {
            string value = string.Empty;
            if (WinApi.IsClipboardFormatAvailable(WinApi.ClipboardFormat.CF_UNICODETEXT))
            {
                IntPtr ptr = WinApi.GetClipboardData(WinApi.ClipboardFormat.CF_UNICODETEXT);
                if (ptr != IntPtr.Zero)
                {
                    value = Marshal.PtrToStringUni(ptr);
                }
            }
            return value;
        }
    }

// 以下是业务处理部分的代码片断
switch (it)
{
    case InputTypes.Account:
        if (null != LogMessage) LogMessage($"输入账号'{accounts[currentAccIndex].Id}'。");
        MouseDoubleClick(new Point(30, points.FirstEntity.Y));
        Thread.Sleep(1000);
        WinCommon.OpenClipboard(IntPtr.Zero);
        WinCommon.ClearClipboard();
        WinCommon.SetTextToClipboard(accounts[currentAccIndex].Id);
        WinCommon.CloseClipboard();
        WinCommon.OpenClipboard(winRenderHandle);
        Trace.WriteLine("LoginProcessor:Process:InputTypes.Account: " + WinCommon.GetTextFromClipboard());
        WinCommon.CloseClipboard();
        Thread.Sleep(1000);
        WinApi.SendCtrlV(winRenderHandle);  // 这里粘贴的数据还OK
        Thread.Sleep(1000);
        //MouseClick(new Point(30, points.FirstEntity.Y - 100));
        MouseClick(points.FirstEntity);
        //Thread.Sleep(1500);
        it = InputTypes.Password;
        waitForPageChange = true;
        break;
    case InputTypes.Password:
        if (null != LogMessage) LogMessage($"输入密码'{accounts[currentAccIndex].Pswd}'。");
        MouseDoubleClick(new Point(30, points.FirstEntity.Y));
        Thread.Sleep(1000);
        WinCommon.OpenClipboard(IntPtr.Zero);
        WinCommon.ClearClipboard();
        WinCommon.SetTextToClipboard(accounts[currentAccIndex].Pswd);
        WinCommon.CloseClipboard();
        WinCommon.OpenClipboard(winRenderHandle);
        Trace.WriteLine("LoginProcessor:Process:InputTypes.Password: " + WinCommon.GetTextFromClipboard());
        WinCommon.CloseClipboard();
        Thread.Sleep(1000);
        WinApi.SendCtrlV(winRenderHandle);  // 就是这里粘贴的数据不符合预期
        Thread.Sleep(1000);
        //MouseClick(new Point(30, points.FirstEntity.Y - 100));
        MouseClick(points.FirstEntity);
        //Thread.Sleep(1500);
        it = InputTypes.Finished;
        waitForPageChange = true;
        break;
    default:
        break;
}

从直接使用C#带的Clipboard到直接调用WinApi,中间还碰到STA的问题,总归折腾了一大圈,还是没能解决。实在是没想法了,请大神施以援手了。

...全文
517 8 打赏 收藏 转发到动态 举报
写回复
用AI写文章
8 条回复
切换为时间正序
请发表友善的回复…
发表回复
泡泡龙 2023-01-17
  • 打赏
  • 举报
回复

WM_CLEAR
WM_COPY
WM_CUT
WM_PASTE

试试发消息

xuzuning 2022-11-21
  • 打赏
  • 举报
回复

你往哪里粘贴,这是重点

MikeCheers 2022-11-21
  • 举报
回复
@xuzuning 可以看作是两个应用,一个是我写的应用(A),一个是客户应用(B),A获取B的Handle,对其进行操作,比如模拟鼠标、键盘操作。文中提到的问题其实就是模拟用户在B执行登录的过程。
wanghui0380 2022-11-21
  • 打赏
  • 举报
回复

看不出啥问题。不过通常来说操作 Clipboard是winform天生具备的能力,所以你可以直接写
Clipboard.SetText();

Clipboard类本身就是System.Windows.Forms 自己带的工具类

MikeCheers 2022-11-21
  • 举报
回复
@wanghui0380 之前是有尝试过直接用System.Windows.Forms.Clipboard类来进行操作,文尾有提到,就是因为没有实现,才转向直接使用WinAPI的。
MikeCheers 2022-11-20
  • 打赏
  • 举报
回复

顶一下,在线等。。。

赵4老师 2022-11-20
  • 打赏
  • 举报
回复 1

可能密码框故意限制了粘贴密码

MikeCheers 2022-11-20
  • 举报
回复 1
@赵4老师 那倒也没有,因为我手动操作,也是可以粘贴的。

110,534

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧