看到 yrt888 提出的问题,抽空研究了一下,发现其实并不像网上那些示例写的,计算其实很简单,只要使用 SystemProcessorPerformanceInformation 参数反复调用 NtQuerySystemInformation,然后根据返回的 IdleTime, KernelTime, UserTime 值,分别用后一次调用取得的值减去前一次调用取得的值,得到各增量值,之后一个除法就得到使用率了。这里唯一要注意的就是 KernelTime 成员的值包含了 IdleTime 和核心调用使用时间。另外发现当使用 SystemBasicInformation 调用 NtQuerySystemInformation 来试图获取 cpu 个数时会失败,返回 0xC0000004 错误码,好在还有 GetSystemInfo 这个 api 可以用来查出 cpu 的个数。所以下面的示例中核心的部分只有一个方法,就是 getCpuTimeInfo,其它只是用来显示使用率的 GUI 界面设计。这个示例对系统安装的每个 cpu 分别使用一个自定义容器来显示使用率图像/数据,不过我们通常只有一个 cpu,而且即使有双核,一般的软件也只能使用到其中的一个,不过印象中有些影片编辑软件可以使用多处理器,例如像“小日本”。
示例代码只在 Windows2003+vfp9 上测试,其他环境未测试。
- public oForm
- oForm = newobject("myform")
- oForm.show
- define class myform as form
- top = 0
- left = 0
- height = 200
- width = 100
- showwindow = 2
- showintaskbar = .f.
- caption = "CPU Usage"
- alwaysontop = .t.
- allowoutput = .f.
- maxbutton = .f.
- minbutton = .f.
- name = "form1"
- cpu_nums = 0 && 系统拥有的 cpu 个数
- dimension aCpuInfo[1,4] && 1/2/3/4 - t_idle / t_kernel / t_user / usage
- procedure updateUsage && 获取最新的 cpu 使用率信息
- local array laTemp[1]
- local liIdleTime, liKernelTime, liUserTime, lnUsage, loUsage
- acopy( this.aCpuInfo, m.laTemp )
- this.getCpuTimeInfo()
- for m.ii = 1 to this.cpu_nums
- m.liIdleTime = m.laTemp[m.ii,1]
- m.liKernelTime = m.laTemp[m.ii,2]
- m.liUserTime = m.laTemp[m.ii,3]
- m.lnUsage = ;
- ( this.aCpuInfo[m.ii,1] - m.liIdletime ) / ;
- ( this.aCpuInfo[m.ii,2] + this.aCpuInfo[m.ii,3] ;
- - m.liKernelTime - m.liUserTime )
- this.aCpuInfo[m.ii,4] = ( 1.00 - m.lnUsage ) && * 100
- m.loUsage = evaluate( 'this.cntUsage' + transform(m.ii))
- m.loUsage.value = this.aCpuInfo[m.ii,4]
- endfor
- endproc
- procedure getCpuTimeInfo && 获取最新的 cpu 空闲/核心/用户用时信息
- #define SystemProcessorPerformanceInformation 8
- local liSize, lcBuff, liBase, ii
- m.liSize = 48 * this.cpu_nums
- m.lcBuff = replicate( chr(0), m.liSize )
- if ( 0 != NtQuerySystemInformation( ;
- SystemProcessorPerformanceInformation, @ m.lcBuff, m.liSize, 0 ))
- wait window nowait '获取 cpu 信息失败: ' + transform( m.iresult, '@0' )
- return
- endif
- for m.ii = 1 to this.cpu_nums
- m.liBase = 1 + ( m.ii - 1 ) * 48
- this.aCpuInfo[ m.ii, 1 ] = ;
- this.ctobin_big( substr( m.lcBuff, m.liBase + 0, 8 ))
- this.aCpuInfo[ m.ii, 2 ] = ;
- this.ctobin_big( substr( m.lcBuff, m.liBase + 8, 8 ))
- this.aCpuInfo[ m.ii, 3 ] = ;
- this.ctobin_big( substr( m.lcBuff, m.liBase + 16, 8 ))
- endfor
- endproc
- function ctobin_big( tcstring as string )
- return ;
- ctobin( substr( m.tcstring, 1, 2 ) + 0h0000, 'rs' ) ;
- + ctobin( substr( m.tcstring, 3, 2 ) + 0h0000, 'rs' ) * 0x10000 ;
- + ctobin( substr( m.tcstring, 5, 2 ) + 0h0000, 'rs' ) * 0x100000000 ;
- + ctobin( substr( m.tcstring, 7, 2 ) + 0h0000, 'rs' ) * 0x1000000000000
- endfunc
- procedure init
- declare long GetSystemInfo in win32api ;
- string @ lpsysteminfo
- declare long NtQuerySystemInformation in ntdll ;
- long systeminformationclass, ;
- string @ systeminformation, ;
- long systeminformationlength, ;
- long @ returnlength
- local lcBuff, lcName, loUsage, ii
- *!* 获取本机已安装的 cpu 数量
- m.lcBuff = replicate( chr(0), 0x40 )
- GetSystemInfo( @ m.lcBuff )
- this.cpu_nums = ctobin( substr( m.lcBuff, 21, 4 ), 'rs' )
- dimension this.aCpuInfo[this.cpu_nums, 4]
- *!* 创建更新计时器
- this.newobject( 'tmrUpdate', 'myTimer' )
- *!* cpu 使用率显示对象, 每个 cpu 一个
- for m.ii = 1 to this.cpu_nums
- m.lcName = 'cntUsage' + transform(m.ii)
- this.newobject( m.lcName, 'myUsage' )
- m.loUsage = evaluate( 'this.' + m.lcName )
- with m.loUsage
- .move( 10+(m.ii-1)*(10+.width), 10, .width, this.height-20 )
- .anchor = 5
- .visible = .t.
- endwith
- endfor
- *!* 调整表单宽度以显示所有使用率对象
- this.width = 10 + ( 10 + this.cntUsage1.width ) * this.cpu_nums
- this.getCpuTimeInfo()
- endproc
- enddefine
- define class myTimer as timer
- interval = 500 && 使用率更新间隔( ms )
- name = "timer1"
- procedure timer
- thisform.updateUsage()
- endproc
- enddefine
- define class myUsage as container
- borderwidth = 0
- backstyle = 0
- width = 50
- height = 100
- value = 0
- function value_assign( tnNewVal ) && 更新显示值
- this.lblUsage.caption = transform(round(m.tnNewVal*100,0)) + '%'
- with this
- .cntBar.height = ( .cntFull.height - 2 ) * m.tnNewVal
- .cntBar.top = .cntFull.height + .cntFull.top - .cntBar.height - 1
- .cntBar.backcolor = icase( ;
- m.tnNewVal <= 0.50, rgb(0,255,0), ;
- m.tnNewVal <= 0.75, rgb(255,255,0), rgb(255,0,0))
- endwith
- endfunc
- procedure init
- this.newobject( 'lblUsage', 'label' )
- with this.lblUsage
- .move( 0, 2, this.width, 16 )
- .alignment = 2
- .backstyle = 0
- .anchor = 11
- .visible = .t.
- endwith
- this.newobject( 'cntFull', 'container' )
- with this.cntFull
- .backcolor = rgb(255,255,255)
- .move( 0, 20, this.width, this.height - 20 )
- .anchor = 15
- .visible = .t.
- endwith
- this.newobject( 'cntBar', 'container' )
- with this.cntBar
- .backcolor = rgb(0,255,0)
- .borderwidth = 0
- .move( 1, 0, this.width-2, 0 )
- .visible = .t.
- endwith
- this.value = 0
- endproc
- enddefine
单独做成一个类来使用的示例:
由于示例中要使用 inkey 函数来延时,所以类库中不能在使用 vfp 自己的计时器来定义更新 cpu 使用率数据,否则 inkey 函数将导致 vfp 的计时器暂停运行,所以这里用 api 函数 SetTimer 来生成一个外部的计时器,并将类库的 updateUsage 方法绑定到这个计时器上。但这也会带了一些问题,因为 inkey 函数肯定也会使用 SetTimer 来创建计时器,这样就可能发生冲突,由于只是想示例 NtQuerySystemInformation 函数的用法,所以类库中也没有仔细处理这些问题,如果你要用它且遇到了问题,可能要从下面几个方面来着手解决:
1. 绑定到 _vfp.hwnd 的 WM_TIMER 事件之前,先用 GetWindowLong 获取并保存好 vfp 原来的缺省窗口处理过程。
2. 被绑定的 updateUsage 处理过程中,应该先判断 p3 是否就是我们定义的 TIMER_ID 值,如果是才执行其中的更新代码,否则调用第一步中保存的 _vfp 缺省处理过程。
3. 定义不同的 TIMER_ID 试试,例如: 0x12345
4. 如果你的程序中使用这个类库的代码段内没有使用 inkey 函数,就恢复使用 vfp 的计时器事件来调用 updateUsage 方法。
- clear
- oo = newobject( 'cpuUsage' )
- for m.ii = 1 to 50
- inkey(0.5) && 这时候你应该干点别的什么,免得 cpu 一只闲着而总是显示 0
- ? oo.GetUsage(1)
- endfor
- oo = null
- * ----------------------------------------------------
- #define WM_TIMER 0x0113
- #define TIMER_ID 200
- define class cpuUsage as session
- cpu_nums = 0 && 系统拥有的 cpu 个数
- dimension aCpuInfo[1,4] && 1/2/3/4 - t_idle / t_kernel / t_user / usage
- procedure getUsage
- lparameters tiCpuNo && 要获取使用率的 cpu 编号
- if ( pcount() < 1 ) or ( 'N' != vartype( m.tiCpuNo ))
- m.tiCpuNo = 1
- endif
- m.tiCpuNo = max( 1, min( this.cpu_nums, m.tiCpuNo ))
- return this.aCpuInfo[ m.tiCpuNo, 4 ]
- endproc
- procedure updateUsage && 获取最新的 cpu 使用率信息
- lparameters p1, p2, p3, p4
- local array laTemp[1]
- local liIdleTime, liKernelTime, liUserTime, lnUsage, loUsage, ii
- acopy( this.aCpuInfo, m.laTemp )
- this.getCpuTimeInfo()
- for m.ii = 1 to this.cpu_nums
- m.liIdleTime = m.laTemp[m.ii,1]
- m.liKernelTime = m.laTemp[m.ii,2]
- m.liUserTime = m.laTemp[m.ii,3]
- m.lnUsage = ;
- ( this.aCpuInfo[m.ii,1] - m.liIdletime ) / ;
- ( this.aCpuInfo[m.ii,2] + this.aCpuInfo[m.ii,3] ;
- - m.liKernelTime - m.liUserTime )
- this.aCpuInfo[m.ii,4] = round(( 1.00 - m.lnUsage ) * 100, 0 )
- endfor
- endproc
- procedure getCpuTimeInfo && 获取最新的 cpu 空闲/核心/用户用时信息
- #define SystemProcessorPerformanceInformation 8
- local liSize, lcBuff, liBase, ii
- m.liSize = 48 * this.cpu_nums
- m.lcBuff = replicate( chr(0), m.liSize )
- if ( 0 != NtQuerySystemInformation( ;
- SystemProcessorPerformanceInformation, @ m.lcBuff, m.liSize, 0 ))
- wait window nowait '获取 cpu 信息失败: ' + transform( m.iresult, '@0' )
- return
- endif
- for m.ii = 1 to this.cpu_nums
- m.liBase = 1 + ( m.ii - 1 ) * 48
- this.aCpuInfo[ m.ii, 1 ] = ;
- this.ctobin_big( substr( m.lcBuff, m.liBase + 0, 8 ))
- this.aCpuInfo[ m.ii, 2 ] = ;
- this.ctobin_big( substr( m.lcBuff, m.liBase + 8, 8 ))
- this.aCpuInfo[ m.ii, 3 ] = ;
- this.ctobin_big( substr( m.lcBuff, m.liBase + 16, 8 ))
- endfor
- endproc
- function ctobin_big( tcstring as string )
- return ;
- ctobin( substr( m.tcstring, 1, 2 ) + 0h0000, 'rs' ) ;
- + ctobin( substr( m.tcstring, 3, 2 ) + 0h0000, 'rs' ) * 0x10000 ;
- + ctobin( substr( m.tcstring, 5, 2 ) + 0h0000, 'rs' ) * 0x100000000 ;
- + ctobin( substr( m.tcstring, 7, 2 ) + 0h0000, 'rs' ) * 0x1000000000000
- endfunc
- procedure init
- declare long GetSystemInfo in win32api ;
- string @ lpSystemInfo
- declare long NtQuerySystemInformation in ntdll ;
- long SystemInformationClass, ;
- string @ SystemInformation, ;
- long SystemInformationLength, ;
- long @ ReturnLength
- declare long SetTimer in win32api ;
- long hWnd, long nIDEvent, long uElapse, long lpTimerFunc
- declare long KillTimer in win32api ;
- long hWnd, long uIDEvent
- local lcBuff
- *!* 获取本机已安装的 cpu 数量
- m.lcBuff = replicate( chr(0), 0x40 )
- GetSystemInfo( @ m.lcBuff )
- this.cpu_nums = ctobin( substr( m.lcBuff, 21, 4 ), 'rs' )
- dimension this.aCpuInfo[this.cpu_nums, 4]
- this.aCpuInfo = 0
- SetTimer( _vfp.hwnd, TIMER_ID, 200, 0 )
- bindevent( _vfp.hwnd, WM_TIMER, this, 'updateUsage' )
- this.getCpuTimeInfo()
- endproc
- procedure destroy
- unbindevent( _vfp.hwnd, WM_TIMER )
- KillTimer( _vfp.hwnd, TIMER_ID )
- endproc
- enddefine