Азбука программирования в Win32 API

         

РАБОТА С ЗАКЛАДКАМИ



Достаточно интересным элементом управления, появившимся только в 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 заполнено и содержит данные, определяемые приложением

 

Тем не менее, сделаю одно замечание. Если размер данных, опреде­ляемых приложением, не равен 4 байтам, то приложение должно опреде­лить собственную структуру и использовать ее вместо ТС_1ТЕМ. Первым полем этой структуры должна быть другая структура, типа TCJTEMHEADER. В файле commctrl.h она описана так:

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

 

Первое поле - mask - определяет, в каком из полей этой структуры со­держится используемая информация. Это поле может принимать значения, приведенные в табл. 48.

Дело за малым - выяснить, что может храниться в каждом из этих по­лей.

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 - содержит указатель на строку, появляю­щуюся в элементе списка. Помимо этого, поле может иметь значение LPSTR_CALLBACK, в этом случае родительское окно отвечает за фор­мирование текста элемента.

Если структура используется для получения информации об элементе, поле 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

 

Как и в случае окна закладок, при смене выбранного элемента роди­тельскому окну посылаются нотификационные сообщения Т VN SELCHANGING перед сменой и TVN_SELCHANGED после смены

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

 

времени Самый высокий возможный

 

 

 

 

 

приоритет

 

Флаг CREATE_DEFAULT_ERROR_MODE (0x040000) указывает, что порождаемый процесс не наследует режим обработки ошибок своего родителя. Ему при создании устанавливается режим обработки ошибок, принятый по умолчанию.



На этом флаги, определяющие способ создания процесса, исчерпаны. Вторая группа битовых флагов определяет класс приоритета создаваемо­го процесса.

Флаги класса приоритета процесса

При создании процесса можно указать и класс его приоритета (табл. 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