优雅地使用Ruby调用windows底层API实现随心所欲的桌面性能测试(ruby调用GetProcessMemoryInfo)

2017年10月25日 0 条评论 1.43k 次阅读 0 人点赞

 

今天跟大家分享windows API的一些妙用。

引出

为什么要研究windows API?

 

功能测试中需要调用底层API进行窗口获取、插入钩子、挪动鼠标等操作(详见Ruby自动化脚本)

在性能测试中,我们获取内存时将调用cmd或者powershell进行内存的获取。众所周知,ruby调用外部cmd (powershell) 时,内存和CPU消耗极高,效率也非常慢,导致自动化进行效率测试时,测试数据不准确、机器性能变慢导致控件失效等情况。

 

实测:

调用cmd获得内存耗时:0.19秒

调用底层API获得内存耗时:0.001秒

实测证明,API的效率是cmd效率的200倍!

 

开始

以下就给大家介绍一下如何优雅地使用ruby调用windows API

举个例子:

我们现在想查找窗口句柄,通过查询windows API:

API参考链接:

https://msdn.microsoft.com/en-us/library/windows/desktop/ms633499(v=vs.85).aspx

在这个页面详细列出了此API的用法

Ruby中怎么用呢:

第一步,引用WIN32API

require 'Win32API'

第二步,映射API,其实就是new一个api对象

find_window = Win32API.new('user32', 'FindWindow', %w(L P), 'L')

解释一下,%w(L P)其中的L指的是API入参为数字,P指的是入参是指针,在ruby中可以传入string,入参是2个参数

第三步,调用api对象的call方法

find_window.call(0, 窗口名称)


 

好,以下详细说一下如何用API优雅、快速、不影响主进程地获取峰值工作设置(内存)、工作设置(内存)、提交大小这三个主要的性能指标。

通过查询windows API,我们知道GetProcessMemoryInfo 这个API可以很好地获得我们所需的内容

参考文档:

https://msdn.microsoft.com/en-us/library/windows/desktop/ms683219(v=vs.85).aspx

 

当然巨硬公司本身也提供了C++的demo代码,链接器加入Psapi.lib可以直接编译exe运行:

C++代码见:

https://msdn.microsoft.com/en-us/library/windows/desktop/ms682050(v=vs.85).aspx

 

GetProcessMemoryInfo的输入参数为3个,第一个值为processID,也就是线程的句柄;第二个值是输出的参数,也就是我们想要的结果,它是一个struct数据结构,具体为:

第三个参数为_PROCESS_MEMORY_COUNTERS 的size大小。 那么我们的重点就是构造struct数据结构和processID。拆解之后我们的步骤为:第一步, 按照窗口名称查找窗口句柄;第二步, 按照窗口句柄查找processID;第三步, 按照processID的pid得到线程句柄hProcess第四步, 从可用内存中查找线程对应的内存地址;第五步, 调用GetProcessMemoryInfo(API方法)获得内存;最后, 关闭当前线程 具体代码见下:下面的代码可以直接粘贴运行,在GAEA.exe打开的情况下输出结果为:

以下代码为本人手写亲测可用,如有问题可直接留言。

#===================代码开始
require 'Win32API'
 
# 变量定义
a = Time.now
PROCESS_QUERY_INFORMATION = 1024
PROCESS_ALL_ACCESS = 0x1F0FFF
WM_USER = 0x400
TB_BUTTONCOUNT = (WM_USER+24)
TB_GETBUTTON = (WM_USER+23)
MEM_COMMIT = 0x1000
MEM_RELEASE = 0x8000
PAGE_READWRITE = 0x4
TBSTATE_HIDDE = 0x8
 
def get_窗口句柄_按窗口名(窗口名称 = '模型转换(版本号:1.0')
  find_window = Win32API.new('user32', 'FindWindow', %w(L P), 'L')
  return find_window.call(0, 窗口名称)
end
 
# API定义
 
FindWindow = Win32API.new('user32', 'FindWindow', 'PP', 'L')
FindWindowEx = Win32API.new('user32', 'FindWindowEx', 'LLPP', 'L')
SendMessage = Win32API.new('user32', 'SendMessage', 'LILL', 'L')
GetWindowThreadProcessId = Win32API.new('user32',
  'GetWindowThreadProcessId', 'LP', 'L')
