Как-то мне понадобилось собрать сведения о загруженных драйверах и службах, причём акцент делался на то, сколько их всего, какие активны, а какие нет, и т.д. Так на свет родилась утилита с которой хочу поделиться здесь. По сути организовать это дело просто, поэтому съехав с основной трассы я решил разобраться и с созданием кастомного окна Windows, прикрутив к нему элемент управления ListView в режиме таблицы, аля рабочая зона проводника "Total Commander". Статью можно считать продолжением недавнего поста про службы.
Под катом:
1. Функции Win32API для служб и драйверов
Для перечисления служб и драйверов в базе данных диспетчера управления службами SCM предусмотрена функция
Обратите внимание на параметр(5) "cbBufSize" - функция ожидает в нём размера буфера для массива структур, чего мы не можем знать заранее. Поэтому функцию нужно вызывать дважды - при первом вызове она вернёт требуемый размер буфа по которому мы выделяем память через
Так получим заполненный буфер в выделенной памяти, причём в переменной "lpServCount" будет указано общее кол-во структур ENUM_SERVICE_STATUS в массиве, и на каждом шаге цикла прибавляя размер одной структуры
2. Окна и элемент управления "ListView"
ListView - это один из самых гибких и мощных компонентов для отображения коллекций данных в графических интерфейсах (WinForms, WPF, и в мобильных платформах). Предназначен для отображения списка элементов с различными уровнями детализации. Он может показывать данные в виде:
Всё, что мы видим в окне приложения принято называть окном. Например кнопка, поле ввода, надписи, чекбоксы и т.д. - всё это окна Window, которым мы можем посылать сообщения функцией
Окно - это базовый объект в Windows.
Примеры: Главное/Диалоговое/Всплывающее окно приложения.
Элемент управления - это окно, предназначенное для взаимодействия с пользователем:
Таким образом, каждый элемент управления - это окно, но не каждое окно - элемент управления. Это как "квадрат - это прямоугольник, но не каждый прямоугольник - квадрат". Все они живут в единой оконной подсистеме Windows, используют дескрипторы HWND, сообщения и процедуры окон, но выполняют разные роли в интерфейсе.
Элементы управления делятся на базовые, и расширенные классы - первые живут в User32.dll, а вторые в Comctl32.dll, что означает "Common Control". К базовым относятся:
Базовые элементы всегда доступны приложению, а вот расширенные нужно подключать уже явно вызовом
Обратите внимание, что классы элементов управления имеют разные префиксы, которые я разделил здесь на блоки - это не случайность, а отражение того, как они были включены в систему. Имена регистрируются в comctl32.dll и жёстко закодированы в ней. Главное различие кроется в истории создания и способе инициализации. Вот краткая сводка различий, причём для подключения последних требуется расширенная(Ex) инициализация
1. msctls - старая гвардия - некоторые пришли из библиотек мультимедиа, и сохранили исторический префикс.
2. Sys - новая волна - появились ещё в Win98 как часть comctl32.dll.
3. Без префикса - специалисты - появились позже, часто для специфических задач, нет единого стандарта именования
Начиная с версии v6.0 в либе comctl32.dll появились доп.украшательства типа: тени, прозрачность, округлые края, которые описываются в файлах xml. В каждой системной библиотеке должна присутствовать функция
Значит вставка элемента ListView осуществляется через 2 структуры - в первой описываются атрибуты столбцов (кол-во, текст в заголовке), а во-второй свойства строк. Вот прототипы этих структур:
После того-как структуры заполнены, вызывается
Строки вставляются в цикле, но поскольку вызов идёт из тела оконной процедуры, я создал для этого отдельную подпрограмму:
3. Заключение
Полный исходный код + файл для тестов найдёте в скрепке, а результат работу утилиты выглядит так. Здесь видно, что в окне можно задать режим поиска, имеется сортировка данных по столбцам, а так-же вывод сведений о пользователе и процессоре, которые читаются из переменных окружения системы при помощи
Ссылки по теме:
Описание свойств всех элементов управления
Под катом:
1. Функции Win32API для служб и драйверов
2. Элемент управления "List-View" и строка статуса
3. Заключение
1. Функции Win32API для служб и драйверов
Для перечисления служб и драйверов в базе данных диспетчера управления службами SCM предусмотрена функция
EnumServicesStatus() из библиотеки Advapi32.dll (расширенная, Advanced API). Она возвращает название и статус каждой службы в структуру ENUS_SERVICE_STATUS. Прототип функции выглядит так:
C-подобный:
BOOL EnumServicesStatusA(
hSCManager dd 0 ;// [in] дескриптор от OpenSCManager()
dwServiceType dd 0 ;// [in] тип запрашиваемой инфы = Драйвера или Службы
dwServiceState dd 0 ;// [in] состояние служб: Все = 3
lpServices dd 0 ;// [out] линк на массив структур ENUM_SERVICE_STATUS
cbBufSize dd 0 ;// [in] общий размер массива
pcbBytesNeeded dd 0 ;// [out] если буфер для массива мал, то размер хвоста
lpServicesReturned dd 0 ;// [out] число возвращённых структур
lpResumeHandle dd 0 ;// [in,out] линк на переменную со значением(0)
;//--------------------------
struct ENUM_SERVICE_STATUS
lpServiceName dd 0 ;// имя драйвера/службы,
lpDisplayName dd 0 ;// ..и её описание
ServiceStatus SERVICE_STATUS ;//----+
ends ;// |
;// |
struct SERVICE_STATUS ;//<-----------------+
dwServiceType dd 0 ;// Тип = драйвер/служба
dwCurrentState dd 0 ;// Статус = запущена/остановлена
dwControlsAccepted dd 0
dwWin32ExitCode dd 0
dwServiceSpecificExitCode dd 0
dwCheckPoint dd 0
dwWaitHint dd 0
ends
Обратите внимание на параметр(5) "cbBufSize" - функция ожидает в нём размера буфера для массива структур, чего мы не можем знать заранее. Поэтому функцию нужно вызывать дважды - при первом вызове она вернёт требуемый размер буфа по которому мы выделяем память через
Heap/VirtualAlloc(), а при втором передаём уже этот размер обратно функции, и вызываем её второй раз. В
Ссылка скрыта от гостей
на MSDN говорится, что макс.размер этого буфера не может превышать 260 КБ, хотя на практике требуется намного меньше, в зависимости от списка драйверов/служб на текущий момент. Вот пример:
C-подобный:
;//--- Пробуем получить доступ к диспетчеру SCM ----
invoke OpenSCManager,0,0,SC_MANAGER_ENUMERATE_SERVICE
mov [hScm],eax
;//--- Запрос статуса служб и выделяем буфер -------
;// выстрел(1) холостой, для вычисления размера приёмного буфера
;// выстрел(2) непосредственно читаем инфу в него
invoke EnumServicesStatus,[hScm],\
[mode],\
[state],0,0,\
lpBytesNeeded,\
lpServCount,\
lpResumeHndl
invoke VirtualAlloc,0,[lpBytesNeeded], MEM_COMMIT, PAGE_READWRITE
mov [lpScBase],eax
invoke EnumServicesStatus,[hScm],\
[mode],\
[state],\
eax,\
[lpBytesNeeded],\
lpBytesNeeded,\
lpServCount,\
lpResumeHndl
Так получим заполненный буфер в выделенной памяти, причём в переменной "lpServCount" будет указано общее кол-во структур ENUM_SERVICE_STATUS в массиве, и на каждом шаге цикла прибавляя размер одной структуры
sizeof.ENUM_SERVICE_STATUS можно будет обойти их все. Только куда выводить нарытую информацию? Самый простой вариант - это конечно через форматированный printf() на консоль, но лучше конечно-же в форточку, вписав в неё таблицу по строкам и столбцам. Для этого предусмотрен элемент управления "ListView" - рассмотрим его подробней.2. Окна и элемент управления "ListView"
ListView - это один из самых гибких и мощных компонентов для отображения коллекций данных в графических интерфейсах (WinForms, WPF, и в мобильных платформах). Предназначен для отображения списка элементов с различными уровнями детализации. Он может показывать данные в виде:
• Крупных значков - Large Icon, 32x32.
• Мелких значков - Small Icon, 16x16.
• Списка - Simple List, компактный режим без колонок, с иконками слева.
• Таблицы - Details, самый популярный режим в виде строк и колонок, поддерживает сортировку.
Всё, что мы видим в окне приложения принято называть окном. Например кнопка, поле ввода, надписи, чекбоксы и т.д. - всё это окна Window, которым мы можем посылать сообщения функцией
SendMessage(), текст при помощи Get/SetWindowText(), или-же создавать динамически в процессе работы новые через CreateWindow(). Однако окном может называться и "элемент управления" в окне, что создаёт путаницу. С технической точки зрения окно(Window) и элемент управления(Control) ничем не отличается, но есть нюансы.Окно - это базовый объект в Windows.
Примеры: Главное/Диалоговое/Всплывающее окно приложения.
• Занимает прямоугольную область экрана
• Получает сообщения от системы и других окон
• Имеет процедуру обработки сообщений
Элемент управления - это окно, предназначенное для взаимодействия с пользователем:
• Обычно является дочерним (WS_CHILD)
• Имеет зарегистрированный системой стандартный класс
• Служит для ввода/вывода данных
• Уведомляет родительское окно о событиях (через WM_COMMAND или WM_NOTIFY)
Таким образом, каждый элемент управления - это окно, но не каждое окно - элемент управления. Это как "квадрат - это прямоугольник, но не каждый прямоугольник - квадрат". Все они живут в единой оконной подсистеме Windows, используют дескрипторы HWND, сообщения и процедуры окон, но выполняют разные роли в интерфейсе.
Элементы управления делятся на базовые, и расширенные классы - первые живут в User32.dll, а вторые в Comctl32.dll, что означает "Common Control". К базовым относятся:
• Button - кнопки, чекбоксы, боксы групп
• Edit - многострочное поле для ввода текста
• ListBox - простой список (выпадающий или обычный)
• ComboBox - комбинированный список (поле ввода + выпадающий список)
• Static - статический текст, рамки, иконки
• ScrollBar - верт/горизонтальные полосы прокрутки
Базовые элементы всегда доступны приложению, а вот расширенные нужно подключать уже явно вызовом
InitCommonControls(). С полным их списком можно ознакомиться в инклуде fasm'a "comctl32.inc":
C-подобный:
;// Common control window classes
HOTKEY_CLASS equ 'msctls_hotkey32'
PROGRESS_CLASS equ 'msctls_progress32'
STATUS_CLASS equ 'msctls_statusbar32'
TRACKBAR_CLASS equ 'msctls_trackbar32'
UPDOWN_CLASS equ 'msctls_updown32'
ANIMATE_CLASS equ 'SysAnimate32'
DATETIMEPICK_CLASS equ 'SysDateTimePick32'
HEADER_CLASS equ 'SysHeader32'
LISTVIEW_CLASS equ 'SysListView32'
MONTHCAL_CLASS equ 'SysMonthCal32'
TABCONTROL_CLASS equ 'SysTabControl32'
TREEVIEW_CLASS equ 'SysTreeView32'
REBAR_CLASS equ 'ReBarWindow32' ;// (контейнер для панели инструментов)
TOOLBAR_CLASS equ 'ToolbarWindow32' ;// (панель инструментов)
TOOLTIPS_CLASS equ 'tooltips_class32' ;// (всплывающие подсказки)
Обратите внимание, что классы элементов управления имеют разные префиксы, которые я разделил здесь на блоки - это не случайность, а отражение того, как они были включены в систему. Имена регистрируются в comctl32.dll и жёстко закодированы в ней. Главное различие кроется в истории создания и способе инициализации. Вот краткая сводка различий, причём для подключения последних требуется расширенная(Ex) инициализация
InitCommonControlEx():1. msctls - старая гвардия - некоторые пришли из библиотек мультимедиа, и сохранили исторический префикс.
2. Sys - новая волна - появились ещё в Win98 как часть comctl32.dll.
3. Без префикса - специалисты - появились позже, часто для специфических задач, нет единого стандарта именования
Начиная с версии v6.0 в либе comctl32.dll появились доп.украшательства типа: тени, прозрачность, округлые края, которые описываются в файлах xml. В каждой системной библиотеке должна присутствовать функция
DllGetVersion(), чтобы разработчик мог узнать версию подключаемой либы. Однако в некоторых dll этой функции может и не быть, а потому вызывать её следует динамически через GetProcAddress(). Она возвращает паспорт либы в следующую структуру:
C-подобный:
struct DLLVERSIONINFO
cbSize dd sizeof.DLLVERSIONINFO
dwMajorVersion dd 0
dwMinorVersion dd 0
dwBuildNumber dd 0
dwPlatformID dd 0
ends
Значит вставка элемента ListView осуществляется через 2 структуры - в первой описываются атрибуты столбцов (кол-во, текст в заголовке), а во-второй свойства строк. Вот прототипы этих структур:
C-подобный:
;// Описание строк
;//---------------------
struct LV_ITEM
mask dd 0 ;// флаг запроса элементов (см. LVIF_xx)
iItem dd 0 ;// индекс элемента начиная с нуля (первый столбец)
iSubItem dd 0 ;// индекс подэлемента, или нуль (второй и сл.столбцы)
state dd 0 ;// состояние (см. LVIS_xx)
stateMask dd 0 ;// маска запросов состояния
pszText dd 0 ;// указатель на текст элемента
cchTextMax dd 0 ;// длина текста
iImage dd 0 ;// индекс значка
lParam dd 0 ;// специфичное для эл.значение
iIndent dd 0 ;// нуль, или значение отступа начиная с 1.
ends
;// Описание столбцов
;//---------------------
struct LV_COLUMN
mask dd 0
fmt dd 0
cx dd 0
pszText dd 0
cchTextMax dd 0
iSubItem dd 0
ends
После того-как структуры заполнены, вызывается
SendMessage() с дескриптором элемента ListView, что приводит к отображению соответствующего поля в окне. Другими словами таблица строится по кирпичику при каждом вызове SendMessage(). Если атрибуты столбцов задаются отдельно, то строки обычно заполняются в цикле - в данном случае инфу будем брать из массива SCM, который получили на предыдущем шаге. Вот пример, где закомментированный стиль "EX_GRIDLINES" рисует линии по строкам и столбцам (сетка):
C-подобный:
;//--->>--->>---- Установить расширенный стиль списка "LIST_VIEW"
invoke SendMessage,[hLV],LVM_SETEXTENDEDLISTVIEWSTYLE,0,\
LVS_EX_FULLROWSELECT + LVS_EX_HEADERDRAGDROP ;// + LVS_EX_GRIDLINES
;//--->>--->>---- Вставить столбцы "LV_COLUMN" в список
mov [lvc.mask],LVCF_TEXT + LVCF_WIDTH
mov [lvc.cx],160 ;// имя сервиса
mov [lvc.pszText],colName0
invoke SendMessage,[hLV], LVM_INSERTCOLUMN, 0, lvc
mov [lvc.cx],145 ;// тип
mov [lvc.pszText],colName1
invoke SendMessage,[hLV], LVM_INSERTCOLUMN, 1, lvc
mov [lvc.cx],93 ;// статус
mov [lvc.pszText],colName2
invoke SendMessage,[hLV], LVM_INSERTCOLUMN, 2, lvc
mov [lvc.cx],410 ;// описание
mov [lvc.pszText],colName3
invoke SendMessage,[hLV], LVM_INSERTCOLUMN, 3, lvc
mov [lvc.cx],-2 ;// пустой столбец
mov [lvc.pszText],colName4
invoke SendMessage,[hLV], LVM_INSERTCOLUMN, 4, lvc
;//--->>--->>---- Меняем цвет фона и текста ListView
invoke SendMessage,[hLV], LVM_SETBKCOLOR, 0, 0x393431
invoke SendMessage,[hLV], LVM_SETTEXTCOLOR, 0, 0xeeeeee
invoke SendMessage,[hLV], LVM_SETTEXTBKCOLOR,0, 0x393431
Строки вставляются в цикле, но поскольку вызов идёт из тела оконной процедуры, я создал для этого отдельную подпрограмму:
C-подобный:
proc ScmDataToListview hDlg
;//--->>--->>---- Заполнить строки списка "ListView" ------
;//------ Циклический вывод служб на консоль
mov [lvi.mask],LVIF_TEXT + LVIF_PARAM ;// LVIF_PARAM для сортировки!
mov edi,[lpScBase]
mov ecx,[lpServCount]
and [count],0
@@: push ecx
mov eax,[edi+ENUM_SERVICE_STATUS.lpServiceName]
mov ebx,[count]
mov [lvi.iItem],ebx
mov [lvi.lParam],ebx
mov [lvi.pszText],eax ;// имя сервиса
mov [lvi.iSubItem],0
invoke SendMessage,[hLV], LVM_INSERTITEM, 0, lvi
and [lvi.mask],not LVIF_PARAM
mov eax,[edi+ENUM_SERVICE_STATUS.dwServiceType]
mov esi,typeList
.if eax=2 ;// вывод типа службы/драйвера по маске
add esi,4
.elseif eax=10h
add esi,8
.elseif eax=20h
add esi,12
.elseif eax=110h
add esi,16
.endif
mov esi,[esi]
mov [lvi.pszText],esi ;// тип
mov [lvi.iSubItem],1
invoke SendMessage,[hLV], LVM_SETITEM, 0, lvi
mov esi,stateList
mov eax,[edi+ENUM_SERVICE_STATUS.dwCurrentState]
.if eax=4
inc [countRun]
.endif
dec eax
shl eax,2
add esi,eax
mov esi,[esi]
mov [lvi.pszText],esi ;// статус
mov [lvi.iSubItem],2
invoke SendMessage,[hLV], LVM_SETITEM, 0, lvi
mov eax,[edi+ENUM_SERVICE_STATUS.lpDisplayName]
mov [lvi.pszText],eax
mov [lvi.iSubItem],3
invoke SendMessage,[hLV], LVM_SETITEM, 0, lvi
add edi,sizeof.ENUM_SERVICE_STATUS
inc [count]
pop ecx
dec ecx
jnz @b
;// Строка 'Найдено всего:' в поле(0) StatusBar
invoke SendMessage,[hSBar],SB_SETTEXT,SBT_OWNERDRAW +0, 0
;// Строка 'Из них активных:' в поле(1) StatusBar
invoke SendMessage,[hSBar],SB_SETTEXT,SBT_OWNERDRAW +1, 0
invoke VirtualFree,[lpScBase]
mov [countRun],0
ret
endp
Если хочется дополнительно посмотреть, как Win32-интерфейс собирается на практике и почему диалоговый подход иногда сильно упрощает работу с окнами, загляните в статью: Форк софта CPUZ - скан бортового железа.
3. Заключение
Полный исходный код + файл для тестов найдёте в скрепке, а результат работу утилиты выглядит так. Здесь видно, что в окне можно задать режим поиска, имеется сортировка данных по столбцам, а так-же вывод сведений о пользователе и процессоре, которые читаются из переменных окружения системы при помощи
GetEnvironmentVariable(). Более того предусмотрена строка статуса в подвале окна, для вставки которой достаточно позвать на помощь CreateStatusWindow(), куда оседает инфа с счётчиком о найденных и активных из общего числа. Обратите внимание на версию либы comctl32.dll - если она старше/равно v6.0, значит графическая подсистема ОС поддерживает доп.плюшки для украшательств окна. Всем удачи, пока!Ссылки по теме:
Описание свойств всех элементов управления
Ссылка скрыта от гостей
Вложения
Последнее редактирование модератором: