C# 应用使用时间统计

439 阅读2分钟

结构示意图

graph TD;
DLL调用,DLLInvoke.cs-->窗口监控,WindowWatcher.cs;
DLL调用,DLLInvoke.cs-->输入监控,InputWatcher.cs;
窗口监控,WindowWatcher.cs-->窗口管理器,IWindowManager.cs;
窗口管理器,IWindowManager.cs-->过滤器,ApplicationFilter.cs;
过滤器,ApplicationFilter.cs-->窗口渲染;

窗口监控:SetwinEventHook()

窗口监控可以监听活动窗口的切换. 核心是通过对系统活动窗口切换事件SetWinEventHook()(文档入口)的监听实现的。

/// <summary>
/// 对windows事件添加钩子函数
/// </summary>
/// <param name="eventMin"></param>
/// <param name="eventMax"></param>
/// <param name="hmodeWinEventProc"></param>
/// <param name="pfnWinEventProc"></param>
/// <param name="idProcess"></param>
/// <param name="idThread"></param>
/// <param name="dwFlags"></param>
/// <returns></returns>
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern IntPtr SetWinEventHook(Int32 eventMin, Int32 eventMax, IntPtr hmodeWinEventProc, WinEventDelegate pfnWinEventProc, Int32 idProcess, Int32 idThread, Int32 dwFlags);

public delegate void WinEventDelegate(IntPtr hWinEventHook, Int32 eventType, IntPtr hwnd, long idObject, long idChlid, Int32 dwEventThread, Int32 dwmsEventTime); 

SetWinEventHook()要求传入要监听的事件常量范围eventMineventMax,idProcess要监听的进程id,idThread线程id. idProcess为0监听当前桌面上的所有的进程.idThread为0监听当前桌面上的所有的线程. 还有对监听事件的回调函数,用C#的托管函数实现.SetWinEventHook()已经定义了自己的回调函数类型,就是上面的WinEventDelegate()(在文档里也有对该函数的定义).把他传到pfnWinEventProc里. 所有事件常量 活动窗口切换的事件常量是0x0003,只要对eventMin,eventMax都传入该值,就行了.

在文档中有说明0x0003事件在切换同一线程中的不同的窗口也会触发此事件. 所以要对切换前后的线程或进程进行判断.

pfnWinEventProc()

pfnWinEventProc()中的几个参数:

  • hWinEventHook: 注册的挂钩函数的句柄
  • eventType: 事件常量
  • hwnd: 生成事件的窗口(切换到前台的窗口)的句柄

输入监控:GetLastInputInfo()

输入监控用来判断系统为繁忙/空闲状态,以决定是否累计使用时间. 输入监控通过GetLastInputInfo()获取最后一次输入的时间来判断系统是否空闲.

/// <summary>
/// 返回上次输入的时间
/// 收到最后一个输入事件时的TickCount(TickCount: 系统运行时间)
/// 实际返回的是最后一个输入事件时的系统运行时间
/// </summary>
/// <param name="lastInputInfo"></param>
/// <returns></returns>
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern int GetLastInputInfo(ref LastInputInfo lastInputInfo);

GetLastInputInfo要求传入一个指针,指针结构如下:

public struct LastInputInfo {
	public int cbSize;
	public uint dwTime;
}

其中cbSize字段被要求设置为结构体的大小(sizeof(LastInputInfo)). GetLastInputInfo文档入口 C#的使用例子:

public DLLInvoke.LastInputInfo LastInputInfo;
LastInputInfo = new DLLInvoke.LastInputInfo();
// Marshal.SizeOf()类似c的sizeof()
LastInputInfo.cbSize = Marshal.SizeOf(LastInputInfo);
DLLInvoke.GetLastInputInfo(ref lastInputInfo)

窗口切换

End

程序的核心主要就是上面两个dll的调用,剩下的就是代码逻辑性稍微有点强,因为窗口切换和系统繁忙的判定对时间顺序的要求比较高. Github地址