Автономный, резьбовой класс для мониторинга присутствия пользователей несколькими способами
Это простой класс, который отслеживает 3 события: состояние монитора (выключение, включение или тусклое), сообщение «Пользователь пользователя» и, если вы находитесь на ноутбуке, статус крышки (Open, Close). Это происходит в совершенно самоконтрольном классе- осложнение с этим возникает, потому что эти события отправляются в виде сообщения WM_POWERBROADCAST , выполняя окно, чтобы получить его. Но я не хотел ограничивать класс графическими приложениями и просто подкласс в форму hwnd, хотя ради полноты демонстрационное приложение показывает, как это сделать таким образом. Таким образом, класс создает свое собственное скрытое окно, в отдельном потоке, чтобы избежать цикла сообщения, блокирующего цикл сообщения формы, если он присутствует.
Класс использует преимущества параметризованных конструкторов Twinbasic, чтобы указать доступные аргументы прямо в новом ключевом слова; Здесь это необязательные аргументы о том, какие события для подъема и подтягивание зарегистрироваться в соответствии с ( App.hInstance 99% времени; если вы его опустите, он будет использовать GetModuleHandleW() , что возвращает то же значение, что и App.hInstance без создания зависимости от WinnativeForms). Затем мы добираемся до одного из великих удовольствий туберкулеза: вызов CreateThread без каких -либо тщательно продуманных взломов, необходимых, как и в VB6. Сам TradeProc просто вызывает остальную часть кода.
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 является функцией в классе благодаря вспомогательному AddressOf TB здесь. Последняя важная часть настройки заключается в том, что мы должны зарегистрироваться для событий, которые мы хотим; Все они доставляются в виде сообщений PBT_POWERSETTINGCHANGE . Нам нужно сохранить ручку для каждого в качестве переменной класса, но регистрация проста; Tbshelllib предоставляет API и гииды:
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, а затем указывает на POWERBROADCAST_SETTING UDT. Но мы сталкиваемся с проблемой:
typedef struct {
GUID PowerSetting;
DWORD DataLength;
UCHAR Data[1];
} POWERBROADCAST_SETTING, *PPOWERBROADCAST_SETTING;
Это массив C-стиля, а не SAFEARRAY ; Данные немедленно следует в памяти, где массивы переменной длины в туберкулезе (в настоящее время) могут быть только 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
Идея состоит в том, чтобы скопировать фиксированную часть, затем использовать длину, чтобы исправить переменную часть и сделать отдельную копию в varptr (data (0)). Этот класс берет ярлык; Поскольку мы работаем только с одним 4-байтовым DWOR для свойств, которые нас интересуют, похоже, это так:
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, на какое событие это на этот раз, но затем просто копируйте 4 байта, начиная с смещения Data (GUID составляет 16 байтов, плюс длиной 4 байта, = 20) непосредственно к переменной, представляющей перечисление возможных значений. Все, кроме последней строки, обеспечивается TBshelllib, включая общий PointerAdd , который безопасно выполняет беззнательное дополнение. Затем мы просто проверяем, хочет ли звонящий это событие, и поднимаем его. Удивительно, но призыв изюминку из -за нити, подобной этой, работал без особой обработки и еще не потерпел крах, и я оставил класс, работающий на 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
И это все, что нужно! Это все довольно просто, но стоит написать, поскольку многие из них являются новичками для всей «легкой многопоточной».