OpenProcess = Win32API.new('kernel32', 'OpenProcess', 'LIL', 'L')
GetModuleFileNameEx = Win32API.new('psapi', 'GetModuleFileNameEx',
  'LLPL', 'L')
CloseHandle = Win32API.new('kernel32', 'CloseHandle', 'L', 'L')
ReadProcessMemory = Win32API.new('kernel32', 'ReadProcessMemory',
  'LLPLP', 'L')
VirtualAllocEx = Win32API.new('kernel32', 'VirtualAllocEx', 'LLLLL',
  'L')
VirtualFreeEx = Win32API.new('kernel32', 'VirtualFreeEx', 'LLL', 'L')
 
GetProcessMemoryInfo = Win32API.new('psapi', 'GetProcessMemoryInfo', 'LPL', 'L')
 
 
def getProcessNameFromhWnd(dwhWnd)
  # 此方法从句柄得到线程ID
  ppid = 0.chr * 4
  GetWindowThreadProcessId.call(dwhWnd,ppid)
  pid = ppid.unpack('L').first
  hProcess = OpenProcess.call(PROCESS_ALL_ACCESS, 0, pid)
  buffer = 0.chr * 1024
  cbLen = GetModuleFileNameEx.call(hProcess, 0, buffer, buffer.length)
  CloseHandle.call(hProcess)
  buffer.strip
end
 
# 第一步, 按照窗口名称查找窗口句柄 返回值 => hWindow
hWindow = get_窗口句柄_按窗口名 'GAEA'
 
# 第二步, 按照窗口句柄查找processID 返回值 => ppid 通过ppid(struc数据结构)得到pid
 
ppid = 0.chr * 4
GetWindowThreadProcessId.call(hWindow, ppid)
pid = ppid.unpack('L').first
 
# 第三步, 按照processIDpid得到线程句柄hProcess 返回值 => hProcess
# 此操作需要打开此线程, 在最后将关闭此线程
hProcess = OpenProcess.call(PROCESS_ALL_ACCESS, 0, pid)
 
# 第四步, 从可用内存中查找线程对应的内存地址 返回值 => addr
addr = VirtualAllocEx.call(hProcess, 0, 24, MEM_COMMIT, PAGE_READWRITE)
 
# 返回值数据结构定义
tray = 0.chr * 24
tb = 0.chr * 20
cbRead = 0.chr * 4
data = 0.chr * 4
buffer = "\0" * 40
 
# 最后一步, 调用GetProcessMemoryInfo(API方法)获得内存 返回值 => buffer
# buffer PROCESS_MEMORY_COUNTERS 类型的数据结构, 具体参考
# https://msdn.microsoft.com/en-us/library/windows/desktop/ms684877(v=vs.85).aspx
 
GetProcessMemoryInfo.call(hProcess, buffer, 40)
 
# 峰值工作设置(内存)
p '峰值工作设置(内存):'
p buffer.unpack('L10')[2] / 1024
 
# 工作设置(内存)
p '工作设置(内存)'
p  buffer.unpack('L10')[3] / 1024
 
# 提交大小
p '提交大小'
p buffer.unpack('L10')[8] / 1024
 
# 最后, 关闭当前线程
CloseHandle.call(hProcess)
#===================代码结束

 

从下图可以看出,获取的值与从任务管理器中获取的是一致的,耗时仅需要0.001秒(1毫秒),而且调用纯C的windowsAPI,效率非常高。

参考文献:

  1. MSDN Process Status API

https://msdn.microsoft.com/en-us/library/windows/desktop/ms684884(v=vs.85).aspx

  1. rb

https://github.com/cstrahan/argss/blob/master/bin/Test/Tests%20scripts/memoryleak.rb

  1. Windows : Retrive Quick Launch toolbar items

https://www.ruby-forum.com/topic/182211

  1. STACKOVERFLOW

https://stackoverflow.com/

 

 

 

今从晚向

这个人太懒什么东西都没留下

文章评论(0)

你必须 登录 才能发表评论