python使用win32库模拟拖拽文件发给指定窗口

python 专栏收录该内容
1 篇文章 0 订阅

    最近要用python模拟人的操作给窗口发送拖拽文件的消息,网上搜了一大圈也没搜到现成可用的代码。幸好以前做过vc开发,熟悉点win32编程,于是装上vs和msdn,从消息WM_DROPFILES查起,慢慢得实现了这个功能。

    WM_DROPFILES是向win32窗口拖拽一个文件松开鼠标左键后会触发发送给窗口的消息,前提是目标窗口是支持拖拽消息的响应,在win32中是通过窗口样式:WS_EX_ACCEPTFILES或者调用API:DragAcceptFiles(HWND hWnd,BOOL fAccept)设置过的窗口才会正常响应拖拽消息。

    官方定义发送消息WM_DROPFILES方式如下:

PostMessage(

    (HWND) hWndControl,   // 这里是目标窗口的句柄,可以通过FindWindowEX或者FindWindow函数获取

    (UINT) WM_DROPFILES,  // 这里是消息ID,实际就是个数值0x0233

    (WPARAM) wParam,      // 重要的是这个参数,是指定消息中的一些必要信息和被拖拽文件路径的结构体

    (LPARAM) lParam       // 这里设置为0

);
//虽然官方是用PostMessage,但是我实际测试使用SendMessage也是可以的。

   根据文档确定wParam是一个指针,指向DROPFILES结构体紧跟上文件路径列表的一段数据,其中文路径列表以‘\0’间隔,列表最终以'\0\0'结束。也就是:DROPFILESD:\test.txt\0\0。需要注意的是这个内存地址得是目标进程地址空间中的内存地址,不能是你python程序中的内存地址。win32程序每个程序都有自己的内存地址空间。所以在发送之前还得先通过api在目标进程中申请一段内存地址存放这个消息体,然后把这个内存地址传入wParam参数。

typedef struct _DROPFILES {
  DWORD pFiles;  //消息体重文件路开始位置偏移量
  POINT pt;      //消息触发的坐标点。
  BOOL  fNC;     //坐标点是基于全屏还是基于对应窗口。TRUE:基于全屏。FALSE:基于目标窗口
  BOOL  fWide;   //好像是指定文件路径中是否包含unicode字符。我设置为TRUE和FALSE都可以。
} DROPFILES, *LPDROPFILES;

    

接下来就是实现过程,先构建消息体,然后在目标进程申请内存区域,再把消息体拷贝进目标进程申请的内存区域,然后调用python的win32库里的SendMessage或者PostMessage来向对应窗口发送这个消息就行了。代码如下:

import ctypes
import struct
import logging
from ctypes.wintypes import *
import win32clipboard
import win32con
import win32gui
from win32con import PAGE_READWRITE, MEM_COMMIT, PROCESS_ALL_ACCESS
from ctypes.wintypes import FILETIME

#下面这些API在pywin32库中没有,需要通过windll间接获取函数,类似于win32开发中动态使用dll中函数的办法
GetWindowThreadProcessId = ctypes.windll.user32.GetWindowThreadProcessId
VirtualAllocEx = ctypes.windll.kernel32.VirtualAllocEx
VirtualFreeEx = ctypes.windll.kernel32.VirtualFreeEx
OpenProcess = ctypes.windll.kernel32.OpenProcess
WriteProcessMemory = ctypes.windll.kernel32.WriteProcessMemory
ReadProcessMemory = ctypes.windll.kernel32.


def dragFileToWnd(file, hwnd):
    """
     dragFileToWnd(file, hwnd)
     file:文件绝对路径或者相对路径。
     hwnd:窗口的句柄,可以通过win32gui.FindWindowEx 精确查找获取。返回值就是这个hwnd
     """

    filepath = bytes(file + "\0\0", encoding="GBK")  # 文件路径结尾必须紧跟两个0,为了支持中文编码用了GBK

# 用ctypes的struct来模拟DROPFILES结构体。
#其中第一个参数0x14是指消息体内存区域中的文件路径是从第几个字节开始,
#也就是对应的DROPFILES的pFiles参数,后面参数类似原理,
#我把鼠标松开的点位定义在了窗口的(10,10)位置,所以是0x0A,0x0A
    DropFilesInfo = struct.pack("iiiii" + str(len(filepath)) + "s",*[0x14, 0x0A, 0x0A, 00, 00, filepath])  

# 从结构体创建缓冲区,相当于获取结构体的地址,
#然后把整个结构体看成一段内存区域,也可以用ctypes.addressof(DropFilesInfo)直接获取地址值
    s_buff = ctypes.create_string_buffer(DropFilesInfo)  

    pid = ctypes.c_uint(0)
    GetWindowThreadProcessId(hwnd, ctypes.addressof(pid)) #获取进程ID,以便后续在进程地址空间中申请内存。

    print("pid:%x" % pid.value)

    hProcHnd = OpenProcess(PROCESS_ALL_ACCESS, False, pid)#打开进程,获取进程句柄。
    print("open Process:%x" % hProcHnd)

    pMem = VirtualAllocEx(hProcHnd, 0, len(DropFilesInfo), MEM_COMMIT,PAGE_READWRITE)  # 在目标进程中申请一段大小能容纳DROPFILES和文件路径的内存区域

    copied = ctypes.c_int(0)
    WriteProcessMemory(hProcHnd, pMem, s_buff, len(DropFilesInfo), ctypes.addressof(copied))  # 将消息体写入
    print("copied:", copied.value)
    win32gui.SendMessage(hwnd, win32con.WM_DROPFILES, pMem)  # 模拟发送拖拽消息给对应进程。
    VirtualFreeEx(hProcHnd,pMem,0,win32con.MEM_RELEASE) #用完后要释放对应内存区域,防止内存泄漏。




#调用测试,模拟将d盘下test.txt 文件拖入打开着的notepad记事本窗口。当路径指定的文件不存在,notepad会弹出新建的提示。
file = "D:\\test.txt"  

hwnd1 = win32gui.FindWindow("Notepad2U", "1.txt - Notepad2-mod (管理员)") #

dragFileToWnd(file, hwnd1)

    补充FindWindow函数说明:

    HWND FindWindow( LPCTSTR lpClassName, LPCTSTR lpWindowName );

    这个函数用于通过窗口类名和窗口标题查找到指定的窗口句柄。可以通过spy++或者spylite24来捕获。

 

  • 3
    点赞
  • 0
    评论
  • 8
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页

打赏作者

kaiixing

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值