فئة قائمة بذاتها لرواية مراقبة وجود المستخدم بعدة طرق
هذه فئة بسيطة تراقب 3 أحداث: حالة الشاشة (OFF ، ON ، أو DIMMED) ، رسالة "المستخدم" للنظام ، وإذا كنت على جهاز كمبيوتر محمول ، فإن حالة الغطاء (مفتوحة ، إغلاق). يقوم بذلك في فئة محتوية على نفسها تمامًا- تعقيد مع هذا ينشأ لأن هذه الأحداث يتم إرسالها في شكل رسالة WM_POWERBROADCAST ، مع إعادة تدوين نافذة لاستلامها. لكنني لم أكن أرغب في قصر الفصل على التطبيقات الرسومية وفقط فئة فرعية من النموذج ، على الرغم من أنه من أجل الاكتمال ، يوضح التطبيق التجريبي كيفية القيام بذلك بهذه الطريقة أيضًا. لذلك ، يقوم الفصل بإنشاء نافذة مخفية مخصصة تمامًا ، في سلسلة رسائل منفصلة لتجنب حلقة الرسالة التي تمنع حلقة رسالة النموذج إذا كانت موجودة.
يستفيد الفصل من مُنشئات TwinBasic المعلمة لتحديد الوسيطات المتاحة في الكلمة الرئيسية الجديدة ؛ هنا هذه وسيطات اختيارية من الأحداث التي يجب رفعها و Hinstance للتسجيل تحت ( App.hInstance 99 ٪ من الوقت ؛ إذا قمت بحذفها ، فستستخدم GetModuleHandleW() ، والتي تُرجع نفس القيمة مثل App.hInstance دون إنشاء اعتماد على الأداء الأصلي). ثم نصل إلى واحدة من الملذات الرائعة للسل: استدعاء 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 وظيفة داخل الفصل بفضل AddressOf TB الداعم هنا. آخر جزء مهم من الإعداد هو أنه يتعين علينا التسجيل في الأحداث التي نريدها ؛ يتم تسليمها جميعًا كرسائل 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
تتمثل الفكرة في نسخ الجزء الثابت ، ثم استخدام الطول لإعادة إعادة الجزء المتغير ، والقيام بنسخة منفصلة في varptr (البيانات (0)). هذا الفصل يأخذ اختصار رغم ذلك ؛ نظرًا لأننا نعمل فقط مع DWORD 4 بايت للخصائص التي مهتمون بها ، يبدو أن هذا:
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 (A GUID هو 16 بايت ، بالإضافة إلى 4 بايت طويل ، = 20) مباشرة إلى متغير يمثل تعداد القيم المحتملة. يتم توفير كل شيء إلى جانب السطر الأخير بواسطة TBShellLib ، بما في ذلك PointerAdd العام ، الذي يؤدي بأمان إضافة غير موقعة. ثم نتحقق فقط مما إذا كان المتصل يريد الحدث ، ورفعه. من المثير للدهشة أن استدعاء RASEEVENT من خارج الخيط مثل هذا العمل دون أي معالجة خاصة ولم تحطم بعد ، وقد تركت الفصل يركض لمدة 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
وهذا كل ما في الأمر! كل شيء واضح ومباشر ، ولكن يستحق الكتابة لأن الكثير من الأشياء الجديدة في كل شيء "السهل المتعدد مؤشرات الترابط".