РАБОТА С ЗАКЛАДКАМИ
Достаточно интересным элементом управления, появившимся только в Win32, являются закладки. Их появление, как и появление большинства общих элементов управления, давно ожидалось. Этот элемент действует подобно «алфавиту» в записной книжке, при выборе определенной буквы (в данном случае - определенной закладки) всплывает нужная страница (в данном случае - диалоговое окно). Читатель вспомнит, надеюсь, интерфейс электронной таблицы Excel 5.0 и рабочие листы в ней, которые можно было перебрать с помощью переключателей в нижней части таблицы. Эти переключатели и являлись закладками (tab control'ами). Связав каждую из закладок со страницей информации, возможно разместить несколько С'фаниц информации на одном и том же месте. Специальный тип закладок действует как кнопки - при выборе закладки вместо отображения очередной страницы просто производится посылка команды.
К сожалению, и в этом случае специальной функции для создания окна не предусмотрено. Программист должен использовать одну из функций - CreateWindowQ или CreateWindowExQ. При этом в качестве имени класса необходимо указать макрос WC_TABCONTROL, который описан в файле commctrl.h следующим образом:
#ifdef_WIN32
#define WC_TABCONTROLA
"SysTabControl32"
#detme WC_TABCONTROLW
#ifdef UNICODE
#define WC_TABCONTROL
#elsc
#define WC_TABCONTROL
#endif
#elsc
#dcfine WC_TABCONTROL
#cndif
L"SysTabControl32" WC TABCONTROLW WC'_TABCONTROL.A
"SysTabControl"
При создании окна с закладками могут использоваться как общие стили, применяющиеся для всех окон, так и стили, специфические для закладок (табл. 43).
Таблица 43. Стили закладок
Стиль
TCS_TABS
tcs_singleline
tcs rightjustify tcs"forceiconleft
tcs_forcelabelleft tcs_buttons tcs multiline tcs_fixedwidtm
tcs raggfdright
tcs focusonbuttondown
tcsjavnfrdrawfixed
tcs_. tooltips tcs focusnever
0x0020 0x0100 0x0200 0x0400
0x0800 0x1000 0x2000
0x4000 0x8000
Закладки являются закладками, а не
кнопками
Закладки располагаются в одну линию и
при необходимости скроллируются с
помощью up-down control'a
Иконка сдвинута к левому краю заклад-ки, текст центрирован И текст, и иконка сдвинуты к левому краю закладки
Закладки выглядят и действуют как кнопки
Закладки при необходимости располагаются в несколько строк Все закладки имеют одинаковую ширину
За прорисовку закладок отвечает прикладная npoipaMMa. а не система Задержка курсора мыши на одной из закладок вызывает появление подсказки Закладка никогда не получает фокуса ввода
После создания окна с закладками, необходимо определить каждую закладку, в которой могут быть иконка, заголовок (текст) и дополнитель-
ные данные, определяемые приложением. Для этого в программе нужно заполнить столько структур типа ТСМТЕМ, сколько закладок планируется создать. Структура ТСМТЕМ определена в файле commctrl.h:
typedcf struct JTCJTEMA
I
UINTmask; UINTlpRcservedl; UINT lpRcservcd2; LPSTR pszTcxt: inl cchTexlMax, int ilmagc:
LPARAM IParam; | TCJTEMA;
lypedef struct _TC_ITEMW {
UINT mask;
UINTlpRcservedl;
UINT lpRescrved2;
LPWSTR ps/Text;
int ccliTcxtMax;
int ilmage;
LPARAM IParam; } TCJTEMW;
#ifdefUNICODE
#define TCJTEM
#elsc
Adeline TCJTEM
#endif
TCJTEMW TC ITF.MA
Как следует из названий, поля IpReservedl и lpReserved2 не используются, они зарезервированы Microsoft для применения в будущем.
В поле mask этой структуры указывается, какие данные определяют внешний вид закладки. Это поле может принимать значения, приведенные в табл. 44.
После того, как читатель ознакомился с этой таблицей, назначения полей pszText, ilmage и IParam, надеюсь, стали понятны. Единственное поле, оставшееся нерассмотренным, - это cehTextMax. В случае, если структура типа ТСМТЕМ используется для получения информации о закладке, в поле cehTextMax определяется размер буфера, на который указывает pszText.
Т а б л it ц а 44. Битовые флаги, определяющие внешний вид и поведение закладок
Флаг |
Значение |
Описание |
TCIFJTEXT TCIFJMAGE TCIFJITLREADING TCIF_PARAM |
0x0001 0x0002 0x0004 0x0008 |
Поле pszText заполнено, в нем хранитея указатель на строку - заголовок закладки или па буфер, в который будет записана информация Полк ilmage заполнено, в нем хранится индекс отображаемого на закладке изображения в списке изображений или -1, если список изображений не используется Текст отображается справа налево, как, например, в арабском языке Поле IParam заполнено и содержит данные, определяемые приложением |
typedef struct JTCJTEMHEADERA
{
UINT mask;
UINTlpRescrvedl;
UINT ]pReserved2;
LPSTR pszText;
int cchTextMax;
int ilmage: } TCJTEMHEADERA;
typedef struct JTCJTEMHEADERW
{
UINT mask;
UINTipReservedl;
UTNT lpRescrved2;
LPWSTR pszText;
int cchTextMax;
int ilmage; } TCJTEMHEADERW;
tfifdcf UNICODE
«define TCJTEMHEADER
Seise
#definc TCJTEMHEADER
#endit'
TCJTEMHEADERW TC ITEMHEADERA
Как всегда, для управления окном с закладками используются сообщения. Их список приведен в табл. 45.
184
Таблица 45. Сообщения, посылаемые закладкам
Сообщение
Значение
Описание
TCMJ4RST
ТСМ GETIMAGELIST
ТСМ SETIMAGELIST
ТСМ GETITEMCOUNT
ТСМ GETITEM
ТСМ SETITEM
ТСМ INSERTITEM
ТСМ DELETEITEM
ТСМ DELETEALLITEMS
TCM_GETITEMRECT ТСМ GETCURSEL
ТСМ SETCURSEL
ГСМ HITTEST
0x1300 ТСМ FIRST+ 2
ТСМ FIRST+ 3
ТСМ FIRST+ 4
ТСМ FIRST+ 8
ТСМ FIRST+ 9
TCMJ4RST+ 10 TCMJ4RST + 11
ТСМ FIRST + 12
ТСМ FIRST + 13
Получить хэндл используемого совместно с закладками списка изображений, wParam и IParam = О, возвращается хэндл списка изображений
Связать список изображений с закладками, wParam = 0, IParam хэндлу списка изображений, возвращается хэндл предыдущего списка изображений
Получить число закладок, wParam = О, IParam — 0, возвращается число закладок
Получить информацию о закладке, wParam = индексу закладки, IParam указателю на структуру типа ТС JTEM, в которую будет записана информация
Установить атрибуты закладки, wParam = индексу закладки, IParam указателю на структуру типа TCJTEM, которая определяет атрибуты
Вставить закладку, wParam - индексу новой закладки, IParam = указателю на структуру тина TCJTEM, возвращается индекс новой закладки или -I Удалить закладку, wParam = индексу закладки, IParam = 0, возвращается TRUE при успешном выполнении Удалить все закладки, wParam — О, IParam = 0, возвращается TRUE при успешном выполнении
Получение индекса текущей закладки, wParam = 0, IParam = О Установка заданной закладки текущей, wParam = индексу закладки, возвращается индекс ранее выбранной закладки
Окончание таил, 45
Сообщение |
Значение |
Описание |
TCM_SETITEMEXTRA |
ТСМ FIRST + 14 |
Установка размера дополнительных |
|
|
данных для закладки, wParam — числу |
|
|
байт, выделяемых для дополнитель- |
|
|
ных данных |
ТСМ ADJUSTRECT |
ТСМ FIRST ^40 |
|
ТСМ SETTTEMSIZE |
ТСМ FIRST + 41 |
|
ТСМ REMOVEIMAGE |
ТСМ FIRST -.-42 |
|
ТСМ SETPADDING |
ТСМ FIRST + 43 |
|
ТСМ GETROWCOUNT |
ТСМ FIRST + 44 |
|
ТСМ GETTOOLTIPS |
ТСМ FIRST + 45 |
|
ТСМ SETTOOLTIPS |
ТСМ FIRST + 46 |
|
ТСМ GETCURFOCUS |
ТСМ FIRST + 47 |
|
ТСМ SETCURFOCUS |
ТСМ FIRST + 48 |
|
Сообщение TCM_SETITEMEXTRA может в работе окна с закладками использоваться только один раз и только до момента добавления первой закладки. Некоторые сообщения в этом списке, которые могут использовать параметры как в Unicode, так и в ANSI-кодировках, сами являются макросами. В таких случаях в графе «Значение» оставлен пропуск. Для примера ниже приведено описание макроса TCM_GETITEM:
#defme TCM_GETITEM/\
#deime TCM_GETITEMW
ffifdef UNICODE
#defme TCM_GETITEM
#eise
«define TCM_GETITEM
#endif
(TCM_FIRST + 5)
(TCM_FIRST + 60)
TCM GETITEMW TCM GETITEMA
В отличие от других элементов управления, для окна с закладками разработаны специальные макросы, которые облегчают работу с сообщениями. Вместо привычного SendMessage(...) можно использовать соответствующие макросы, о которых будет сказано дальше.
Имя каждого макроса образуется из имени сообщения:
1. От имени сообщения отбрасывается префикс ТСМ_.
2. Все слова оставшейся части изменяются таким образом, что прописной остается только первая буква слова, а все остальные делаются строчными, например, Getltem, Insertltem и т. д.
3. К полученному добавляется префикс TabCtrl_, например, TabCtrl Getltem, TabCtrl Insertltem.
Сообщение, которое посылается тем или иным макросом, определяется именем этого макроса. Каждый макрос может содержать один, два или три параметра. Число аргументов определяется очень просто - (число параметров сообщения, не равных нулю) + I. Если у сообщения wParam и IParam равны 0, то у макроса определен только первый аргумент, если определен только wParam - макрос требует наличия двух аргументов. Первым аргументом макроса всегда является хэндл окна, которому посылается сообщение, т. е. хэндл окна с закладками. Второй и третий аргументы (при необходимости) - это wParam и IParam сообщения соответственно. К примеру, макрос TabCtrl_DeleteAHItems() имеет один аргумент, макрос TabCtrl Deleteltem() - два аргумента, TabCtrl Insertltem() - все три аргумента.
Итак, с управляющими сообщениями и макросами все ясно. А как обстоит дело с получением информации о том, что выбрана одна из закладок'.' Если пользователь что-то сделал с закладкой, то закладка посылает родительскому окну сообщение WMJNOTIFY, при этом wParam этого сообщения содержит идентификатор элемента управления, a
IParam
- указатель на структуру типа NMHDR. Эта структура описана в файле winuser.h и имеет вид, приведенный ниже:
typedef struct lagNMHDR i
HWND hwmlFrom;
UINT idFrom;
HINT code: //NM_code } NMHDR; typedcfNMHDR FAR * LPNMHLJR:
Первое поле этой структуры - hwndFrom - содержит хэндл элемента управления, который послал сообщение WMJNOTIFY. Второе поле -idFrom - идентификатор элемента управления. Третье поле - code - содержит код нотификации, т. е. код того действия, которое было произведено с элементом управления. В случае закладки это может быть один из двух кодов - TCN SF.LCHANGING или TCN SELCHANGE.
Сообщение с кодом TCN SELCHANGING посылается после того, как пользователь произвел действие, но до изменения состояния закладки. Это сообщение может быть использовано, скажем, для того, чтобы сохранить информацию, введенную пользователем в диалоговом окне, связанном с закладкой. После того, как состояние закладки изменилось, посылается сообщение TC'N_SELCHANGE. При получении этого сообщения программа может произвести какие-либо действия по формированию вновь отображае-,'ой страницы.
187
Но использование закладок именно тем и осложнено, что каждая из закладок обычно связана с диалогом. Другими словами, при выборе новой закладки старое диалоговое окно должно исчезнуть, а на его месте должно появиться новое. Так как каждая из закладок может быть выбрана в любой момент, то с закладками должны быть связаны немодальные диалоги. Для того чтобы отобразить впоследствии диалог, связанный с невыбранной закладкой, программа должна сохранять состояние диалога. В качестве примера приведена небольшая программа, иллюстрирующая работу с закладками. Эта программа создает три закладки. При размещении курсора мыши над любой из них возникает подсказка (помните, при разборе окон подсказок я обещал, что продемонстрирую их использование при описании закладок?). При выборе любой из закладок отображается окно диалога, связанное с этой закладкой. В программе используется файл ресурсов:
Dialog I DIALOG 2, 40, 250, 108
STYLE DSJDLOOK DS_CONTEXTHELP | WS_POPUP | WS_VISIBLE
FONT 8, "MS Sans Serif'
{
DEFPUSHBUTTON "OK". IDOK, 24, 65, 50, 14
CONTROL "This is a text in the first dialog", -1, "static". SSJ.EFT | WS_CHILD | WS VISIBLE, 20, 13,96,9
Dialog2 DIALOG 2, 40, 250, 108
STYLE DS_3DLOOK DS_CONTEXTHELP
FONT 8. "MS Sans Serif"
WS POPUP I WS VISIBLE
DEFPUSHBUTTON "OK", IDOK, 24, 65, 50, 14
CONTROL "This is a text in the second dialog", -1, "static", SS_LEFT | WSJTHILD | WS VISIBLE, 20, 13, 120.9
Dialog3 DIALOG 2, 40, 250, 108
STYLE DS_3DLOOK | DSJTONTEXTHELP
FONT 8, "MS Sans Serif
WS POPUP I WS VISIBLE
DEFPUSHBUTTON "OK", IDOK, 24, 65, 50, 14
CONTROL "This is a text in the third dialog", -1, "static", SS_LEFT | WS_CHILB WS VISIBLE, 20, 13,96,9
Ниже приводится текст программы:
#includc <windows.h> ^include <commctrl.h> ^include <stdio.h>
188
HINSTANCE hlnst; HWND hWnd;
I.RESULT CALLBACK TabControlWndProc ( HWND. UINT, UINT, LONG ); BOOL CALLBACK DialogProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain (HINSTANCE hlnstance, HINSTANCE hPrevInstance,
LPSTR IpszCmdParam, int nCmdShow ) }
WNDCLASS WndClass ;
MSG Msg;
char szClassName[] = "TabControl";
hlnst = hlnstance; /* Registering our window class */ /* Fill WNDCLASS structure */
WndClass.stylc = CSJTREDRAW | CS_VREDRAW;
WndClass.lpfnWndProc = TabControlWndProc;
WndClass.cbClsExtra = 0;
WndClass.cbWndExtra = 0;
WndClass. hlnstance ~ hlnstance ;
WndCIass.hlcon = Loadlcon (NULL.IDI_APPLICATION);
WndClass.hCursor = LoadCursor (NULL, IDC_ARROW);
WndClass.hbrBackground = (HBRUSH) GetStockObjcct (LTGRAY_BRUSH);
WndClass. Ips/McmiNamc = "";
WndClass. IpszClassName = szClassName;
if ( !RegisterClass(&WndClass) ) {
McssageBox(NULL,"Cannot register class". "Error", MB__OK); return 0;
liWnd = CrcateWindow(szClassName, "Tab Control Demo Program",
WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME & ~WS_MAXIMIZEBOX,
CW USEDEFAULT, CW_USEDEFAULT, 400, 300, NULL, NULL, hfnstance,NULL); ifllhWnd) {
Message I.iox(NULL, "Cannot create window", "Error", MB_OK); return 0;
fnitC'oinmonControls(); /* Show our window */
ShowWmdow(hWnd,nCmdShow); UpdateWindow(hWnd);
/* Beginning of messages cycle */
while(GetMessage(&Msg, NULL, 0, 0))
{
TranslateMessage(&Msg); DispatchMessage(&Msg);
} return Msg.wParam;
LRESULT CALLBACK TabControIWndProc (HWND hWnd, UINT Message,
UINT wParam, LONG IParam )
{
HWND hTabControlWnd; RECT Rect; LPNMHDR IpNMHdr; LPTOOLTIPTEXT IpTooITipText; static HWND hDlg = 0; int nTab; TC_ITEM TCJtem;
switch(Message)
{
case WM_CREATE: GetClientRect(hWnd, &Rect);
hTabControlWnd = CreateWindow(WC_TABCONTROL, "", WS_ VISIBLE |
WS_TABSTOP | WS_CHILD | TCS_TOOLTIPS, 0, 0, Rect.right, Rect.bottom, hWnd, NULL, hlnst, NULL); TCJtem.mask = TCIFJTEXT; TC_Item.iImage = -1 ; TC_Item.pszText = "The first dialog"; TabCtrl_InsertItem(hTabControlWnd, 0, &TC_Item); TC_Item.pszText = "The second dialog"; TabCtrl_InsertItem(hTabControlWnd, 1, &TCJtem); TC_Item.pszText = "The third dialog"; TabCtrl_InsertItem(hTabControlWnd, 2, &TC_Item); hDlg = CreateDialog(hInst, "Dialog 1", hTabControlWnd, DialogProc); return 0;
case WMJMOTIFY: IpNMHdr = (LPNMHDR) IParam; switch(lpNMHdr->code) {
case TTN_NEEDTEXT: IpToolTipTcxt = (LPTOOLTIPTEXT) IParam; sprintf(lpToolTipText->lpszText, "Tip about tab No %d",
lpToolTipText->hdr.idFrom); break; caseTCN SELCHANGE:
if(hDlg)
Destroy Window(hDlg);
nTab = TabCtrl_GetCurSel( (HWND) lpNMHdr->hwndFrom); switch(nTab)
{
case 0:
hDlg = CreateDialog(hInst, "Dialog 1", hTabControlWnd, DialogProc); break; case 1:
hDlg = CreateDialog(hInst, "Dialog2", hTabControlWnd, DialogProc); break; case 2:
hDlg = CreateDialog(hInst, "Dialog3", hTabControlWnd, DialogProc); break; } break;
x t
return 0;
case WM_DESTROY: PostQuitMessage(O); return 0;
} return DefWmdo\vProc(hWnd,Message,wParam, IParam);
BOOL CALLBACK DialogProc(HWND hDlg, UINT Message,
WPARAM wParam, LPARAM IParam) {
switch(Message) {
case WM_COMMAND: PostQuitMessage(O); return I;
} return 0;
Вид окна, создаваемого программой, приведен на рис. 18.
В этом примере не была использована одна из возможностей, которая резко улучшает внешний вид программы. В закладках можно использовать изображения из созданного ранее списка изображений. Я этого не сделал. В качестве упражнения рекомендую читателю самостоятельно добавить изображения к закладкам.
Ещё один элемент управления рассмотрен. А сколько их осталось? Рассмотрим еще один интересный и достаточно сложный элемент управления, который называется окно просмотра деревьев (Tree View control).
В Tab Control Demo Program
The first dialog The second dialog The third dialog j
.'--•""• -This is: a text in the fiist dialog
OK
Рис. 18. Окно с закладками
РАБОТА С ОКНОМ ПРОСМОТРА ДЕРЕВЬЕВ
Окно просмотра деревьев используется для просмотра списка объектов, имеющего иерархическую структуру, как, например, файлы на диске. Ярким примером использования окна просмотра деревьев является Explorer, который использует окна этого типа для отображения структуры информации на диске.
И этот элемент управления не имеет специальной функции для своего создания, т. е. для того, чтобы создать окно просмотра деревьев, программа должна использовать функции CreateWindowQ или CreateWindowEx(). При этом в качестве имени класса создаваемого окна необходимо использовать макрос WC_TREEVIEW, который описан в файле commctrl.h следующим образом:
#ifdef_WIN32
#define WCJTREEVIEWA
#define WC_TREEVTEWW
#ifdef UNICODE
#define WC_TREEVir-W
#else
"SysTrecVicw32" E"SysTrecVicw32"
WC TREEVIEWW
192
tfdetinc WC_TREEVIEW
#endif
#clse
«define WC_TREEVIEW
#endif
WC TREEVIEWA
"SysTreeView"
При создании окна просмотра деревьев можно использовать несколько стилей, разработанных специально для окон данного типа. Все эти стили приведены в табл. 46.
На некоторых из этих стилей остановимся более подробно. Начнем со стиля Т VS_H AS BUTTONS. Если читатель запустит Explorer, то увидит, что у директорий, содержащих внутри себя еще что-то, справа есть небольшой квадратик. Это и есть те кнопки, наличие которых и предполагает стиль. Если элементы следующего для директории уровня отображаются, то внутри квадратика содержится знак «+», в противном случае -знак «-». Для сворачивания и разворачивания ветви дерева необходимо щелкнуть кнопкой мыши на этой кнопке. К сожалению, этот стиль не добавляет кнопки к элементам наивысшего уровня. Для того чтобы появились кнопки и у этих элементов, необходимо комбинировать стили TVS_HASLINES, TVS_LINESATROOT и TVS_HASBUTTONS.
При использовании стиля TVSJHASLINES есть одна особенность. Линиями соединяются только родительские и дочерние элементы.
Таблица 46. Стили окна просмотра деревьев
Стиль
Значение
Описание
TVS_HASBUTTONS
TVSJ-IASLINES
TVSJJNESATROOT TVS_EDITLABELS TVSJ3ISABLEDRAGDROP TVS SHOWSELALWAYS
0x0001
0x0002
0x0004 0x0008 0x0010 0x0020
К элементам, имеющим дочерние элементы, слева добавляютея небольшие кнопки, позволяющие раскрывать и закрывать список подчиненных элементов Дочерние элементы списка соединяются с родительским элементом линиями, элементы высшего уровня не соединяются Элементы высшего уровня соединяются друг с другом
Названия элементов списка могут быть изменены
Запрещает операции drag-and-drop с элементами списка
Выбранные элементы остаются таковыми даже тогда, когда окно теряет фокус
193
Таблица 47. Сообщения, посылаемые окнам просмотра деревьев
Сообщение
Опиеание
TV_FIRST
TMVJNSERTTTEM
TVM_DELETEITEM
TVM_EXPAND
TVM_GETITEMRECT
TVM_GETCOUNT
TVM_GETINDENT TVM_SETINDENT TVM GETIMAGEEIST
TVM_SETIMAGELIST TVM GETNEXTITEM
TVM_SELECTITEM
TVM_GETITEM
TVM_SETITI-M
TVMJZDITLABEL
TVM_GETEDITCONTROL
TVMJ3ETVISIBLECOUNT
TVMJ1ITTEST
TVM_CREATEDRAGIMAGE
TVM_SORTCHILDREN
TVM_ENSUREVISIBLE TVM SORTCHILDRENCB
tvm endeditlabelnow tvm' "getisearchstring
TV_F1RST -i- 1 TV_FIRST + 2 TV_FIRST + 4
TVJTRST + 5
TV_FIRST + 6 TV_FIRST + 7 TV FIRST 1-8
TVJTRST + 9 TV_FIRST+ 10
TV FIRST + 11
TV FIRST- 15 TVJ-TRST + 16
TV_FIRST+ 17 TVJFIRST- 18 TVJ4RST+ 19
TV_FIRST + 20 TV FIRST+ 21
TV FIRST + 22
Вставка элемента в список Удаление элемента из списка «Распахнуть» или «свернуть» элемент Получить ограничивающий прямоугольник для элемента списка Вернуть количество элементов в списке
Получить значение отступа Установить значение отступа Получить хэндл списка изображений, связанного с окном просмотра деревьев
Связать список изображений с окном просмотра деревьев Получить дополнительную информацию об элементе, находящемся с текущим в определенных отношениях (следующий, родительский, первый дочерний и так далее) Сделать элемент' текущим Получить информацию об элементе Установить параметры элемента Изменитьтекст элемента
Получить число видимых элементов списка
Отсортировать дочерние элементы в алфавитом порядке
Отсортировать дочерние элементы в соответствии с критерием, определенным программой
Элементы наивысшего уровня друг с другом не соединяются, т. е. визуально отображаются несколько отдельных деревьев. Если пользователь хочет, чтобы отобразилось единое дерево, необходимо указать комбинацию стилей TVS J1ASLINES и TVS LINESATROOT.
Раз окно просмотра деревьев является окном (прошу извинить меня за тавтологию), то обмен информацией с этим окном и управление им
194
осуществляется е помощью сообщений. Список всех возможных сообщений, используемых при работе с окном просмотра деревьев, приведен в табл. 47.
Как и в случае с закладками, посылка сообщений дереву просмотра деревьев может быть осуществлена с помощью макросов. Имена макросов для сообщений формируются точно так же, как и в случае закладок. Единственное отличие состоит в том, что в качестве префикса используется не TabCtrl, a Tree View.
А теперь, после краткого знакомства с сообщениями, применяемыми в работе с окнами просмотра деревьев, рассмотрим некоторые из этих сообщений, наиболее части применяемые в прикладных программах.
Для того чтобы вставить элемент в синеок, необходимо послать окну просмотра деревьев сообщение TVMJNSERTITEM или, что то же самое, использовать макрос TreeView Insertltem. При этом параметр wParam должен быть равным 0, a IParam должен содержать указатель на структуру типа TVJNSERTSTRUCT. Эта структура описана в файле commctrl.h так:
typedef struct JTVJNSERTSTRUCTA (
HTREEITEM hParent:
HTREEITEM hlnsertAt'tcr;
TVJTEMA item; } TVJNSERTSTRUCTA, FAR *LPTV_INSERTSTRUCTA;
typedef struct _TV INSF.RTSTRUCTW {
HTREEITEM hParent;
HTREEITEM hlnsertArter;
TVJTEMW item; } TVJNSERTSTRUCTW, FAR *LPTVJNSERTSTRUCTW;
#ifdef UNICODE
«define TVJNSERTSTRUCT
Adeline LPTVJNSERTSTRUCT
#clse
#deime TVJNSERTSTRUCT Adeline LPfvjNSERTSTRUCT
#endif
TVJNSERTSTRUCTW LPTVJNSERTSTRUCTW
TV INSERTSTRUCTA LPTV INSERTSTRUCTA
Поле первое - hParent - хэндл родительского элемента. Если этот элемент равен TVI_ROOT или NUEE, то элемент не имеет родителей и добавляется в список наивысшего уровня.
Второе поле - hlnsertAfter - определяет хэндл элемента, после которого вставляется новый элемент. Помимо этого, поле может принимать следующие значения:
TVI_FIRST - элемент вставляется в начало списка;
TVI_LAST - элемент вставляется в конец списка;
TVI~SORT - элемент вставляется в список в алфавитном порядке.
Третье поле - item - описывает непосредственно вставляемый элемент. Он представляет собой очередную структуру (структура в структуре!). Тип этой структуры - TVJTEM - описан в файле commctrl.h:
typedef struct JTVJTEMA {
UINT mask;
HTREEITEM hltem;
UINT state;
UINT stateMask;
LPSTR pszText;
int cchTextMax;
int ilmage;
int iSelectedlmage;
int cChildren;
LPARAM IParam; } TVJTEMA, FAR "LPTVJTEMA;
typedef struct _TV_ITEMW {
UINT mask;
HTREEITEM hltem;
UINT state;
UINT stateMask;
LPWSTR pszText;
int cchTextMax;
int ilmage;
int iSelectedlmage;
int cChildren; LPARAM IParam; } TVJTEMW, FAR *LPTV_ITEMW;
Таблица 48. Флаги, определяющие в каком поле структуры типа TV_ITEM содержится (или куда должны записываться) информация
#ifdef UNICODE
#defme TVJTEM
#definc LPTVJTEM
#else
#definc TVJTEM tfdefine LPTVJTEM
#endif
TVJTEMW LPTVJTEMW
TVJTEMA LPTV ITEMA
Теперь наберемся сил и рассмотрим структуру типа TVJTEM. Это поможет нам понять, что представляет собой элемент списка. Кроме этого, при её рассмотрении мы выясним, какого рода информацию об элементе списка можно получить, так как структура именно этого типа используется и для получения информации об элементе.
Поле |
Значение |
Описание |
TVIF TEXT |
0x000 1 |
Информация содержится в полях pszText и |
|
|
cchTextMax |
TVIF IMAGE |
0x0002 |
Информация содержится в поле ilmage |
TVIF PARAM |
0x0004 |
Информация содержится в поле IParam |
TVIF_STATE |
0x0008 |
Информация содержится в полях state и |
|
|
staleMask |
TVIF HANDLE |
0x00 1 0 |
Информация содержится в поле hltem |
TVIF SELECTEDIMAGE |
0x0020 |
Информация содержится в поле iSelectedltem |
TVIF CHILDREN |
0x0040 |
Информация содержится в поле cChildren |
Дело за малым - выяснить, что может храниться в каждом из этих полей.
hltem - хэндл элемента, информация о котором содержится в структуре.
Поле state определяет флаги состояние элемента, а поле stateMask -какое состояние элемента должно быть установлено или получено. Поле state может принимать значения, приведенные в табл. 49.
Т а б л и ц а 49. Флаги, определяющие внешний вид и состояние окна просмотра деревьев
|
Состояние |
Значение |
Описание |
|
TVIS_FOCUSED |
0x000 1 |
Элемент получил фокус ввода, т. е. он обрам- |
|
|
|
лен стандартным прямоугольником |
|
TVIS SELECTED |
0x0002 |
Элемент выбран |
|
TVIS CUT |
0x0004 |
Элемент выбран для операции копирования |
|
TVISJ3ROPHILITED |
0x0008 |
Элемент выбран как место назначения для |
|
|
|
операции drag-and-drop |
I |
TVIS BOLD |
0x00 1 0 |
Текст элемента написан жирным шрифтом |
|
TVISJZXPANDED |
0x0020 |
Дочерние элементы списка видны, т. е. |
|
|
|
элемент «распахнут» |
|
TVIS EXPANDEDONCE |
0x0040 |
Элемент «распахивался» минимум один раз |
|
TVIS OVERLAYMASK |
OxOFOO |
|
|
TVIS STATEIMAGEMASK |
OxFOOO |
|
|
TVIS USERMASK |
OxFOOO |
То же, что и предыдущее |
Если структура используется для получения информации об элементе, поле IpszText содержит указатель на буфер, в который будет записан текст элемента. В этом случае поле cchTextMax определяет размер выделенного буфера.
Поля ilmage и iSelectedlmage определяют индексы изображений, использующихся с элементом, для прорисовки невыбранного и выбранного элементов. Если значение поля равно IJMAGECALLBACK, то за формирование изображения отвечает родительское окно.
Поле cChildren имеет значение, равное единице, в том случае, когда у элемента есть дочерние элементы. В противном случае значение этого поля равно нулю.
И наконец, последнее поле - IParam - хранит данные, связанные с элементом. Об этих данных мы уже говорили при обсуждении окон списков.
На этом заканчивается рассмотрение параметров сообщения TVMJNSERTITEM. Много ли еще подобных структур ждет нас? Конечно^ работает все это эффективно и эффектно (самоё Wmdows'95 и Windows NT тому подтверждение!), но, по-моему, иногда фирме Microsoft неплохо было бы подумать и о тех, кто будет изучать ее творения! ( © )
Мы изучили только параметры сообщения. Еще нужно узнать, что возвращает функция, с помощью которой было послано данное сообщение. А возвращает она при успешном завершении хэндл элемента, а в случае неудачи - NULE.
Те же действия, как читатель уже догадался, могут быть произведены с помощью макроса TreeViewJmsertItem().
После изучения структуры типа TVJTEM, рекомендую читателю самостоятельно изучить работу сообщений TVM GETITEM и TVM SETITEM. В них тоже используется структура этого типа, поэтому никаких сложностей встретиться не должно.
В каждый момент элемент, у которого есть дочерние элементы, может быть «свернут» или «распахнут». Элемент автоматически меняет свое состояние либо при двойном щелчке мышью на нем, либо при щелчке мышью на кнопке элемента, если, конечно, у элемента установлен стиль TVSJ-IASBUTTONS. Программа может изменять состояние элемента с помощью посылки окну просмотра деревьев сообщений TVM_EXPAND или, что то же самое, обращением к макросу TreeView_Expand(). IParam этого сообщения определяет хэндл элемента, с которым производится
198
действие, a wParam определяет, что нужно произвести с элементом. В данном случае wParam
может принимать значения, приведенные в табл. 50.
В случае изменения состояния элемента окно просмотра деревьев посылает родительскому окну сообщение WMJNOTIFY, посредством которого передает информацию о том, что состояние элемента каким-то образом изменилось. В случае «распахивания» или «сворачивания» элемента родительскому окну посылаются сообщения TVN_ITEMEXPANDING до «распахивания» или «сворачивания» и TVNJTEMEXPANDED - после.
В том случае, когда элемент «распахнут», дочерние элементы списка отображаются смещенными вправо относительно родительского элемента. Получить значение смещения или установить это значение можно с помощью сообщений TVM GETINDENT и TVM_SETINDENT.
Программа может дать пользователю возможность изменить текст элемента, послав этому элементу сообщение TVM_EDITLABEL. В этом случае родительское окно получает нотификационные сообщения TVN BEGINLABELEDIT перед началом редактирования и TVM_ENDLABELEDIT после его окончания.
При необходимости программа может отсортировать элементы списка в алфавитном порядке, послав окну сообщение TVM_SORTCHILDREN. Если необходима сортировка списка по какому-то другому критерию, то тогда окну просмотра деревьев необходимо послать сообщение TVM_SORTCHILDRENCB, указав в качестве IParam адрес процедуры сортировки.
Т а б л и ц а 50. Действия, производимые с элементом окна просмотра деревьев при посылке окну срообшепия TVM_EXPAND
wParam |
Значение |
Описание |
TVR COLLAPSE |
0x000 1 |
Элемент «сворачивается» |
TVE EXPAND |
0x0002 |
Элемент «распахивается» |
TVE TOGGLE |
0x0003 |
Если элемент «свернут», то он |
|
|
«распахивается», и наоборот |
TVE COLLAPSHRESET |
0x8000 |
Элемент «сворачивается», при этом |
|
|
дочерние элементы удаляются, действует |
|
|
только в паре с TVE COLLAPSE |
199
выбранного элемента. Для того чтобы сменить выбор, программа должна послать окну сообщение TVM_SELECTITEM.
Для того чтобы получить информацию об элементе, необходимо послать окну сообщение TVM_GETITEM. Сообщение TVM_GETNEXTITEM, вопреки своему названию, позволяет получить информацию не только о следующем за текущим элементе, но и о других элементах, находящихся в определенных отношениях с текущим.
Сообщение TVM_GETCOUNT позволяет получить число элементов списка, а сообщение TVM GETVISIBLECOUNT - число элементов списка, видимых в данный момент.
Для того чтобы связать с окном просмотра деревьев список изображений, нужно воспользоваться сообщением TVM_SETIMAGEEIST. Сообщение TVM_GETIMAGELIST позволяет получить хэндл списка изображений, связанного с окном просмотра деревьев.
Для того чтобы пояснить то, о чем шла речь в этом разделе, ниже приведена демонстрационная программа. В ней элементами наивысшего уровня являются десятки от 0 до 100 (0, 10, 20... 100), а элементами второго уровня - числа, располагающиеся на числовой оси между целыми десятками. Вот текст этой программы:
#include <windows.h>
#include <commctrl.h> ^include <stdio>
HINSTANCE hlnst;
LRESULT CALLBACK TrceVicwWndProc ( HWND, UINT, UINT, LONG );
int WINAPI WinMain (HINSTANCE hlnstance, HINSTANCE hPrcvInstancc, LPSTR IpszCmdParam, int nCmdShow )
{
HWND hWnd ; WNDCLASS WndClass ; MSG Msg; char szClassName[] = "TreeView";
hlnst = hlnstance; /* Registering our window class */ /* Fill WNDCLASS structure */
WndClass.style = CS_HREDRAW | CSJVREDRAW;
WndClass.IpfnWndProc = TrecViewWndProc;
WndClass.cbClsExtra = 0;
WndClass.cbWndExtra = 0;
WndClass.hlnstance = hlnstance ;
WndClass.Mcon = Loadlcon (NULL,IDI_APPLICATION);
WndClass.hCursor = LoadCursor (NULL, IDC_ARROW); WndClass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH); WndClass.IpszMenuName = ""; WndClass.IpszClassNamc = szClassName;
if ( IRegisterClassf&WndClass))
>
\
MessageBox(NULL,"Cannot register class","Error",MB_OK); return 0;
hWnd = CreateWindow(szClassName, "TreeView Demo Program",
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hlnstance.NULL); if(!hWnd) i
MessageBox(NULL,"Cannot create window","Error",MB_OK); return 0;
}
InitCommonControlsO; /* Show our window */
ShowWmdo\v(hWnd,nCmdSliow); UpdatcWindow(hWnd);
/* Beginning of messages cycle */
while(GctMcssage(&Msg, NULL, 0, 0)) {
TranslateMessage(&Msg); DispatchMessage(&Msg);
} return Msg.wParam;
LRESULT CALLBACK TreeViewWndProc (HWND hWnd, UINT Message,
UINT wParam, LONG IParam )
static HWND hTreeView;
RECT Rcct;
TV_INSERTSTRUCT TV InsertStruct;
TVJTEM TVJtem;
int i. j;
charcBufferfl2]:
swileh(Message) { caseWM CREATE:
201
GetCiientRect(hWnd, &Rect);
hTreeView = CreatcWindow(WC_TREEVIKW,"",
WSJVISIBLE j WS JTABSTOP | WS_CHILD | TVSJIASLINES j TVSJiASBUTTONS | TVS_LINESATROOT, 0, 0. Rcct.right, Rcct.bottom, hWnd, NULL, hlnst, NULL);
TVJnsertStruct.MnsertAHcr = TVI_ LAST;
TVJtem.mask = TVIF_TEXT;
lor(i"0; i< 100; i+= 10)
!
TVJnsertStruct.hParent - TVIJIOOT; TVJtcm.pszText = itoa(i, cBuffer, 10); TVJnsertStruct.itcm = TVJtcm; TV_InscrtStruct.hParcnt = TreeViewJnsertItem(hTrceView,
&TV_InscrtStrucl); for(j = l;j < 10;j++)
{
TVJtcm.pszText = iloa(i + j, cBufTer, 10); TVJnsertStruct.itcm = TVJtcm; TrceViewJnsertItcm(hTreeView, &TVJnscrtStruct);
return 0;
case WMJ5ESTROY: PostQuitMessage(O); return 0;
return DefWindowProc(hWnd,Message,wParam. IParam);
На рис. 19 показан вид создаваемого программой окна. Как всегда, самое трудное (©) - добавить изображения в список - я оставляю читателю в качестве упражнения.
РЕЕСТР
Реестр - это база данных, определенная в Windows, которая используется приложениями для того, чтобы хранить в ней конфигурационные данные.
Надеюсь, что читатель помнит массу файлов с расширением .ini в подавляющем большинстве случаев, которые приложения, разработанные для Windows более старых версий, использовали для хранения данных. Там хранились данные, нужные только данному приложению для работы. Для работы с ними использовались функции, имена которых содержали строку PrivateProfile. В Win32 для хранения подобных данных разработан совершенно новый механизм, получивший название реестра (registry -
202
реестр, регистратура). Этот механизм, во-первых, облегчил работу с данными приложений, и, во-вторых, упростил работу с ними. При этом следует заметить, что хотя никаких особых ограничений для хранимой в реестре информации нет, хранить в нем следует только инициализацион-ные и конфигурационные данные. В help'e no Win'32 API записано, что если данные превышают один килобайт, их целесообразно хранить в отдельном файле, а не в регистре. Мне кажется, что в большинстве случаев следует поступать именно так. Какой же должна быть программа, чтобы данные инициализации и конфигурационные данные занимали бы 1 кбайт!
В TreeView Demo Program
в о
•: Ш- 10
: и 20 , и зо
:: Ш 40 ; Й-50
В 60 . Ш- 70
:ш 80 I а эо
Рис. 19. Окно просмотра деревьев с одним "распахнутым" и девятью "нераспахнутыми" элементами
СТРУКТУРА РЕЕСТРА
Данные в реестре хранятся в виде иерархического дерева. Каждый элемент этого дерева называется ключом. Каждый ключ может содержать как ключи более низкого уровня, гак и конечные элементы данных. При необходимости приложение открывает ключ и использует данные, сохра-
203
ненные в нем. Каждый ключ может содержать любое число данных (конечно, все ограничивается объемом памяти), при этом данные могут быть в произвольном формате. Учитывая то, о чем я говорил выше, если данных очень много и они хранятся в отдельном файле, то в реестре может быть создан ключ, который ссылался бы на этот файл. Имена ключей не могут содержать обратные слеши (\), пробелы, звездочки ( * ) и вопросительные знаки. Имя ключа не должно совпадать с именами ключей, располагающихся выше него по иерархии.
РАБОТА С РЕЕСТРОМ
СОЗДАНИЕ И ОТКРЫТИЕ КЛЮчЕЙ
Для того чтобы работать с данными реестра, приложение должно сначала создать собственный ключ или открыть ключ, созданный ранее. Для создания ключа приложению необходимо вызвать функцию RegCreateKeyExQ, которая описана в файле winreg.h так:
WINADVAPI LONG APIENTRY RegCrcatcKcyExA (HKEY hKey,
LPCSTR IpSubKcy, DWORD Reserved, LPSTR IpClass, DWORD dwOptions, REGSAM samDesircd,
LPSECURITY^ATTRIBUTESIpSecurityAttnbutes, PHKEY phkResult, LPDWORD IpdwDisposition); WINADVAPI EONG APIENTRY RegCreateKeyExW (HKEY hKey,
LPCWSTR IpSubKey, DWORD Reserved, EPWSTR IpClass, DWORD dwOptions, REGSAM samDesircd,
LPSECURITY_ATTRIBUTESlpSecurityAttributcs, PHKEY phkResult, LPDWORD IpdwDisposition);
#ifdef UNICODE
#defme RegCreateKeyEx RegCreateKeyExW
#elsc
#define RegCreateKeyEx RegCreateKeyExA
#cndif// iUNICODE
Опять функция с массой аргументов! Первый аргумент - hKey -хэндл ранее открытого ключа или одно из следующих значений: HKEY_CLASSES_ROOT; HKEY_CURRENT_USER; HKEY_LOCALJVIACHINE; HKEY USERS.
Здесь нужно остановиться и рассмотреть, что за значения были приведены выше.
При инсталляции Windows создаются четыре ключа. Их имена совпадают со значениями, приведенными выше. Другими словами, эти ключи являются основой для создания иерархии ключей.
Ключи, находящиеся по иерархии ниже первого из предопределенных ключей, HKEY_LOCAL_MACHINE, определяют физическое состояние компьютера, включая данные о типе шины, системной памяти, инсталлированном аппаратном и программном обеспечении.
Ключи, находящиеся по иерархии ниже HKEY_CLASSES_ROOT, определяют типы (или классы) файлов и свойства, ассоциированные с этими классами. Свойства классов определяются только программистом. Обычно эти свойства применяются при работе приложений, использующих внедрение и связывание объектов, а также приложений, использующих среду Windows (shell applications). К примеру, при открытии файлов в Explorer'e используются свойства файлов, записанные в реестре.
Ключи, подчиненные HKEY USERS, определяют конфигурацию по умолчанию при подключении нового пользователя на локальной машине и конфигурацию текущего пользователя.
И наконец, ключи, подчиненные HKEY_CURRENT_USER, определяют установки, сделанные текущим пользователем, касающиеся переменных окружения, данных о принтерах, сетевых подключениях и т. д. Кроме этого, в этой ветви дерева хранятся установки, сделанные конкретными приложениями.
Возвращаясь к аргументам функции RegCreateKeyExQ, я теперь могу сказать, что перед созданием нового ключа необходимо продумать, в какую ветвь дерева необходимо включить новый ключ. Если новый ключ необходимо создать подчиненным ключу более низкого уровня, то определенным образом можно пройти по дереву и найти тот ключ, который необходим. Кроме этого, ключ, хэндл которого указан в первом аргументе, должен быть открыт с атрибутом доступа KEY_CREATE_SUB_KEY. Об атрибутах доступа мы поговорим при рассмотрении шестого аргумента функции.
Вторым аргументом - IpSubKey - является указатель на строку, содержащую имя создаваемого ключа. Создаваемый ключ будет подчиненным ключа, хэндл которого указан в первом аргументе.
Третий аргумент - Reserved -зарезервирован и должен быть равным нулю.
Четвертый аргумент - IpClass - указатель на строку, определяющую класс создаваемого ключа.
Очередной, пятый аргумент - dwOptions, определяет опции создаваемого ключа. Этот аргумент может принимать одно из значений -REGJ3PTION VOLATILE или REG_OPTIONNON_VOLATILE. ' В Windows'95 первое значение не используется. Второе значение указывает, что при перезагрузке системы значение этого ключа сохраняется, т. е. информация сохраняется в файле, а не в памяти.
Следующий, шестой аргумент - samDesired, определяет маску доступа к ключу. Этот параметр представляет собой битовую шкалу и может быть комбинацией флагов, приведенных в табл. 51.
Седьмой аргумент - IpSecurityAttributes - указатель на структуру типа SECURITY_ATTRIBUTES, которая определяет атрибуты безопасности создаваемого ключа. К сожалению, Windows'95 не поддерживает безопасность, поэтому этот параметр игнорируется.
Туда, куда указывает восьмой аргумент - phkResult - записывается хэндл созданного ключа.
Т а б .ч и ц а 51. Флаги, составляющие маску доступа к ключу
Флаг
Значение
Описание
KF.Y_QUERY_ VALUE
KEY_SET_VALUE
KEY_CREATE_SUB_KEY
KEY_ENUMERATE_SUB_KEY
KEY_NOTIFY
KEY_CREATE_LINK
KEY READ
KEY WRITE
KEY_EXECUTE KEY ALL ACCESS
0x0001 0x0002 0x0004 0x0008 0x0010 0x0020
Права 'запрашивать данные подключен
Права устанавливать данные подключен
Права создавать подключи
Права перебирать подключи
Права изменять нотификацию
Права создавать символическую связь
(STANDARD_RIGHTS_READ |
KEY_QUERY_VALUE
KEYJiNUMERATE SUBJCEYS |
KEY_NOTIFY) & (-SYNCRONIZE)
(STANDARD_RIGHTS_WRITE
KEY_SET_VALUE |
KEY_CREATE_SUB_KEY) &
(-SYNCRONIZE)
KEY READ & (-SYNCRONIZE)
(STANDARD_RIGHTS_ALL j
KEY_QUERY_VALUE|
KEY_SET_VALUE [
KEY_CREATE_SUB_KEY |
KEY_ENUMERATE_SUB_KEYS |
KEY_NOTIFY j KEY_CREATE_LINK) &
(-SYNCRONIZE))
206
И наконец, последний, девятый аргумент - IpdwDisposition - указывает место, куда будет записана информация о том, что произошло с ключом. Дело в том, что если с помощью этой функции производится попытка создать ключ, который уже существует, то ключ не создается, а просто открывается. Поэтому приложению необходимо знать, что произошло при создании ключа. Если ключ был создан, то в поле, определяемое IpdwDisposition, записывается значение REG_CREATEDJNEWJCEY. В том случае, если ключ существовал и был открыт, записываемое значение равно REG OPENED EXISTING KEY. Это поле может быть использовано и для того, чтобы узнать, не открыт ли ключ другим приложением. Открытый ключ доступен только тому приложению, которое создало его. Таким образом, если приложение открывает заведомо существующий ключ и получает в ответ значение REG_CREATED_NEW KEY, то можно сделать вывод о том, что ключ занят другим приложением.
Функция возвращает значение ERROR SUCCESS в том случае, если ключ создан или открыт удачно. Любое другое значение является свидетельством того, что при создании или открытии ключа встретилась ошибка.
Итак, считаем, что ключ мы создали. А что необходимо сделать для того, чтобы не создать, а открыть существующий ключ? Для этого нужно вызвать функцию RegOpenKeyEx(), описание которой приведено ниже:
WINADVAPI LONG APIENTRY RcgOpenKeyExA (HKEYhKey,
LPCSTR IpSubKey, DWORD ulOptions, REGSAM samDcsired, PHKEY phkResult);
WINADVAPI LONG APIENTRY RegOpcnKeyExW (HKEY hKey,
LPCWSTR IpSubKey, DWORD ulOptions, REGSAM samDesired, PHKEY phkResult);
tfifdcfUNICODE
ffdeime RcgOpenKeyEx RegOpenKcyExW
#elsc
/'define- RcgOpcnKeyEx KcgOpenKeyExA
#cndil"// 'UNICODE
.Надеюсь, что читатель, сравнив описания обеих функций, разберется с аргументами открывающей ключ функции самостоятельно (небольшая подсказка - полю Reserved функции RegCreateKeyExQ соответствует поле ulOptions).
Так, создавать и открывать ключи мы научились. Теперь необходимо научиться ключи закрывать.
207
ЗАКРЫТИЕ КЛЮЧЕЙ И СОХРАНЕНИЕ ПРОИЗВЕДЕННЫХ
В НИХ ИЗМЕНЕНИЙ
Закрывается ключ с помощью функции RegCloseKeyO, описание которой, приведенное ниже, можно встретить в файле winreg.h:
WINADVAPI LONG APIENTRY RegCloseKey (HKEY hKey);
Единственным аргументом этой функции является хэндл закрываемого ключа. Но при выполнении этой функции читатель может встретиться с одной проблемой, незаметной с первого взгляда Дело в том, что данные из реестра на время работы с ними переписываются в кэш и записываются обратно на диск при выполнении функции RegFlushKeyO, описание которой имеет следующий вид:
WINADVAPI LONG APIENTRY RegFlushKey (HKEY hKey);
Другими словами, если вы не хотите, чтобы данные, которые вы изменили во время работы программы, были потеряны, перед закрытием ключа сбрасывайте на диск. С другой стороны, у программиста может появиться соблазн сбрасывать данные на диск достаточно часто. Так как RegFlushKeyO использует огромное количество системных ресурсов, то эту функцию нужно вызывать только в том случае, когда действительно в этом есть необходимость.
ДОБАВЛЕНИЕ ДАННЫХ К КЛЮЧАМ И УДАЛЕНИЕ
ДАННЫХ ИЗ КЛЮЧЕЙ
После того, как ключ создан, возникает необходимость добавить к ключу некоторые данные, которые будут использоваться программой. Для этого нужно вызвать функцию RegSetValueExQ. Описание этой функции, которое приведено ниже, взято из файла winreg.h:
WFNADVAPI LONG APIENTRY RegSetValueExA (HKEY hKcy,
LPCSTR IpValueName, DWORD Reserved, DWORD dwType, CONST BYTE* IpData, DWORD cbData); WINADVAPI LONG APIENTRY RegSetValueExW (HKEY hKey,
LPCWSTR IpValueName, DWORD Reserved, DWORD dwType, CONST BYTE* IpData,
DWORD cbData);
#ifdef UNICODE
#define RegSetValueEx RegSetValueExW
#else
#defme RegSetValueEx RegSetValueExA
#endif// IUNICODE
208
Первый аргумент - хэндл ключа, к которому добавляются данные. Второй аргумент - указатель на строку, содержащую имя добавляемых данных. Третий аргумент зарезервирован. Четвертый аргумент определяет тип информации, который будет сохранен в качестве данных. Этот параметр может принимать одно из значений, приведенных в табл. 52.
Таблица 52. Типы сохраняемой в реестре информации
I lapaMCip
Значение
Описание
REG_NONE
REG_SZ
REG_EXPAND_SZ
REG_BINARY REGJDWORD REG_LINK REG MULTI SZ
REG_RESOURCE_LIST
REG FULL RESOURCE_DESCRIPTOR
REG_RESOURCE_REQUIREMENTS LIST
REG_DWORD_LITTLE_ENDIAN
REG DWORD BIG ENDIAN
4
Тип данных не устанавливается Строка, оканчивающаяся нулем Строка со ссылками на переменные окружения (типа %РАТН%) Бинарные данные в любой форме Двойное слово Символическая связь Массив из нескольких строк, заканчивающихся нулями, который, в свою очередь, заканчивается двумя нулями Список драйверов устройств Список ресурсов в виде частей аппаратуры
То же, что и REG_DWORD То же, что и REGJ5WORD, но наиболее значащим в слове является младший байт
Пятый аргумент является указателем непосредственно на данные, которые будут сохранены. И наконец, шестой аргумент определяет размер данных, на которые указывает пятый аргумент. Все легко и просто, не так ли?
А удалить данные можно с помощью обращения к функции RegDeleteValueQ. Её описание приведено ниже:
WINADVAPI LONG APIENTRY RegDeleteValueA (HKEY hKey,
LPCSTR IpValueName);
WINADVAPI LONG APIENTRY RegDeleteValueW (HKEY hKey,
LPCWSTR IpValueNamc);
#ifdcfUNICODE
#define RegDeletcValue RegDeleteValueW «else
#defme RegDek-teValue RegDeleteValucA
#cndif// IUNICODE
Аргументы этой функции очевидны - хэндл ключа и указатель на строку с именем данных.
Но если данные записываются в реестр, то, наверное, их можно и нужно считывать из реестра. Поэтому сейчас мы рассмотрим вопрос о том, как происходит
ВЫБОРКА ДАННЫХ ИЗ РЕЕСТРА
Если прикладной программе нужно осуществить выборку данных из реестра, то для начала программа должна определить, из какой ветви дерева регистрации ей нужно выбрать данные. Естественно, что никаких функций для этого нет. При написании программы программист должен сам позаботиться об этом. После того как решение принято, начинается второй этап. Программа должна перебирать все ключи в этой ветви до тех пор, пока не найдет нужный ключ. Для этого приложение может воспользоваться функцией RegEnumKeyEx(). Как и всегда, обратимся к заголовочному файлу winreg.h для того, чтобы найти описание этой функции. Оно приведено ниже:
WINADVAPI LONG APIENTRY RegEnumKeyExA (HKEY hKey,
DWORD dwlndex, LPSTR IpName, LPDWORD IpcbName, LPDWORD IpReserved, LPSTR IpClass, LPDWORD IpcbClass, PFLETIME IpftLastWriteTime);
WINADVAPI LONG APIENTRY RegEnumKeyExW (HKEY hKey,
DWORD dwlndex, LPWSTR IpName, LPDWORD IpcbName, LPDWORD IpReserved, LPWSTR IpClass, LPDWORD IpcbClass, PFLETIME IpftLastWriteTime);
#ifdef UNICODE
#defme RegEnumKeyEx RegEnumKeyExW
#else
«define RegEnumKeyEx RegEnumKeyExA
#endif// 'UNICODE
Функция перебора объектов нам встречается впервые. Давайте сначала рассмотрим аргументы этой функции, а потом поговорим о том,
что происходит при переборе ключей. Многие аргументы этой функции уже должны быть знакомы читателю. Первый аргумент - это хэндл ключа, подчиненные ключи которого будут перебираться в поисках нужного ключа. Второй аргумент - dwlndex - является индексом требуемого подключа. Третий аргумент - IpName - указывает на буфер, в который будет записано имя ключа. Четвертый аргумент - IpcbName - определяет размер этого буфера в байтах. Пятый аргумент, как следует из его названия - IpReserved - зарезервирован для использования в будущем и должен быть равным NULL. Шестой аргумент - IpClass - должен указывать на буфер, в котором после завершения работы функции будет содержать имя класса подключа. Если это имя программе не требуется, то этот аргумент должен быть равным NULL. Размер этого буфера определяется седьмым аргументом - IpcbClass. И последний, восьмой аргумент -IpftLastWriteTime - после завершения работы функции содержит время последнего обновления данного подключа.
Знать функцию и ее аргументы - это хорошо. Но какой от функции и аргументов прок, если мы не умеем пользоваться функцией? Для того чтобы перебрать подключи, приложение должно сначала вызвать функцию RegEnumKeyExQ со вторым аргументом (dwlndex), равным нулю (поиск начинается с начала дерева). Если искомый ключ найден с первой попытки, то приложению повезло. В противном случае необходимо dwlndex увеличить на единицу и снова обратиться к функции. Так необходимо делать до тех пор, пока не будет найден искомый ключ или функция не вернет значение ERROR_NO_MORE ITEMS. Естественно, что поиск можно производить и в обратном порядке. Для того чтобы поиск мог быть нормально осуществлен, ключ, хэндл которого указан первым аргументом, должен быть открыт с правом доступа KEY__ENUMERATE_SUB_KEYS. Если функция выполнена успешно, то она возвращает значение ERROR SUCCESS. Любое другое возвращенное значение является кодом ошибки. Кстати, получить полную информацию о подключе можно с помощью функции RcgQueryInfoKey().
Давайте считать, что с помощью способа, описанного выше, мы перебрали подключи и нашли нужный нам подключ. Теперь в этом подключе нам необходимо найти нужные данные. Способ поиска точно такой же, как и в предыдущей функции. Для поиска необходимо перебрать все данные, связанные с подключом. Чтобы произвести этот перебор, обычно используется функция RegEnumVa!ue(). описание которой, приведенное ниже, можно найти в файле winreg.h:
WINADVAPI LONG APIENTRY KegEnumValueA (HKEY hKey,
DWORD dwlndex.
LPSTR IpValueName, LPDWORD IpcbValueName, LPDWORD IpReserved, LPDWORD IpType, LPBYTE IpData. LPDWORD IpcbData); WINADVAPI LONG APIENTRY RegEnumValueW (HKEY hKey,
DWORD dwlndex, LPWSTR IpValueName, LPDWORD IpcbValueName, LPDWORD IpReserved, LPDWORD IpType, LPBYTE IpDala, LPDWORD IpcbData); flifdefUNICODE
#defme RegEnumValue RegEnumValueW
#else
#defme RegEnum Value RegEnum ValueA
#endif// IUNICODE
Порядок использования этой функции полностью совпадает с порядком использования функции RegEnumKeyEx(), поэтому я не стану на нем останавливаться. Опишу только аргументы этой функции. Понятно, что hKey - это хэндл ключа, которому принадлежит подключ, индекс которого представлен вторым аргументом - dwlndex. Следующий аргумент -указатель на буфер, в который будет записано имя подключа. IpcbValueName определяет размер этого буфера. Аргумент IpReserved зарезервирован и должен быть равным NULL. Последние три аргумента определяют класс подключа, указатель на буфер, в который будут записаны эти данные и размер буфера. После возврата функции предпоследний аргумент содержит число записанных данных.
Теперь я, наконец, могу сказать, что у читателя есть полное представление о том, как использовать реестр. Как всегда, рассмотрение темы заканчивается демонстрационной программой:
#includc <windows.h>
#include <commctrl.h>
#defme hKeyMin 0x80000000
#dcfi:ie hKeyMax 0x80000006 HINSTANCE hlnst; HWND hTrccChild; TVJNSERTSTRUCT InscrtStruct;
LRESULT CALLBACK RegistryWndProc ( HWND, UINT, UINT, LONG ); void FillTree(H\VND, HTREEITEM);
void FillBranch(ULONG, DWORD, HWND, HTREEITEM); void FillSubBranch(HKEY, char*, HWND, HTREEITEM);
int WINAPI WinMain (HINSTANCE hlnstance, HINSTANCE hPrevhistance. LPSTR IpszCmdParam, int nCmdShow )
HWND hWnd;
WNDCLASS WndClass ;
MSG Msg;
char s/ClassNamef] = "Registry";
hlnst = hlnstance; /* Registering our window class */ /* Fill WNDCLASS structure */
WndClass.stylc = CS_HREDRAW | CSJVREDRAW;
WndClass.lpfnWndProc - RegistryWndProc;
WndClass.cbClsExtra = 0;
WndClass.cbWndExtra = 0;
WndClass.hlnstance = hlnstance ;
WndClass.hlcon = Loadlcon (NULL.IDI_APPLICATION1;
WndClass.hCursor = LoadCursor (NULL, IDC_ARROW);
WndClass.hbrBackground = (HBRUSH) GetStockObjcct (WHITE_BRUSH);
WndClass.IpszMenuName = "";
WndClass.IpszClassName = szClassName;
if ( !RcgisterClass(&WndClass))
!
MessageBox(NULL,"Cannot register class","Error",MB_OK); return 0;
hWnd - CreateWindow(szClassName, "Registry Demo Program",
WS_POPUPWINDOW | WS_VISIBLE | WSJTAPTION,
100, 100,300,400,
NULL, NULL,
hlnstancc.NULL); if(!hWnd)
i
>
MessageBox(NULL,"Cannot create window" ."Error", MB_OK); return 0;
InitCommonControls(); /* Show our window */
ShowWindow(hWnd, nCmdShow); UpdatcWindow(hWnd);
/* Beginning of messages cycle */
while(GctMessage(&Msg, NULL, 0, ()))
TranslatcMessage(&Msg); DispalchMcssage(&Msg);
} return Msg.wParam;
LRESULT CALLBACK RegistryWndProc (HWND hWnd, UINT Message,
UINT wParam, LONG IParam )
»
RECT Rcct;
static HWND hTreeChild;
static HTREEITEM hParentltem;
switch(Message)
{ case WM_CREATE:
GetC'licntRcct(hWnd, &Rect);
hTreeChild = CreateWindow(WC_TREEVIEW,"",
WS_VISIBLE | WS_TABSTOP j WS_CHILD |
TVS_HASLINES | TVS J.INESATROOT |
TVS_HASBUTTONS | WS_DLGFRAME,
0, 0, Rect.right, Rcct.bottom,
hWnd,
NULL,
hlnst,
NULL);
InsertStruct.item.mask = TVIF_TEXT;
InscrtStruct.item.hltem = NULL;
InsertStruct.item.pszText = "Registry Keys";
InsertStruct. item. cchTextMax = 14;
InscrtStmct.hParcnt = TVI_ROOT;
InsertStruct.hlnsertAfter = TVI_LAST;
hParentltem = TreeView_InscrtItem(hTreeChild, &InsertStruct);
FillTrce(hTreeChild, hParentltem);
TreeView_Expand(hTreeChild, hParentltem. TVE_EXPAND);
TreeView_SclcclItem(hTreeChild, hParentltem);
return 0; case WM_SIZE:
Move\Vindow(hTreeChild, 0. 0, LOWORD(lParam), HIWORD(lParam), TRUE);
return 0; case WM_DESTROY:
PostQuitMcssage(O);
return 0;
!
return DeAVindovvProc(hWnd, Message, wParam, ll'aram);
void FillTrce( HWND hTrceWnd, HTREEITEM hParentltem) {
ULONG i; TVJTEM Item;
TVJNSERTSTRUCT IiisertSmict; LPSTR IpszKeysf?] •--= {"HKEY CLASSES ROOT",
"HKEY~CURRENf USER",
"HKEY" LOCAL_MACHINE".
"HKEYJJSERS",
"HKEY PERFORMANCE_DATA",
"HKEY CURRENT_CONFIG",
"HKEY DYN_DATA"j; char cClass[80] - ""; DWORD dwSizc ~ 80, dwSubKeys, dwMaxLength, dwMaxClass, dwValucs,
(IwMaxValuc, dwMaxData, dwSec; FILETIME ItFilcTime; HTREEITEM hNewParentltem;
for(i - hKcyMin, i <-•' hKeyMax; i-i-~)
// Add the highest items
illERROR^SUCCESS -=-- RegQiierylnl'oKeyffHKEY) i, cClass, &dwSizc,
NULL.
&dwSubKeys, &dwMaxLength, &dwMaxClass, &dwValues, &dwMaxValue, &dwMaxData, &dwSec, &ftF)leTime))
Uem.mask = TVIFJTEXT;
Itcin.pszTcxt - lps/Keys[i - hKcyMin];
InsertStruct.item ~ Item;
InsertStruct.hParcnl = hParentltem;
hNewParentltem = TreeView_Insertltem(hTrecWnd, &InsertStruct);
FillBrauclUi. dwSubKeys, hTrceWnd, liNewParcntltcm);
void FillBranch( ULONG i, DWORD dwSubKeys, IIWND hTrceWnd, HTREEITEM hNewParentltem)
int j;
DWORD dwClassNameSizc = 80;
char cChissName[80] - "";
if (dwSubKeys ^= 0)
return; else
for(j = 0, j < dwSubKeys; j—)
RegEnumKey((HKEY) i, (DWORD) j, cClassName. dwClassNameSize);
dwClassNameSize = 80;
FillSubBranch((HKEY) i, cClassName, hTreeWnd, hNewParentltem);
I
void FillSubBranch(HKEY hKcy, char* cClassName, HWND hTreeWnd, HTREEITEM hParentltem)
)
HKEY hNewKey; char cClass[80], cNewCIass[80]; DWORD dwClassSize = 80, dwSK, j; TVJTEM Item;
TVJNSERTSTRUCT InscrtStruct; HTREEITEM hNewParentltem;
Item.mask = TVIFJTEXT;
Item.pszText = cClassName;
InsertStruct.hParent = hParentltem;
InsertStruct.hlnsertAfter = TVI_SORT;
InsertStruct.item = Item;
hNewParentitem = TreeView_InsertItem(hTreeWnd, &InsertStruct);
RegOpenKey(hKey, cClassName, &hNewKey);
RegQueryIntbKey(hNewKey, cClass, &dwClassSize, NULL, &dwSK, NULL,
NULL, NULL, NULL, NULL, NULL, NULL); dwClassSize = 80; if(dwSK != 0)
for(j = 0; j < dwSK; j+
RegEnumKey(hNewKey, j, cNewClass, dwClassSize); FillSubBranch(hNewKey, cNewClass, hTreeWnd, hNewParentltem);
} RegCloseKey(hNewKey);
Вид, создаваемого программой окна, показан на рис. 20.
Я не большой специалист в рисовании ( © ), поэтому подключение изображений, как всегда, оставляю на долю читателя.
Эта программа просто перебирает ключи и позволяет просмотреть все «дерево» реестра. Обращаю внимание читателя на тот факт, что предопределенные ключи (их имена начинаются с HKEY_) всегда открыты. Открывать следует только ключи, находящиеся ниже предопределенных в иерархии. Если читатель будет разрабатывать программу, храпящую конфигурационные данные на диске, я настоятельно рекомендую использовать реестр, а не пользоваться произвольными файлами.
Ш Registry Demo Program
Й- HKEY_CLU.SSES_ROOT Ш HKEY_CURRENT_USER Ш HKEY_LOCAL_MACHINE Й- HKEY_USERS
В- .Default i Ep AppE vents
И Console
: Ш Control Panel : i-- InstallLocationsMRU : Ш keyboard layout i Ш Network : :•••• RemoteAccess ; И Software В HKEY_CURRENT_CONFIG i В Display
i !•• • Fonts '• i '-•• Settings : 0 System
; В CurrentControlSet H HKEY_DYN_DATA Ш Config Manager В PerfStats
Рис. 20. Окно с деревом реестра, созданное программой
КОЕ-ЧТО О МНОГОЗАДАЧНОСТИ В WINDOWS
Одним из основных отличий Win32 от его предшественников явилась многозадачность. "Как это - разве в Windows 3.x не была реализована истинная многозадачность?" - может спросить кто-нибудь из неискушенных пользователей. "НЕ БЫЛА!" - отвечу я. И вот почему.
Мне Windows 3.x представляется чем-то вроде однорукого натуралиста. У этого натуралиста есть зверинец, в каждой клетке которого сидит хищник - программа. Каждому хищнику (программе) натуралист подает корм (сообщение) рукой. Как только хищник (программа) съест корм (обработает сообщение), корм (сообщение) получает очередной хищник. И так далее по кругу. Но иногда один из хищников (программ) мертвой хваткой вцепляется в руку (зависает и не возвращает управление) и натуралист умирает (Windows зависает), после чего на смену умершему смотрителю зверинца приходит новый однорукий натуралист (производится перезагрузка системы). И все начинается сначала. Разве
вам не знакома эта ситуация, уважаемый читатель? В Windows 3.x была реализована ПСЕВДОМНОГОЗАДАЧНОСТЬ, т. е. управление передавалось программе, возвращалось системе и передавалось следующей программе, т. е. фактически программы работали последовательно, друг за другом. В случае зависания одной из задач средств завершить ее, не перезагружая систему, практически не было. Кроме этого, фактически все задачи разделяли одни и те же системные ресурсы, например, память. Не было никаких проблем для одной задачи затереть содержимое памяти, выделенной другой.
В Windows'95 и Windows NT дело обстоит не так. В этой системе реализована истинная многозадачность, т. е. каждой программе системой выделяется квант времени, в течение которого программа обрабатывает поступившие в ее адрес сообщения. Вне зависимости от состояния программы СИСТЕМА забирает управление у программы и передает его другой программе. Если программа зависла, то система от этого не пострадает. Управление в любом случае будет передано другой программе. Кроме этого, в Windows'95 и Windows NT введено понятие процесса. Грубо говоря, процесс - это совокупность выполняющейся программы и выделенных ей системных ресурсов. Случай, при котором программа может вырваться из рамок своего процесса и повредить еще чьи-то ресурсы, практически не возможен.
Рассуждаем дальше. Раз программа получает управление на время, то почему бы этой программе не распараллелить свою работу и не запустить несколько одновременно выполняющихся программ под своим управлением? В WTin32 эти программы называются потоками.
Таким образом, в системе Windows реализованы два типа многозадачности - процессная и потоковая. Рассмотрим оба типа многозадачности, после чего отдельно остановимся на вопросе синхронизации работы в многозадачной среде.
Остановимся на одной детали. Оттого, что мы назвали Windows многозадачной системой, физический смысл этой многозадачности не изменился. На однопроцессорном компьютере в каждый конкретный момент выполняется одна задача. Если при запуске двух-трех маленьких программ временная задержка субъективно не заметна, то при запуске нескольких программ, требующих колоссальных ресурсов (к примеру, WinWord или Borland C++ 5.0), задержка при выполнении программ становится достаточно заметной. На многопроцессорных системах за каждым процессором может быть закреплен свой ноток, поэтому на таких системах выполнение программ осуществляется действительно в многозадачном режиме.
ЗАПУСК ПРОЦЕССА
Давайте, уважаемый читатель, все же более точно определим, что есть процесс. В Windows 3.x, да иногда и в Win32 процесс определяют как копию (экземпляр) выполняющейся программы. Так оно и есть, но при этом забывают, что копия - понятие статическое. Другими словами, процесс в Win32 - это объект, который не выполняется, а просто «владеет» выделенным ей адресным пространством, другими словами, процесс является структурой в памяти. А вот в адресном пространстве процесса находятся не только код и данные, но и потоки - выполняющиеся объекты. При запуске процесса автоматически запускается поток (он называется главным). При остановке главного потока автоматически останавливается и процесс. А так как процесс без потока просто бесцельно занимает ресурсы, то система автоматически уничтожает ставший ненужным процесс. Первичный процесс создается системой при запуске, точно так же при создании первичного процесса в нем создается и поток.
Приложение тоже может создать процесс с главным потоком, используя для этой цели функцию CreateProcessQ. Её прототип, находящийся в файле winbase.h, при первой встрече с ним внушает легкий ужас:
WINBASEAPI BOOL WINAPI CreateProcessA(LPCSTR IpApplicationName,
LPSTR IpCommandLinc,
LPSECURITY_ATTRroUTESlpProcessAttributes,
LPSECUR ITY_ATTRIBUTES IpThreadAttributes,
BOOL blnheritHandles,
DWORD dwCreationFlags,
LPVOID IpEnvironment,
LPCSTR IpCurrentDirectory,
LPSTARTUPINFOA IpStartupInfo,
LPPROCESSJNFORMATIONlpProcessInformation); WINBASEAPI BOOL WINAPI CreateProcessW(LPCWSTR IpApplicationName,
LPWSTR IpCommandLinc,
LPSECURITY_ATTRIBUTESlpProcessAttributes,
LPSECURITY_ATTR]BUTESlpTlireadAttributcs,
BOOL blnheritHandles,
DWORD dwCreationFlags,
LPVOID IpEnvironment,
LPCWSTR IpCurrentDirectory,
LPSTARTUPINFOW IpStartupInfo,
LPPROCESSJNFORMATIONlpProccssInformation);
#ifdcf UNICODE
#defme CrcateProccss CrcateProcessW
#else
#defme CreateProcess CreateProcessA tfendif/'IUNICODE
Так как понимание сущности процессов и потбков крайне важно для программирующих в Win32, в этом месте я чуть отступлю от принятого стиля изложения и более подробно расскажу о том, что происходит при вызове этой функции.
Я уже говорил, что процесс - это структура в памяти. Таким образом, в начале работы функция выделяет память для этой структуры, а потом выделяет память (виртуальную, естественно) для адресного пространства процесса. Если выделение памяти прошло без ошибок, в адресное пространство процесса загружается код исполняемой программы и используемых программой динамических библиотек. Только после этого создается главный поток процесса. Если функции удастся произвести все эти действия без ошибок, то возвращаемое значение будет равно TRUE. FALSE явится индикатором того, что по каким-то причинам процесс не создан.
Перейдем к рассмотрению аргументов функции CreateProcessQ.
АРГУМЕНТЫ ФУНКЦИИ CREATEPROCESSQ
С моей точки зрения, взаимодействие первых двух аргументов не совсем продумано.
Первый аргумент - IpApplicationName - определяет имя исполняемого файла (обязательно указывать имя и расширение файла, автоматически расширение .ехе не подставляется), для которого создается процесс.
Второй аргумент - IpCommandLine определяет передаваемую этому файлу командную строку. Если IpApplicationName равен NULL, то первый (до первого пробела) элемент IpCommandLine считается именем исполняемого файла.
Таким образом, имя исполняемого файла можно передавать как в первом, так и во втором аргументе. Но здесь нужно быть внимательным и не допустить, скажем, такой ситуации, когда IpApplicationName равен «Wordpad.exe», a IpCommandLine - «Wordpad.exe MyFile.doc». Нетрудно догадаться к чему это приведет.
Третий и четвертый аргументы определяют атрибуты доступа к процессу и потоку соответственно. Я намеренно употребил слово «должны», ибо Windows'95 системы разграничения доступа не имеет. В Windows'95 эти значения, как правило, равны NULL.
Очередное поле - dwCreationFlag - является комбинацией битовых флагов. Одна группа битовых флагов определяет способ создания процесса.
Флаги способа создания процесса
Флаг DEBUG_PROCESS (0x00000001) устанавливается в тех случаях, когда родительский процесс должен осуществлять отладку порождаемого процесса и всех его потомков. Система будет оповещать родительский процесс о возникновении определенных событий в порождаемом процессе и его потомках.
Флаг DEBUG_ONLY_THIS_PROCESS (0x00000002) почти эквивалентен предыдущему, разница состоит в том, что система будет оповещать о событиях только в порождаемом процессе, но не в его потомках.
Флаг CREATE_SUSPENDED (0x00000004) указывает, что главный поток порождаемого процесса создается, но не выполняется до вызова функции ResumeThreadQ. Этот флаг обычно используется в отладчиках.
Флаг DETACHED PROCESS (0x00000008) запрещает создаваемому консольному процессу использовать консоль родительского процесса. Порождаемый процесс вынужден будет вызвать функцию AllocConsoleQ для получения собственной консоли.
Флаг CREATE NEW CONSOLE (0x00000010) указывает на необхо димость создания новой консоли для порождаемого процесса. Этот флаг не может использоваться вместе с предыдущим.
Флаг CREATE_NEW_PROCESS_GROUP (0x00000200) создает группу консольных процессов, которые будут одновременно реагировать на нажатие клавиш Ctrl-C и Ctrl-Break.
Флаг CREATE_UNICODE_ENVIRONMENT (0x00000400) означает, что данные, на которые указывает IpEnvironment, используют символы Unicode. По умолчанию считается, что используется ANSI-кодировка.
Флаг CREATE_SEPARATE_WOW_VDM (0x00000800) используется только при запуске 16-битовых Windows-приложений и указывает, что приложению необходимо выделить отдельную виртуальную машину (Virtual DOS Machine, VDM) (по умолчанию, все 16-битовые Windows-приложения используют одну разделяемую виртуальную машину). Преимуществом выделения отдельной машины является то, что приложение почти не влияет на остальные. Даже зависнув, оно приведет к краху только своей VDM. Недостаток - каждая виртуальная машина требует большого объема памяти.
Флаг CREATE_SHARED_WOW_VDM (0x00001000) используется при запуске 16-битовых Windows-приложений и указывает на необходимость создания для процесса разделяемой VDM.
Таблица 53. Флаги класса приоритета процесса
Флаг |
Значение |
Эффект |
NORMAL PRIORITY CLASS IDLE_PRIORITY_CLASS |
0x00000020 0x00000040 |
Нормальный приоритет Потоки этого процесса выполня- |
|
|
ются только тогда, когда система |
HIGH_PRIORITY_CLASS |
0x00000080 |
простаивает Приоритет выше нормального, но |
|
|
ниже приоритета реального |
REALTIME PRIORITY CLASS |
0x00000100 |
времени Самый высокий возможный |
|
|
приоритет |
На этом флаги, определяющие способ создания процесса, исчерпаны. Вторая группа битовых флагов определяет класс приоритета создаваемого процесса.
Флаги класса приоритета процесса
При создании процесса можно указать и класс его приоритета (табл. 53). Если при создании процесса не указан ни один из флагов, приведенных ниже, класс приоритета порождаемого процесса по умолчанию устанавливается равным IDLE PRIORITY CLASS, если этот класс установлен у процесса родителя, и NORMAL_PRIORITY_CLASS во всех остальных случаях.
Тем не менее, присвоение класса приоритета вновь создаваемому потоку не рекомендуется - Windows сама присвоит потоку класс приоритета по умолчанию.
Следующий аргумент функции CreateProcessQ - IpEnvironment -обычно равен NULL. Это означает, что порождаемый процесс наследует переменные окружения родительского процесса. Если этот аргумент не равен NULL, то он должен содержать указатель на блок памяти, содержащий те переменные окружения, которыми будет пользоваться порождаемый процесс.
Наименование следующего аргумента функции - IpCurrentDirectory -говорит само за себя. Этот аргумент позволяет установить текущие диск и директорию для порождаемого процесса. Если этот аргумент равен NULL, порождаемый процесс наследует текущие диск и директорию
родительского процесса. В противном случае этот аргумент должен указывать на строку, в которой указан полный путь к устанавливаемой текущей директории, включающий и букву дисковода.
Очередной аргумент - указатель на структуру типа STARTUPINFO. Эта структура, служащая для описания свойств окна, создаваемого в новом процессе, описана в winbase.h следующим образом:
typedef struct _STARTUPINFOA {DWORD cb; LPSTR ipReserved;
LPSTR IpDesktop; LPSTR IpTitle; DWORD dwX; DWORD dwY; DWORD dwXSize; DWORD dwYSize; DWORD dwXCountChars; DWORD dwYCountChars; DWORD dwFillAttribute; DWORD dwFlags; WORD wShowWindow; WORD cbReserved2; LPBYTE lpReserved2; HANDLE hStdlnput; HANDLE hStdOutput; HANDLE hStdError;
} STARTUPINFOA, *LPSTARTUPINFOA; lypedef struct _STARTUPINFOW {DWORD cb; LPWSTR IpReserved;
LPWSTR IpDesktop; LPWSTR IpTitle; DWORD dwX; DWORD dwY; DWORD dwXSize; DWORD dwYSize; DWORD dwXCountChars; DWORD dwYCountChars; DWORD dwFillAttribute; DWORD dwFlags; WORD wShowWindow; WORD cbReservedl; LPBYTE lpReserved2; HANDLE hStdlnput; HANDLE hStdOutput; HANDLE hStdError; } STARTUPINFOW, «LPSTARTUPINFOW;
#ifdef UNICODE
typedef STARTUPINFOW STARTUPINFO;
typedef LPSTARTUPINFOW LPSTARTUPINFO;
#else
typedef STARTUPINFOA STARTUPINFO;
typedef LPSTARTUPINFOA LPSTARTUPINFO;
#endif//UNICODE
Рассмотрим поля этой структуры.
Поля структуры типа STARTUPINFO
Первое поле - cb - размер этой структуры. Оно должно быть равно sizeof(STARTUPrNFO).
Второе поле - IpReserved - зарезервировано и должно быть равно NULL.
Третье поле - IpDesktop - в Windows'95 просто игнорируется.
Четвертое поле - IpTitle - определяет заголовок консольного приложения. Для GUI или приложений, не создающих новой консоли, должен быть равным NULL.
Поля с пятого по восьмое включительно определяют положение окна и его размеры (dwX, dwY - координаты верхнего левого угла окна в пикселах, dwXSize, dwYSize - ширина и высота окна в пикселах).
Таблица 54. Флаги, определяющие, в каких полях структуры типра STARTUPINFO содержится информация
Флаг
Значение
Эффект
STARTFJJSESHOWWINDOW STARTF JJSESIZE STARTFJJSEPOSITION STARTFJJSECOUNTCHARS
STARTF_USEFILLATTRIBUTE
STARTF_RUNFULLSCREEN STARTF FORCEONFEEDBACK
0x00000001 0x00000002 0x00000004 0x00000008
0x00000010
0x00000020 0x00000040
STARTF_FORCEOFFFEEDBACK STARTFJJSESTDHANDLES
STARTF USEHOTKEY
0x00000080 0x00000100
0x00000200
Если флаг не установлен, поле
wShowWindow игнорируется
Если флаг не установлен, поля
dwXSize и dwYSize игнорируются
Если флаг не установлен, поля dwX
и dwY игнорируются
Если флаг иг установлен, поля
dwXCountChars и dwYCounChars
игнорируются
Если флаг не установлен, поле
dwFillAttribute игнорируется
Курсор становится « песочными часами» на две секунды, за которые должно произойти обращение к GUI, после чего за 5 секунд должно быть создано окно и еще за 5 секунд оно должно перерисоваться При создании процесса форма курсора не меняется Если флаг установлен, то используются потоки, хэндлы которых определены полями hStdlnput, hStdOutput, hStdError
Девятое и десятое поля (dwXCountChars, dwYCountChars) определяют ширину и высоту окна консоли в символах (не пикселах!). Одиннадцатое поле - dwFillAttribute - определяет атрибуты консольного окна.
Двенадцатое поле - dwFIags - используется для того, чтобы определить, какие поля структуры типа STARTUPINFO будут использоваться при создании окна порождаемым процессом. Это поле представляет собой комбинацию битовых флагов (табл. 54).
Тринадцатое поле - wShowWindow - определяет, каким образом окно будет отображено (помните функцию ShowWindow())? Значение этого поля игнорируется, если только в dwFIags не установлен флаг STARTF USESHOWWINDOW. Возможные значения этого поля - те же константы, начинающиеся с SW__, которые используются в функции ShowWindowQ.
Четырнадцатое и пятнадцатое поля, - cbReserved2 и lpReserved2 зарезервированы. Должны инициализироваться нулем и NULL соответственно.
Шестнадцатое, семнадцатое и восемнадцатое поля - hStdlnput, hStdOutput и hStdError - определяют хэндлы стандартных потоков ввода-вывода.
Нерассмотренным остался только один аргумент функции CreateProcess() - IpProcessInformation, указывающий на структуру типа PROCESS INFORMATION, в которую записывается информация о порожденном процессе после его создания. Структура описана в файле winbase.h следующим образом:
typedcf struct J>ROCESS_rNFORMATION {
HANDLE hProcess; HANDLE hThrcad; DWORD dwProcessId; DWORD dwThreadld;
PROCESSJNFORMATION, *PPROCESS INFORMATION, *LPPROCESS_INFORMATION;
В первое поле - h Process - система записывает хэндл созданного процесса, во второе - hThread - хэндл потока. Поля dwProcessId и dwThreadld являются уникальными идентификаторами процесса и потока соответственно. Рекомендую обратить особое внимание на последние два поля. Дело в том, что Win32, если идентификатор освобожден, может повторно использовать его. К примеру, пусть процессу присвоен идентификатор 0x00001111. После завершения процесса идентификатор освобождается и какому-нибудь новому процессу может опять быть присвоен тот же
идентификатор 0x00001111. Это необходимо учитывать при написании программ.
Итак, аргументы функции рассмотрены. Основные результаты - хэндлы и идентификаторы процесса и потока - получены. А какое значение возвращает функция? Возвращаемое функцией значение TRUE говорит о том, что процесс создан и функция завершилась нормально. А при получении значения FALSE программисту придется искать ошибку.
Итак, мы подробно рассмотрели вопрос о запуске процесса. Теперь, очевидно, процесс необходимо завершить.
ЗАВЕРШЕНИЕ ПРОЦЕССА
Процесс может быть завершен вызовом одной из двух функций -ExitProcess() пли TerminateProcess(). Рассмотрим более подробно каждую из этих функций.
ФУНКЦИЯ EXITPROCESSQ
В обычных условиях процесс завершается тогда, когда один из принадлежащих ему потоков вызывает функцию ExitProcessQ, которая описана в файле winbase.h следующим образом:
WINBASEAPI VOID WINAPI ExitProcess(UINT uExitCode);
Читателю следует обратить внимание на тот факт, что завершение процесса начинается изнутри процесса. Почему так сделано? Во-первых, только поток процесса знает, когда он выполнил свою работу и когда ему необходимо завершиться. Во-вторых, только процесс в тот момент, когда он узнает о необходимости завершения, может оповестить об этом все принадлежащие ему потоки и произвести нормальное завершение. Извне процесса эти действия произвести почти невозможно.
Если говорить более конкретно, то при завершении процесса производятся следующие действия:
вызываются функции деинициализации всех подключенных библиотек динамической компоновки, т. е. происходит нормальное завершение всех подключенных DLL;
закрываются и/или уничтожаются все объекты, открытые и/или созданные процессом;
состояние процесса изменяется на «освобожденный» (signaled), что является сигналом для всех потоков, ожидающих завершения процесса;
состояние всех потоков изменяется на «освобожденный» (signaled), что является сигналом для всех потоков других процессов, которые ожидают завершения потоков текущего процесса;
код завершения меняется со STILL ACTIVE на код, записываемы!! в uExitCode;
счетчик числа пользователей процесса уменьшается на единицу (заметим, что данные процесса удаляются из памяти, но сам объект остается в памяти до того момента, пока счетчик пользователей не достигнет нулевого -.шачения, или, другими словами, пока не будут закрыты все хэндлы процесса. Определить, завершен ли процесс можно с помощью функции GetExitProcessCode(), которая в случае незавершенности процесса возвращает STILL ACTIVE).
Необходимо отметить, что завершение процесса не приводит к завершению порожденных им процессов.
Сразу после деинициализации и выгрузки библиотек из памяти, по до своего завершения, функция заносит в параметр uExitCode код завершения. После этого процесс можно считать полностью завершенным.
ФУНКЦИЯ TERMINATEPROCESSO
Эта функция является аварийным средством завершения процесса и её рекомендуется использовать только в крайнем случае. Она описана в том же winbase.li:
WINBASHAPI BOOL WINAPI Termm;UeProcess(HANDLE hProccss,
UlNTuExilCodc);
Функция используется только тогда, когда иными средствами завершить процесс не удается. С этой целью извне (!), а не изнутри процесса вызывается функция TerminateProcess(). которая и завершает процесс. Но в данном случае не освобождаются используемые процессом DLL, хотя все используемые объекты освобождаются. Освобождается также и память, занимаемая процессом. Число пользователей процесса также уменьшается.
Отметим один интересный факт. Обычно один процесс запускает другой как обособленный и после запуска забывает о нем. Для того чтобы порожденный процесс мог быть завершен, сразу после создания процесса порождающий процесс должен закрыть хэндл порожденного процесса и его потока. Делается это примерно следующим образом:
PROCESS INFORMATION Processlnformation;
BOOL hMyProcess;
if ((hMyProcess = CreateProcess(...... &ProcessInformation))
{
CloseHandle(ProcessInfonnation.hThread);
CloseHandle(ProcessInformation.hProcess);
О процессах можно рассказать намного больше, но надеюсь, что написанного хватит для того, чтобы приступить к программированию. Для того чтобы проиллюстрировать все эти длинные рассуждения, приведу в качестве примера небольшую программу.
ДЕМОНСТРАЦИОННАЯ ПРОГРАММА
В этой программе не происходит ничего интересного. Просто при выборе элемента меню «Создать процесс» создается процесс, в котором запускается обычный Notepad (надеюсь, он у всех в доступной директории? Если нет, то вы можете заменить Notepad любой другой программой). Максимум может быть запущено 10 процессов. По команде «Kill process» процессы уничтожаются в порядке, обратном их созданию. Предлагаю читателю обратить внимание на то, что я завершаю процесс посредством вызова TerminateProcess(), а не ExitProcessQ. Для того чтобы завершить процесс обычным способом, пришлось бы писать программу, которая вызывала бы функцию ExitProcessQ изнутри процесса, а мне бы не хотелось рассеивать внимание читателя. Результаты создания процесса, взятые из структуры типа PROCESS_ INFORMATION, отображаются в окне сообщений. Если кого-то раздражает необходимость постоянно убирать окно сообщений с отображения, рекомендую воспользоваться программой pview95.exe, которая поставляется с SDK.
Текст демонстрационной программы приведен ниже:
#include <windows.h>
#include <stdio.h>
#include "proc.h"
LRESULT CALLBACK ProcessesWndProc ( HWND, UINT, UINT, LONG );
int WINAPI WinMain (HINSTANCE hlnstance, HINSTANCE hPrevInstancc, LPSTR IpszCmdParam, int nCmdShow )
HWND hWnd ;
WNDCLASS WndClass ;
MSG Msg;
char szClassName[] = "Processes"; /* Registering our window class */ /* Fill WNDCLASS structure */
WndClass.style = CS_HREDRAW CS_VREDRAW;
WndClass. IpfnWndProc = ProcessesWndProc;
WndClass.cbClsExtra = 0;
WndClass.cbWndExtra - 0;
WndClass. hlnstance = hlnstance ;
WndClass.hlcon = Loadlcon (NULL,IDI_APPLICATION);
WndClass.hCursor= LoadCursor (NULL, IDC_ARROW); WndClass.hbrBackground - (HBRUSH) GetStockObject (WHITE_BRUSH); WndClass.IpszMcnuName = "ProcessesMeivu"; WndClass.IpszClassName = szClassName;
if( !RegisterClass(&WndClass)) {
MessageBox(NULL,"Cannot register class","Error",MB_OK); return 0;
hWnd = CrcateWindow(szClassName, "Processes Demo",
WS^OVERLAPPEDWINDOW, CWJJSEDEFAULT, CWJJSEDEFAULT, CWJJSEDEFAULT, CWJJSEDEFAULT, NULL, NULL, hlnstance.NULL);
if(!hWnd)
t t
MessageBox(NULL,"Cannot create window", "Error",MB_OK); relurn 0;
/* Show our window */
ShowWindow(hWnd,nCmdShow);
UpdateWindow(hWnd);
/* Beginning of messages cycle */
while(GetMessage(&Msg, NULL, 0, 0))
{
TranslateMessage(&Msg);
DispatchMessage(&Msg); i return Msg.wParam;
LRESULT CALLBACK ProcessesWndProc (HWND hWnd, UINT Message,
UINT wParam, LONG IParam )
const Max ~ 10;
STARTUP1NFO Startuplnfo;
static int ProccssNumber = 0;
static PROCESS INFORMATION ProccssInformationfMax];
static char cMyMessage[80]; static HMENU hSubMcnu;
switch(Message)
i
case WM CREATE: hSubMemi = GetSubMenu(GetMcnu(hWnd).0);
return 0;
case WM_COMMAND: switch( LOWORD(wParam))
case IDM_New_Process: if(ProcessNumber < Max)
t
Startuplnfo.cb = sizeof(STARTUPINFO);
StartupInfo.lpReservcd = NULL;
StartupInfo.lpDesktop = NULL;
StartupInfo.lpTitlc - NULL;
StartupInfo.dwFlags = STARTFJJSESHOWWINDOW;
StartupInfo.wShowWindow = SW_SHOWNORMAL;
StartiipInfo.cbReserved2 = 0;
StartupInfo.lpReserved2 = NULL;
if(CreateProcess(NULL."Notepad.exe",
NULL, NULL, FALSE, 0,
NULL, NULL, &StartupInfo,
&(ProcessInformation[ProcessNumber])))
ProcessNumber—;
wsprintffcMyMessage,"hProcess is %x.\nhThread is
%x.\ndwProcess!d is %x.\ndwThrcad!d is %x.", ProcessInformation[ProcessNumber - Ij.hProcess, ProcessInrormation[ProcessNumber - IJ.hThread, ProcessInformation[ProcessNumber - IJ.dwProcessId, ProcessInformation[ProccssNumber - IJ.dwThrcadld);
MessageBox(hWnd, cMyMessage, "Process is created", MB_OK);
EnableMenuItem(hSubMenu, IDMJCill_Process,
MF_BYCOMMAND | MF_ENABLED);
else
MessageBox(hWnd, "Cannot create process", "Process creation", MB_OK);
else
!
MessageBoxfhWnd, "Too many created processes...", "Process creation", MB_OK);
break;
case IDM_Kill_Process: if(ProcessNumber > 0)
if(TenninatcProcess(ProcessIntbrmation[ProcessNumber-l].hProcess, 0))
ProcessNumber-; if(! ProcessNumber) Enab!eMenuItem(hSubMenu, IDM Kill_Process,
MF BYCOMMAND | MF_GRAYED);
else McssageBox(hWnd. "Cannot terminate process",
"Process termination", MB OK)' }
else MessageBox(hWnd, "No more processes", "Process termination"
MB_OK); break;
case IDM_Exit:
SendMessage(hWnd, WM_CLOSE, 0, 0); break; !
return 0;
case WM_ DESTROY: PostQuitMessage(O); return 0; » return DefWindowProc(hWnd,Mcssage,wParam, IParam),
В этой программе используется файл описаний:
#define IDM_ About 104
#define IDM_Exit 103
#define IDM_KiIl_Process 102
#define IDM_New_Proeess 101
Кроме этого, в программе используется файл ресурсов:
^include "proc.h"
ProcessesMcnu MENU i
POPUP "&Processes"
{
MENU1TEM "&New process", IDM_New Process
MENUITEM "&Kill process", IDM_Kill_Proccss GRAYED
MENU1TEM SEPARATOR
MENUITEM "E&xit", IDM Exit
POPUP "AHelp"
{
MENUITEM "&About", IDM About