一个独立的螺纹类,可通过多种方式监视用户的存在
这是一个简单的类,可监视3个事件:监视器状态(关闭,打开或昏暗),系统的“用户出席”消息,如果您在笔记本电脑上,则盖子状态(打开,关闭)。它在完全自给自足的类中进行操作 - 出现的复杂性是因为这些事件是以WM_POWERBROADCAST消息的形式发送的,并重新考虑接收它的窗口。但是我不想将类限制为图形应用程序,而只是为了完整的演示应用程序而显示了如何以这种方式进行操作。因此,该类创建它自己的完全自定义的隐藏窗口,以避免消息循环阻止表单的消息循环(如果存在)。
该类利用Twinbasic的参数化构造函数来指定新关键字中的可用参数;这是可选的论点,要为哪些事件提出以及在( App.hInstance 99%的时间)下注册的hinstance;如果您省略它,它将使用GetModuleHandleW() ,它将返回与App.hInstance相同的值,而无需创建对winnativeforms的依赖性)。然后,我们获得了TB的最大乐趣之一:Calling CreateThread而无需任何精心制作的黑客攻击,就像在VB6中一样酷。 ThreadProc本身只是调用代码的其余部分。
Sub New(Optional ByVal dwNotifyMask As CPMonEventNotify = CPMEN_ALL, Optional ByVal hInst As LongPtr)
m_hInst = If(hInst = 0, GetModuleHandleW(), hInst)
If dwNotifyMask = CPMEN_ERROR Then Exit Sub
m_Mask = dwNotifyMask
tConfig.hInst = m_hInst
tConfig.Mask = m_Mask
m_hThread = CreateThread(ByVal 0, 0, AddressOf CPMonProc, tConfig, 0, m_idThread)
End Sub
RegisterClassEx和CreateWindowEx仅在一个普通的例程中用于创建一个隐藏的窗口,WNDProc在课程中是一个函数,这要归功于TB的支持AddressOf 。设置的最后一个重要部分是我们必须注册所需的事件;它们都以PBT_POWERSETTINGCHANGE消息传递。我们需要将每个人作为类变量保留,但是注册很简单。 tbshelllib提供了API和GUIDS:
Private Function RegisterEvents() As Boolean
m_hEventM = RegisterPowerSettingNotification(m_hWnd, GUID_SESSION_DISPLAY_STATUS, DEVICE_NOTIFY_WINDOW_HANDLE)
m_hEventP = RegisterPowerSettingNotification(m_hWnd, GUID_SESSION_USER_PRESENCE, DEVICE_NOTIFY_WINDOW_HANDLE)
m_hEventL = RegisterPowerSettingNotification(m_hWnd, GUID_LIDSWITCH_STATE_CHANGE, DEVICE_NOTIFY_WINDOW_HANDLE)
If m_hEventM Then Return True
End Function
如前所述,它们由UMSG WM_POWERBROADCAST通过WPARAM PBT_POWERSETTINGCHANGE交付;我们从MSDN lparam知道,然后指向POWERBROADCAST_SETTING udt。但是我们遇到了一个问题:
typedef struct {
GUID PowerSetting;
DWORD DataLength;
UCHAR Data[1];
} POWERBROADCAST_SETTING, *PPOWERBROADCAST_SETTING;
那是C风格的阵列,而不是SAFEARRAY 。数据紧接在内存中,其中TB中的可变长度数组只能是具有完全不同结构的SAFEARRAY 。解决方案tbshelllib使用有点笨拙。
[ Description ("WARNING: You can't use this directly due to the SAFEARRAY. To receive, fill the first 20 bytes, then the data in the array. To send, create a byte buffer excluding the safearray member.") ]
Public Type POWERBROADCAST_SETTING
PowerSetting As UUID
DataLength As Long
Data() As Byte
End Type
这个想法是复制固定零件,然后使用长度来重新安排变量零件,然后将单独的副本(data(data(data))进行。不过,这堂课的捷径是捷径。由于我们只使用一个4个字节DWord来用于我们感兴趣的属性,因此看起来像这样:
Case WM_POWERBROADCAST
If wParam = PBT_POWERSETTINGCHANGE Then
Dim pSetting As POWERBROADCAST_SETTING
CopyMemory pSetting, ByVal lParam, 20
If IsEqualGUID(pSetting.PowerSetting, GUID_SESSION_DISPLAY_STATUS) Then
Dim pState As MONITOR_DISPLAY_STATE
CopyMemory pState, ByVal PointerAdd(lParam, 20), 4
Select Case pState
Case PowerMonitorOff
If (m_Mask And CPMEN_MONITOROFF) Then RaiseEvent MonitorOff()
我们将固定零件复制,以便我们可以检查这次是哪个事件的GUID,但是只需复制从Data偏移开始(GUID为16个字节,加4个字节长,= 20)的4个字节,直接代表代表可能值的枚举的变量。除最后一行以外的所有内容均由TBShelllib提供,包括通用PointerAdd ,它可以安全地执行无签名的添加。然后,我们只需检查呼叫者是否想要该活动,然后提高活动。出人意料的是,从这样的话题上称Raiseevent的效果没有特殊的操作,还没有崩溃,我已经让课堂跑了6个小时以上了,许多活动都在举起。
笔记
首次注册事件时,您将收到当前状态。因此,即使监视器不仅打开,它也会发送“监视器”。
该类提供了一种Destroy()方法来关闭监视并摆脱隐藏的窗口。如果您的应用程序计划继续运行,则要求您在尝试将其设置为Nothing之前,请进行调用(如果退出,可以让系统销毁所有内容)。线程在其消息循环中,因此不会退出消息循环,因此,为此,需要破坏窗口。您无法在其他线程中在窗口上调用DestroyWindow ,因此您要做的就是将WM_CLOSE与PostMessage有关,窗口会自行摧毁事件,并解开事件:
Case WM_CLOSE
DestroyWindow m_hWnd
Case WM_DESTROY
UnregisterEvents
PostQuitMessage 0
除此之外,我们还要给线程几秒钟以关闭,然后解开我们的自定义窗口类。之后,一切都清理了,班级本身就准备被摧毁了。
Public Sub Destroy()
If m_hWnd Then PostMessageW(m_hWnd, WM_CLOSE, 0, ByVal 0)
Dim lRet As WaitForObjOutcomes = WaitForSingleObject(m_hThread, 5000)
Debug.Print "Wait outcome=" & lRet
Dim hr As Long = UnregisterClassW(StrPtr(wndClass), m_hInst)
Debug.Print "Unregister hr=" & hr & ", lastErr=" & Err.LastDllError
End Sub
这就是它!这一切都很简单,但值得一提,因为很多是整个“简单的多线程”内容的新事物。