斌哥说
大家好,我是斌哥。
一说起造轮子,相信大家和我一样会感觉毛骨悚然。
就拿一个WindowSDK提供的MAKELONG宏来说。
它的定义是这样的,在C中:
#define MAKELONG(a, b) ((LONG)(((WORD)(a)) | ((DWORD)((WORD)(b))) << 16))
看着就很头大,没关系,下面斌哥就来说说怎么通过WindowsAPI来造出我们Python上能用的“轮子”。
来看看轮子怎么造,大致分为这样几步:
定义功能 ->> 调用windowsAPI ->> 实现功能
分别实现3个功能:
1、取得屏幕某个像素点的颜色2、给窗口发送点击消息3、获得窗口大小实际上,python给我们提供了和C交互的模块ctypes,在这个模块中我们可以快速的C语言结构的dll打交道。
我们都知道,api被封装在dll中,在windows系统上,所有的用户过程,其实都是调用了api在运行。
常用的几个装有API的dll文件也被ctypes写成类,它们分别是user32.dll和kernel32。
调用方式:
import ctypes
ctypes.windll.user32.user32内提供的函数
ctypes.windll.kernel32.kernel32内提供的函数
如果是调用其他dll则使用LoadLibrary,例如ctypes.windll.LoadLibrary(r"c:\my.dll")
先来看看第一个需求:取得屏幕某个像素点的颜色
我们要用到的接口是gdi.dll提供的GetPixel,其原型为:
COLORREFGetPixel(HDChdc,intnXPos,intnYPos);
GetPixel原形
在使用GetPixel之前,我们还必须获得当前屏幕设备句柄。
通过GetDC可以获得设备句柄,当窗口句柄参数为NULL时返回当前屏幕设备句柄。
GetDC原形
GetPixel可以获得指定坐标的像素颜色。
成功则返回一个RGB颜色的数字。
HDC hdc是一个设备上下文句柄,nXPos和nYPos对应xy坐标
import ctypes
def getpix_color(x,y):
if(type(x) != int or type(y) != int): #类型判断
return ""
dc = ctypes.windll.user32.GetDC(None)
return hex(ctypes.windll.LoadLibrary("gdi32.dll").GetPixel(dc,x,y))
需求1示例
第二个需求:给窗口发送点击消息。
这需要用到SendMessage或PostMessage,它们的原型是一样的,SendMessage和PostMessage的区别在于:SendMessage发送一个消息给窗口的消息队列,然后等待窗口执行,PostMessage则是发送到消息队列后立即返回,就像TCP和UDP的区别。
SeneMessage原形
我们要做的是:给窗口发送WM_LBUTTONDOWN和WM_LBUTTONUP消息(分别是鼠标左键按下和松开)
lParam参数为需要点击的坐标
因此我们要用到前面的MAKELONG这个宏来把坐标x和y转换成一个长整型,然后再取指针
但是Python并没有类似MAKELONG这样的玩意,怎么办呢?
我们自己造:
代码分析:
再来看看这个宏定义#define MAKELONG(a, b) ((LONG)(((WORD)(a)) | ((DWORD)((WORD)(b))) << 16))
可以看到这段定义先是把参数a转换成WORD类型,然后把b转换成WORD类型,然后再把b转换成DWORD类型,然后 a | b << 16 ,然后再转换成LONG类型
相当于:
a = (WORD)a
b = (WORD)b
b = (DWORD)b
(LONG)(a | b << 16)
Python的ctypes提供了C语言数据类型的基础类。
所有C类型的类都提供了1个value属性
c_数据类型名
例如:c_int c_long c_float
指针类型:ctypes只提供了2种指针类型,c_void_p和c_char_p
不过ctypes提供取地址的操作
无符号:c_u数据类型名 例如c_ulong c_ushort
以上各种类型的定义为:
typedef unsigned short WORD
typedef unsigned long DWORD
执行这样的操作,在Python中我们需要这样做:
import ctypes
def makelong(a,b): #转换成long
return ctypes.c_long(ctypes.c_ushort(a).value | ctypes.c_ulong(ctypes.c_ushort(b).value).value << 16).value
CLICK_LEFT_DOWN = 0x201
CLICK_LEFT_UP = 0x202
def click(hwnd,x,y): #给窗口发送左键点击消息
xy = makelong(x,y)
PostMessage(hwnd,CLICK_LEFT_DOWN,None,xy)
PostMessage(hwnd,CLICK_LEFT_UP,None,xy)
#传入窗口句柄和需要点击的按钮坐标即可执行左键点击
第三个需求:获得窗口大小。
要说明一点,这里的窗口大小是窗口客户区的大小,所以用到的是user32.dll提供的GetClientRect接口。
GetClientRect原形
BOOLGetClientRect(HWNDhWnd,LPRECTlpRect);
获得窗口的客户区大小,这里需要一个窗口句柄和RECT结构体指针。成功返回True,否则False
OK,那么我们在Python中只能自己写一个RECT结构体了
在Python中,结构体都是通过继承ctypes的Structure类来定义的
并且子类必须要有_fields_ 属性,该属性必须是2元素元组的列表,里面包含字段名和字段类型。
RECT结构体定义如下:
typedef struct _RECT {LONGleft;LONGtop;LONGright;LONGbottom;} RECT;
可以看到所有的结构体成员都是long类型。
那么我们在Python中定义结构体则是这样:
import ctypes
class my_Rect(ctypes.Structure):
_fields_ = [("x",ctypes.c_long),\
("y",ctypes.c_long),\
("width",ctypes.c_long),\
("height",ctypes.c_long)\
]
只要结构相同,成员名我们可以自己定义。
用pointer方法取出对象地址
def get_Client_Size(hwnd):
rect = my_Rect()
if(
bool(ctypes.windll.user32.GetClientRect( hwnd,ctypes.pointer(rect) ) )
):
return tuple( (rect.width,rect.height) ) #以元组方式返回宽高
return None
需求3示例
运行结果
我是斌哥,喜欢请点击关注。
斌哥说Python,只专注于Python技术!