一個獨立的螺紋類,可通過多種方式監視用戶的存在
這是一個簡單的類,可監視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
這就是它!這一切都很簡單,但值得一提,因為很多是整個“簡單的多線程”內容的新事物。