Une classe transparente autonome pour surveiller la présence des utilisateurs de plusieurs manières
Il s'agit d'une classe simple qui surveille 3 événements: l'état du moniteur (off, ON ou Demogd), le message «présent» du système et, si vous êtes sur un ordinateur portable, l'état du couvercle (ouvert, fermeture). Il le fait dans une classe entièrement auto-contenue - la complication avec cela se produit parce que ces événements sont envoyés sous la forme d'un message WM_POWERBROADCAST , requérir une fenêtre pour le recevoir. Mais je ne voulais pas limiter la classe aux applications graphiques et simplement sous-classer un formulaire HWND, bien que, dans un souci d'exhaustivité, l'application de démonstration montre comment le faire de cette façon aussi. Ainsi, la classe crée sa propre fenêtre cachée entièrement personnalisée, dans un fil séparé pour éviter la boucle de message bloquant la boucle de message du formulaire si elle est présente.
La classe profite des constructeurs paramétrés de Twinbasic pour spécifier les arguments disponibles directement dans le nouveau mot-clé; Ici, ce sont des arguments facultatifs pour les événements à élever et l'Hinstance pour s'inscrire sous ( App.hInstance 99% du temps; si vous l'omettez, il utilisera GetModuleHandleW() , qui renvoie la même valeur que App.hInstance sans créer une dépendance à WinNativeFormes). Ensuite, nous arrivons à l'un des grands plaisirs de la tuberculose: appeler CreateThread sans aucun hacks élaboré nécessaire, aussi cool que dans VB6. Le ThreadProc lui-même appelle simplement le reste du code.
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 et CreateWindowEx sont utilisés dans une routine normale pour créer une fenêtre cachée, le WNDProc étant une fonction au sein de la classe grâce à AddressOf de support de TB ici. La dernière partie importante de la configuration est que nous devons nous inscrire aux événements que nous voulons; Ils sont tous livrés en tant que messages PBT_POWERSETTINGCHANGE . Nous devons garder une poignée pour chacun en tant que variable de classe, mais l'enregistrement est simple; Tbshelllib fournit l'API et les 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
Comme déjà mentionné, ils sont livrés par UMSG WM_POWERBROADCAST avec WPARAM PBT_POWERSETTINGCHANGE ; Nous savons sur MSDN le LPARAM pointe ensuite vers un POWERBROADCAST_SETTING UDT. Mais nous rencontrons un problème:
typedef struct {
GUID PowerSetting;
DWORD DataLength;
UCHAR Data[1];
} POWERBROADCAST_SETTING, *PPOWERBROADCAST_SETTING;
C'est un tableau de style C, pas une SAFEARRAY ; Les données suivent immédiatement en mémoire, où les tableaux de longueur de variable dans la tuberculose (actuellement) ne peuvent être qu'une SAFEARRAY avec une structure entièrement différente. La solution que TbShellLib utilise est un peu maladroite;
[ 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
L'idée est de copier la pièce fixe, puis d'utiliser la longueur pour redim la partie variable et de faire une copie distincte dans Varptr (données (0)). Cette classe prend cependant un raccourci; Puisque nous travaillons uniquement avec un seul DWOR de 4 octets pour les propriétés qui nous intéressent, cela ressemble à ceci:
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()
Nous copie la pièce fixe afin que nous puissions vérifier le GUID pour lequel il est cette fois, mais il suffit de copier 4 octets à partir du décalage des Data (un GUID est de 16 octets, plus le long de 4 octets, = 20) directement à une variable représentant l'énumération des valeurs possibles. Tout en plus de la dernière ligne est fourni par TbShellLib, y compris le générique PointerAdd , qui effectue en toute sécurité un ajout non signé. Ensuite, nous vérifions simplement si l'appelant veut l'événement et le soulevez. Étonnamment, appeler Raiseevent à partir de fil comme celui-ci a fonctionné sans manipulation spéciale et ne s'est pas encore écrasé, et j'ai laissé la classe en cours d'exécution pendant plus de 6 heures avec de nombreux événements.
Note
Vous recevrez l'état actuel lorsque vous vous inscrivez pour la première fois aux événements. Il enverra donc «moniteur sur» même si le moniteur n'a pas seulement été allumé.
La classe fournit une méthode Destroy() pour désactiver la surveillance et se débarrasser de la fenêtre cachée. Il est nécessaire que vous appeliez cela avant d'essayer de le définir sur Nothing si votre application prévoit de continuer à fonctionner (si vous éteignez, vous pouvez tout laisser détruire tout). Le thread est dans sa boucle de message, donc il ne quittera pas les sorties de boucle de message, pour ce faire, la fenêtre doit être détruite. Vous ne pouvez pas appeler DestroyWindow sur la fenêtre dans un autre fil, donc ce que vous faites à la place, c'est envoyer WM_CLOSE avec PostMessage , et la fenêtre se détruit, et déconseille les événements:
Case WM_CLOSE
DestroyWindow m_hWnd
Case WM_DESTROY
UnregisterEvents
PostQuitMessage 0
En outre, nous donnons au fil quelques secondes pour arrêter, puis désinscrivez notre classe de fenêtre personnalisée. Après cela, tout est nettoyé et la classe elle-même est prête à être détruite.
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
Et c'est tout ce qu'il y a! Tout cela est assez simple, mais la peine d'être rédigé car beaucoup sont nouveaux dans toute la chose «multithreading» facile.