| OberonCore https://forum.oberoncore.ru/ |
|
| Регистрация при помощи WinApi RegisterDeviceNotificationW https://forum.oberoncore.ru/viewtopic.php?f=23&t=7030 |
Страница 1 из 1 |
| Автор: | Александр К [ Пятница, 17 Октябрь, 2025 03:50 ] |
| Заголовок сообщения: | Регистрация при помощи WinApi RegisterDeviceNotificationW |
Для получения полной информации о съёмных накопителях в Windows необходимо зарегистрировать устройство при помощи RegisterDeviceNotificationW. Но у меня никак не получается это сделать: выдаёт ошибку "Ошибка регистрации уведомлений", а "информативный" GetLastError как всегда выдаёт 0. Я колдовал по-всякому и DeepSeek'а спрашивал - не даёт вразумительного ответа. У меня Windows 11. Вот коды модулей: Код: MODULE MyRegDev ["user32"]; PROCEDURE RegisterDeviceNotificationW* (hRecipient, NotificationFilter, Flags: INTEGER): INTEGER; PROCEDURE UnregisterDeviceNotification* (handle: INTEGER): INTEGER; END MyRegDev. И основной: Код: MODULE UsbNotifier; IMPORT SYSTEM, WinApi, HostWindows, Windows, StdLog, MyRegDev; CONST WM_DEVICECHANGE = 219H; (* Сообщение об изменении устройств *) DBT_DEVICEARRIVAL = 8000H; (* Устройство подключено *) DBT_DEVICEREMOVECOMPLETE = 8004H; (* Устройство отключено *) DBT_DEVTYP_VOLUME = 2H; (* Тип устройства - том (флешка) *) TYPE DEV_BROADCAST_HDR1* = RECORD dbch_size*: INTEGER; dbch_devicetype*: INTEGER; dbch_reserved*: INTEGER; END; DEV_BROADCAST_HDR* = POINTER TO DEV_BROADCAST_HDR1; DEV_BROADCAST_VOLUME* = POINTER TO RECORD dbch_size*: INTEGER; dbch_devicetype*: INTEGER; dbch_reserved*: INTEGER; dbcv_unitmask*: SET; dbcv_flags*: SHORTINT; END; VAR hwndMain: INTEGER; registered: WinApi.BOOL; devNotify: INTEGER; (* Callback-процедура для обработки оконных сообщений *) PROCEDURE [callback] MainWinProc(hwnd: INTEGER; msg: INTEGER; wParam: INTEGER; lParam: INTEGER): INTEGER; VAR devHdr: DEV_BROADCAST_HDR; devVolume: DEV_BROADCAST_VOLUME; driveLetter: CHAR; driveMask: SET; i: INTEGER; BEGIN IF msg = WM_DEVICECHANGE THEN (* Проверяем, что устройство подключено *) IF wParam = DBT_DEVICEARRIVAL THEN devHdr := SYSTEM.VAL(DEV_BROADCAST_HDR, lParam); IF devHdr # NIL THEN (* Проверяем, что это том (флешка) *) IF devHdr.dbch_devicetype = DBT_DEVTYP_VOLUME THEN devVolume := SYSTEM.VAL(DEV_BROADCAST_VOLUME, lParam);; driveMask := devVolume.dbcv_unitmask; (* Определяем букву подключенного диска *) i := 0; WHILE (i < 26) & ~(i IN driveMask) DO INC(i) END; IF i < 26 THEN (* Выводим сообщение о подключении флешки *) StdLog.String ("Флешка подключена на диск: "); StdLog.Char(CHR(ORD("A") + i)); StdLog.Ln END END END (* Проверяем, что устройство отключено *) ELSIF wParam = DBT_DEVICEREMOVECOMPLETE THEN devHdr := SYSTEM.VAL(DEV_BROADCAST_HDR, lParam); IF (devHdr # NIL) & (devHdr.dbch_devicetype = DBT_DEVTYP_VOLUME) THEN StdLog.String ("Флешка отключена"); StdLog.Ln END END END; (* Передаем сообщение на стандартную обработку *) RETURN WinApi.DefWindowProc(hwnd, msg, wParam, lParam) END MainWinProc; (* Процедура создания окна *) PROCEDURE CreateMainWindow*; VAR wc: WinApi.WNDCLASS; className: WinApi.PtrSTR; style: SET; BEGIN IF hwndMain = 0 THEN (* Заполняем структуру класса окна *) className := "USBNotifierWindow"; wc.style := {}; wc.lpfnWndProc := MainWinProc; wc.cbClsExtra := 0; wc.cbWndExtra := 0; wc.hInstance := WinApi.GetModuleHandle(NIL); wc.hIcon := 0; wc.hCursor := 0; wc.hbrBackground := 0; wc.lpszMenuName := NIL; wc.lpszClassName := className; (* Регистрируем класс окна *) IF WinApi.RegisterClass(wc) = WinApi.FALSE THEN StdLog.String("Ошибка регистрации класса окна"); StdLog.Int(WinApi.GetLastError()); StdLog.Ln ELSE (*Создаем невидимое окно *) style := WinApi.WS_OVERLAPPEDWINDOW; hwndMain := WinApi.CreateWindowEx({}, className, "USB Notifier", style, WinApi.CW_USEDEFAULT, WinApi.CW_USEDEFAULT, 300, 200, 0, 0, wc.hInstance, 0); IF hwndMain = WinApi.FALSE THEN StdLog.String("Ошибка создания окна"); StdLog.Ln ELSE (* Делаем окно невидимым *) registered := WinApi.ShowWindow(hwndMain, WinApi.SW_HIDE); END END END END CreateMainWindow; (* Процедура уничтожения окна *) PROCEDURE DestroyMainWindow*; VAR result: WinApi.BOOL; BEGIN IF hwndMain # 0 THEN IF WinApi.DestroyWindow(hwndMain) = WinApi.FALSE THEN StdLog.String ("Не удалось удалить окно"); StdLog.Ln ELSE IF WinApi.UnregisterClassW ("USBNotifierWindow", WinApi.GetModuleHandle(NIL)) = WinApi.FALSE THEN StdLog.String ("Не удалось удалить класс"); StdLog.Ln ELSE IF (devNotify # 0) & (MyRegDev.UnregisterDeviceNotification (devNotify) = WinApi.FALSE) THEN StdLog.String ("Не удалось отменить регистрацию."); StdLog.Ln END END; hwndMain := 0; registered := WinApi.FALSE; END END END DestroyMainWindow; (* Процедура регистрации для получения уведомлений об устройствах *) PROCEDURE RegisterForDeviceNotifications*; CONST DEVICE_NOTIFY_WINDOW_HANDLE = 0; VAR filter: DEV_BROADCAST_HDR; BEGIN IF hwndMain = WinApi.FALSE THEN CreateMainWindow() ELSE (*DestroyMainWindow*) END; IF hwndMain # 0 THEN (* Заполняем структуру фильтра *) NEW(filter); filter.dbch_size := SIZE(DEV_BROADCAST_HDR1); StdLog.Int(filter.dbch_size);StdLog.Ln; filter.dbch_devicetype := DBT_DEVTYP_VOLUME; filter.dbch_reserved := 0; (* Регистрируем для получения уведомлений *) devNotify := MyRegDev.RegisterDeviceNotificationW(hwndMain, SYSTEM.ADR(filter), DEVICE_NOTIFY_WINDOW_HANDLE); IF devNotify = WinApi.FALSE THEN StdLog.String("Ошибка регистрации уведомлений:"); StdLog.Int(WinApi.GetLastError()); StdLog.Ln; (*DestroyMainWindow*) ELSE StdLog.String("Регистрация для уведомлений USB выполнена успешно"); END END END RegisterForDeviceNotifications; (* Инициализация модуля *) BEGIN hwndMain := 0; registered := WinApi.FALSE; END UsbNotifier. UsbNotifier.RegisterForDeviceNotifications UsbNotifier.DestroyMainWindow Окно регистрируется: при подключении флешки выдаёт сообщение: "Флешка подключена на диск" с указанием буквы диска. |
|
| Автор: | Oleg N. Cher [ Пятница, 17 Октябрь, 2025 18:40 ] |
| Заголовок сообщения: | Re: Регистрация при помощи WinApi RegisterDeviceNotification |
Для получения уведомлений о подключении/отключении съёмных накопителей (томов, volumes) в Windows не требуется использовать RegisterDeviceNotificationW с типом DBT_DEVTYP_VOLUME. Эти события (в частности, DBT_DEVICEARRIVAL и DBT_DEVICEREMOVECOMPLETE для томов) транслируются (broadcast) всем топ-левел окнам автоматически, без необходимости регистрации. Именно поэтому в Вашем коде определение флешки работает, несмотря на "ошибку" регистрации. Функция RegisterDeviceNotificationW не поддерживает DBT_DEVTYP_VOLUME в качестве фильтра — она предназначена для других типов устройств, таких как интерфейсы устройств (DBT_DEVTYP_DEVICEINTERFACE) или OEM-устройства. Если передать ей структуру с dbch_devicetype = DBT_DEVTYP_VOLUME, она всегда вернёт NULL (FAIL), независимо от других параметров. Это задокументированное поведение: функция просто не предназначена для этого случая. Отсутствие ошибки в GetLastError (возврат 0) может быть связано с тем, что функция не устанавливает код ошибки для неподдерживаемого типа (или это артефакт вызова). Ваш код в целом правильный для обработки сообщений, и регистрация здесь избыточна. Чтобы избавиться от "ошибки", просто удалите часть с RegisterDeviceNotificationW — окно и так получит WM_DEVICECHANGE. Если хотите оставить попытку регистрации для других целей, обработайте неудачу молча, но это не повлияет на функциональность. Дополнительные замечания по коду Хотя основная проблема в неподдержке типа, в Вашем коде есть несколько потенциальных ошибок, которые могли бы мешать в других сценариях: Неправильный тип и размер структуры: Вы используете DEV_BROADCAST_HDR (размер ~12 байт), но для томов нужна полная DEV_BROADCAST_VOLUME (размер ~18 байт в Вашей типизации: 4+4+4+4+2). Измените: Код: VAR filter: DEV_BROADCAST_VOLUME; Затем: Код: NEW(filter); filter.dbch_size := SIZE(DEV_BROADCAST_VOLUME1); (* Или SIZE(filter^), в зависимости от системы *) filter.dbch_devicetype := DBT_DEVTYP_VOLUME; filter.dbch_reserved := 0; filter.dbcv_unitmask := {0..25}; (* Маска для всех дисков A-Z (0x03FFFFFF) *) filter.dbcv_flags := 0; (* Или 1 (DBTF_MEDIA) для фокуса на съёмных носителях *) Но, как сказано, это не поможет — функция всё равно откажет. Неправильная передача адреса: SYSTEM.ADR(filter) возвращает адрес переменной filter (указателя), а не адрес структуры. Нужно передать адрес самой структуры: Код: oberondevNotify := MyRegDev.RegisterDeviceNotificationW(hwndMain, SYSTEM.VAL(INTEGER, filter), DEVICE_NOTIFY_WINDOW_HANDLE); (Предполагается, что INTEGER совместим с адресами в Вашей среде) Другие мелочи: В обработчике: Добавьте проверку на devVolume.dbcv_flags с DBTF_MEDIA (0x1), чтобы фильтровать только съёмные носители (флешки), а не все тома. Если код для сервиса (без окна), то для получения уведомлений нужен RegisterDeviceNotification с сервис-хендлом и флагом DEVICE_NOTIFY_SERVICE_HANDLE, но у Вас окно, так что ок. Тестируйте на реальной флешке: Убедитесь, что dbcv_unitmask правильно интерпретируется (SET в Oberon — 32-бит, как DWORD). Если Вам нужно регистрировать уведомления именно для USB-устройств (не томов), используйте DBT_DEVTYP_DEVICEINTERFACE с GUID класса для USB Mass Storage (например, {53f56307-b6bf-11d0-94f2-00a0c91efb8b} для USB). Структура будет DEV_BROADCAST_DEVICEINTERFACE, и регистрация сработает. Ваш код работает, как задумано, — просто игнорируйте/уберите неудачную регистрацию. (c) GROK |
|
| Автор: | Александр К [ Суббота, 18 Октябрь, 2025 16:55 ] |
| Заголовок сообщения: | Re: Регистрация при помощи WinApi RegisterDeviceNotification |
Огромное спасибо за подробный ответ, а то я замучился с этой регистрацией. Оказывается ларчик просто открывался. Ещё раз спасибо! |
|
| Страница 1 из 1 | Часовой пояс: UTC + 3 часа |
| Powered by phpBB® Forum Software © phpBB Group https://www.phpbb.com/ |
|