Программирование для Windows NT© Александр Фролов, Григорий ФроловТом 26, часть 1, М.: Диалог-МИФИ, 1996, 272 стр. Рецензия PC WEEK Приложение PSTARTС помощью приложения PSTART, исходные тексты которого мы привели в этом разделе, вы сможете запускать процессы, определяя для них класс приоритета. Главное окно приложения PSTART показано на рис. 3.1. Рис. 3.1. Главное окно приложения PSTART Выбрав из меню File строку Start process, вы можете выбрать в диалоговой панели Start Process программный файл запускаемого приложения (рис. 3.2). Рис. 3.2. Диалоговая панель Start Process Для выбора параметров запуска воспользуйтесь диалоговой панелью Start Options (рис. 3.3), которая появится на экране при выборе из меню File строки Options. Рис. 3.3. Диалоговая панель Start Options В этой диалоговой панели вы можете выбрать класс приоритета запускаемого процесса, а также, включив переключатель Wait process termination, использовать режим запуска, при котором запускающий процесс дожидается завершения выполнения дочернего процесса. Исходные тексты приложенияГлавный файл исходных текстов приложения PSTART представлены в листинге 3.1. Листинг 3.1. Файл pstart/pstart.c #define STRICT #include <windows.h> #include <windowsx.h> #include <commctrl.h> #include <stdio.h> #include <memory.h> #include "resource.h" #include "afxres.h" #include "pstart.h" HINSTANCE hInst; char szAppName[] = "PStartApp"; char szAppTitle[] = "Process Starter"; // Параметры запуска процессов DWORD dwCreationFlags; // Флаг ожидания завершения процессов BOOL fWaitTermination; // ----------------------------------------------------- // Функция WinMain // ----------------------------------------------------- int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { WNDCLASSEX wc; HWND hWnd; MSG msg; // Сохраняем идентификатор приложения hInst = hInstance; // Преверяем, не было ли это приложение запущено ранее hWnd = FindWindow(szAppName, NULL); if(hWnd) { // Если было, выдвигаем окно приложения на // передний план if(IsIconic(hWnd)) ShowWindow(hWnd, SW_RESTORE); SetForegroundWindow(hWnd); return FALSE; } // Регистрируем класс окна memset(&wc, 0, sizeof(wc)); wc.cbSize = sizeof(WNDCLASSEX); wc.hIconSm = LoadImage(hInst, MAKEINTRESOURCE(IDI_APPICONSM), IMAGE_ICON, 16, 16, 0); wc.style = 0; wc.lpfnWndProc = (WNDPROC)WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst; wc.hIcon = LoadImage(hInst, MAKEINTRESOURCE(IDI_APPICON), IMAGE_ICON, 32, 32, 0); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1); wc.lpszMenuName = MAKEINTRESOURCE(IDR_APPMENU); wc.lpszClassName = szAppName; if(!RegisterClassEx(&wc)) if(!RegisterClass((LPWNDCLASS)&wc.style)) return FALSE; // Создаем главное окно приложения hWnd = CreateWindow(szAppName, szAppTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInst, NULL); if(!hWnd) return(FALSE); // Отображаем окно и запускаем цикл // обработки сообщений ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); while(GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } // ----------------------------------------------------- // Функция WndProc // ----------------------------------------------------- LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { HANDLE_MSG(hWnd, WM_COMMAND, WndProc_OnCommand); default: return(DefWindowProc(hWnd, msg, wParam, lParam)); } } // ----------------------------------------------------- // Функция WndProc_OnCommand // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify) { switch (id) { // Отображаем диалоговую панель для выбора // программного файла и запускаем процесс case ID_FILE_STARTPROCESS: { StartProcess(hWnd); break; } case ID_FILE_OPTIONS: { // Отображаем диалоговую панель, предназначенную // для настройки параметров запуска процессов DialogBox(hInst, MAKEINTRESOURCE(IDD_DIALOG1), hWnd, DlgProc); break; } case ID_FILE_EXIT: { // Завершаем работу приложения PostQuitMessage(0); return 0L; break; } case ID_HELP_ABOUT: { MessageBox(hWnd, "Process Starter\n" "(C) Alexandr Frolov, 1996\n" "Email: frolov@glas.apc.org", szAppTitle, MB_OK | MB_ICONINFORMATION); return 0L; break; } default: break; } return FORWARD_WM_COMMAND(hWnd, id, hwndCtl, codeNotify, DefWindowProc); } // ----------------------------------------------------- // Функция StartProcess // ----------------------------------------------------- void StartProcess(HWND hwnd) { OPENFILENAME ofn; STARTUPINFO si; PROCESS_INFORMATION pi; char szBuf[256]; DWORD dwExitCode; char szFile[256]; char szDirName[256]; char szFileTitle[256]; char szFilter[256] = "Programs\0*.exe\0Any Files\0*.*\0"; char szDlgTitle[] = "Start Process"; memset(&si, 0, sizeof(STARTUPINFO)); si.cb = sizeof(si); memset(&ofn, 0, sizeof(OPENFILENAME)); GetCurrentDirectory(sizeof(szDirName), szDirName); szFile[0] = '\0'; // Подготавливаем структуру для выбора файла ofn.lStructSize = sizeof(OPENFILENAME); ofn.hwndOwner = hwnd; ofn.lpstrFilter = szFilter; ofn.lpstrInitialDir = szDirName; ofn.nFilterIndex = 1; ofn.lpstrFile = szFile; ofn.nMaxFile = sizeof(szFile); ofn.lpstrFileTitle = szFileTitle; ofn.nMaxFileTitle = sizeof(szFileTitle); ofn.lpstrTitle = szDlgTitle; ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; // Выводим на экран диалоговую панель, предназначенную // для выбора файла if(GetOpenFileName(&ofn)) { // Если файл выбран, запускаем его на выполнение if (*ofn.lpstrFile) { // Создаем процесс if(CreateProcess(NULL, ofn.lpstrFile, NULL, NULL, FALSE, dwCreationFlags, NULL, NULL, &si, &pi)) { // Если было указано, что нужно ждать завершение // процесса, выполняем такое ожидание if(fWaitTermination) { // Освобождаем идентификатор задачи запущенного // процесса, так как он нам не нужен CloseHandle(pi.hThread); // Выполняем ожидание завершения процесса if(WaitForSingleObject(pi.hProcess, INFINITE) != WAIT_FAILED) { // Получаем и отображаем код завершения процесса GetExitCodeProcess(pi.hProcess, &dwExitCode); sprintf(szBuf, "Process terminated\n" "Exit Code = %lX", dwExitCode); MessageBox(hwnd, szBuf, szAppTitle, MB_OK | MB_ICONINFORMATION); } // Освобождаем идентификатор процесса CloseHandle(pi.hProcess); } // Если процесс был запущен без ожидания, // освобождаем идентификаторы задачи и процесса else { CloseHandle(pi.hProcess); CloseHandle(pi.hThread); } } // Если про запуске процесса произошла ошибка, // получаем и отображаем ее код else { sprintf(szBuf, "CreateProcess: error %ld", GetLastError()); MessageBox(hwnd, szBuf, szAppTitle, MB_OK | MB_ICONEXCLAMATION); } } } } // ----------------------------------------------------- // Функция DlgProc // ----------------------------------------------------- LRESULT WINAPI DlgProc(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { HANDLE_MSG(hdlg, WM_INITDIALOG, DlgProc_OnInitDialog); HANDLE_MSG(hdlg, WM_COMMAND, DlgProc_OnCommand); default: return FALSE; } } // ----------------------------------------------------- // Функция DlgProc_OnInitDialog // ----------------------------------------------------- BOOL DlgProc_OnInitDialog(HWND hdlg, HWND hwndFocus, LPARAM lParam) { // По умолчанию мы будем запускать процессы // с нормальным приоритетом без ожидания // их завершения CheckDlgButton(hdlg, IDC_NORMAL, 1); return TRUE; } // ----------------------------------------------------- // Функция DlgProc_OnCommand // ----------------------------------------------------- #pragma warning(disable: 4098) void DlgProc_OnCommand(HWND hdlg, int id, HWND hwndCtl, UINT codeNotify) { switch (id) { case IDOK: { dwCreationFlags = 0; fWaitTermination = FALSE; // Устанавливаем класс приоритета if(IsDlgButtonChecked(hdlg, IDC_Realtime)) { dwCreationFlags = REALTIME_PRIORITY_CLASS; } else if(IsDlgButtonChecked(hdlg, IDC_HIGH)) { dwCreationFlags = HIGH_PRIORITY_CLASS; } else if(IsDlgButtonChecked(hdlg, IDC_NORMAL)) { dwCreationFlags = NORMAL_PRIORITY_CLASS; } else if(IsDlgButtonChecked(hdlg, IDC_IDLE)) { dwCreationFlags = IDLE_PRIORITY_CLASS; } // Проверяем и устанавливаем режим ожидания if(IsDlgButtonChecked(hdlg, IDC_TERMINATION)) { fWaitTermination = TRUE; } EndDialog(hdlg, 0); return TRUE; } case IDCANCEL: { EndDialog(hdlg, 0); return TRUE; } default: break; } return FALSE; } Файл pstart.h (листинг 3.2) содержит прототипы функций, определенных в приложении PSTART. Листинг 3.2. Файл pstart/pstart.h // ----------------------------------------------------- // Описание функций // ----------------------------------------------------- LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); void WndProc_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify); void StartProcess(HWND hwnd); LRESULT WINAPI DlgProc(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam); BOOL DlgProc_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam); void DlgProc_OnCommand(HWND hdlg, int id, HWND hwndCtl, UINT codeNotify); Файл resource.h (листинг 3.3) создается автоматически и содержит определения констант для файла описания ресурсов приложения PSTART. Листинг 3.3. Файл pstart/resource.h //{{NO_DEPENDENCIES}} // Microsoft Developer Studio generated include file. // Used by PStart.rc // #define IDR_MENU1 102 #define IDR_APPMENU 102 #define IDI_APPICON 103 #define IDI_APPICONSM 104 #define IDD_DIALOG1 105 #define IDC_Realtime 1004 #define IDC_HIGH 1005 #define IDC_NORMAL 1006 #define IDC_IDLE 1007 #define IDC_TERMINATION 1008 #define IDC_DETACHED 1009 #define ID_FILE_EXIT 40001 #define ID_HELP_ABOUT 40002 #define ID_FILE_STARTPROCESS 40003 #define ID_FILE_OPTIONS 40004 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 106 #define _APS_NEXT_COMMAND_VALUE 40005 #define _APS_NEXT_CONTROL_VALUE 1010 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif В файле определения ресурсов приложения PSTART (листинг 3.4), который создается автоматически системой разработки Microsoft Visual C++, определены главное меню приложения, диалоговая панель для изменения параметров запуска процессов, пиктограммы и текстовые строки. Листинг 3.4. Файл pstart/pstart.rc //Microsoft Developer Studio generated resource script. // #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "afxres.h" ////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ////////////////////////////////////////////////////////////// // English (U.S.) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) #ifdef _WIN32 LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #pragma code_page(1252) #endif //_WIN32 #ifdef APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE DISCARDABLE BEGIN "resource.h\0" END 2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "\0" END 3 TEXTINCLUDE DISCARDABLE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // // Menu // IDR_APPMENU MENU DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "&Start process", ID_FILE_STARTPROCESS MENUITEM "&Options...", ID_FILE_OPTIONS MENUITEM SEPARATOR MENUITEM "&Exit", ID_FILE_EXIT END POPUP "&Help" BEGIN MENUITEM "&About", ID_HELP_ABOUT END END ////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_APPICON ICON DISCARDABLE "PSTART.ICO" IDI_APPICONSM ICON DISCARDABLE "PSTARTSM.ICO" ////////////////////////////////////////////////////////////// // // Dialog // IDD_DIALOG1 DIALOG DISCARDABLE 0, 0, 163, 97 STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "Start Options" FONT 8, "MS Sans Serif" BEGIN DEFPUSHBUTTON "OK",IDOK,103,9,50,14 PUSHBUTTON "Cancel",IDCANCEL,103,26,50,14 GROUPBOX "Priority class",IDC_STATIC,9,4,57,69 CONTROL "Realtime",IDC_Realtime,"Button", BS_AUTORADIOBUTTON | WS_GROUP,15,18,43,10 CONTROL "High",IDC_HIGH,"Button", BS_AUTORADIOBUTTON,15,32,31,10 CONTROL "Normal",IDC_NORMAL,"Button", BS_AUTORADIOBUTTON,15,45,38,10 CONTROL "Idle",IDC_IDLE,"Button", BS_AUTORADIOBUTTON,15,58,27,10 CONTROL "Wait process termination", IDC_TERMINATION,"Button", BS_AUTOCHECKBOX | WS_TABSTOP,15,81,93,10 END ////////////////////////////////////////////////////////////// // // DESIGNINFO // #ifdef APSTUDIO_INVOKED GUIDELINES DESIGNINFO DISCARDABLE BEGIN IDD_DIALOG1, DIALOG BEGIN LEFTMARGIN, 7 RIGHTMARGIN, 156 TOPMARGIN, 7 BOTTOMMARGIN, 90 END END #endif // APSTUDIO_INVOKED #endif // English (U.S.) resources ////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED Определения и глобальные переменныеДля работы со стандартной диалоговой панелью, с помощью которой выбирается запускаемый программный файл, в исходный текст приложения включается файл commctrl.h. В области глобальных переменных нашего приложения определены переменные dwCreationFlags и fWaitTermination. Первая из них содержит флаги создания процессов, которые настраиваются при помощи диалоговой панели Start Options и используются при запуске процессов функцией CreateProcess XE "CreateProcess" . Во второй переменной хранится признак необходимости ожидания завершения процессов. Содержимое этой переменной изменяется также при помощи диалоговой панели Start Options. Описание функцийПриведем краткое описание наиболее важных функций, определенных в нашем приложении. Функция WinMainФункция WinMain выполняет обычные действия, создавая главное окно приложения и запуская цикл обработки сообщений. Функция WndProcВ задачу функции WndProc входит обработка сообщения WM_COMMAND, которая выполняется с помощью функции WndProc_OnCommand. Все остальные сообщения передаются функции DefWindowProc. Функция WndProc_OnCommandЭта функция обрабатывает сообщение WM_COMMAND, поступающее в главное окно приложения от меню. Когда пользователь выбирает из меню File строку Start process, функция WndProc_OnCommand вызывает функцию StartProcess, определенную в нашем приложении. Последняя отображает на экране стандартную панель выбора программного файла и в случае успешного выбора запускает этот файл на выполнение как отдельный процесс. При этом используются параметры запуска, установленные при помощи диалоговой панели Start Options. В том случае когда пользователь выбирает из меню File строку Options, на экране отображается модальная диалоговая панель Start Options, имеющая идентификатор MAKEINTRESOURCE XE "MAKEINTRESOURCE" (IDD_DIALOG1). Отображение диалоговой панели выполняется с помощью функции DialogBox: DialogBox(hInst, MAKEINTRESOURCE(IDD_DIALOG1), hWnd, DlgProc); Эта функция, а также все, что относится к диалоговым панелям, мы описали в главе “Диалоговые панели” 12 тома “Библиотеки системного программиста”. Заметим, что в среде 32-разрядных операционных систем Microsoft Windows 95 и Microsoft Windows NT в качестве последнего параметра функции DialogBox можно указывать имя функции диалога. При этом вам не требуется создавать переходник диалоговой функции, вызывая функцию MakeProcInstance. Функция MakeProcInstance не используется 32-разрядными приложениями. Функция StartProcessФункция StartProcess выполняет запуск программного файла, выбранного пользователем. Для выбора программного файла используется функция GetOpenFileName, которую мы подробно описали в разделе “Стандартные диалоговые панели для открытия файлов” 13 тома “Библиотеки системного программиста”. Путь к выбранному файлу записывается в поле lpstrFile структуры ofn. Запуск процесса выполняется функцией CreateProcess, которая вызывается следующим образом: if(CreateProcess(NULL, ofn.lpstrFile, NULL, NULL, FALSE, dwCreationFlags, NULL, NULL, &si, &pi)) { . . . } Первый параметр функции CreateProcess указан как NULL, поэтому адрес символьной строки, содержащей путь к программному файлу, передается через второй параметр. Напомним, что с помощью этого параметра можно передать функции CreateProcess командную строку (с параметрами) для запуска приложения. Третий и четвертый параметры функции, задающие атрибуты защиты процесса и его главной задачи, указаны как NULL. В результате используются значения атрибутов защиты, принятые по умолчанию. Пятый параметр, определяющий возможность наследования идентификаторов процесса и главной задачи указан как FALSE, поэтому наследование не допускается. Через шестой параметр функции CreateProcess передаются флаги создания процесса. Здесь мы используем глобальную переменную dwCreationFlags, содержимое которой устанавливается функцией диалога диалоговой панели Create Options. Эта функция будет описана ниже. Седьмой и восьмой параметры функции CreateProcess, задают, соответственно, адрес блока среды и путь к рабочему каталогу. Так как оба этих параметра указаны как NULL, создаваемый дочерний процесс пользуется родительской средой и родительским рабочим каталогом. Через девятый параметр функции CreateProcess передается адрес структуры si типа STARTUPINFO XE "STARTUPINFO" . Ни одно из полей этой структуры, кроме поля cb, не используется, поэтому инициализация структуры выполняется очень просто: memset(&si, 0, sizeof(STARTUPINFO)); si.cb = sizeof(si); И, наконец, через последний, десятый параметр функции CreateProcess передается адрес структуры pi типа PROCESS_INFORMATION. Напомним, что после удачного запуска процесса в эту структуру записываются идентификаторы, а также системные номера процесса и его главной задачи. В случае успешного запуска процесса функция StartProcess проверяет необходимость ожидания его завершения. По умолчанию ожидание не выполняется, однако если пользователь включил в диалоговой панели Start Options переключатель Wait process termination, функция этой диалоговой панели записывает в глобальную переменную fWaitTermination значение TRUE. При этом функция StartProcess будет ожидать завершение запущенного процесса. В режиме ожидания функция StartProcess вначале закрывает ненужный ей больше идентификатор главной задачи процесса pi.hThread, а затем вызывает функцию WaitForSingleObject XE "WaitForSingleObject" , передавая ей в качестве первого параметра идентификатор процесса pi.hProcess, завершение которого необходимо дождаться. Второй параметр задает ожилание в течении неограниченного времени: if(WaitForSingleObject(pi.hProcess, INFINITE) != WAIT_FAILED) { . . . } Функция WaitForSingleObject будет описана подробно в главе, посвященной синхронизации задач и процессов. Сейчас отметим только, что эта функция переводит главную задачу приложения PSTART в состояние ожидания, когда ей не выделяются кванты процессорного времени. Такое ожидание не снижает производительность работы системы. После того как запущенный процесс завершит свою работу, функция WaitForSingleObject вернет управление вызвавшей ее функции StartProcess. Вслед за этим приложение получит и отобразит код завершения процесса. Для получения кода завершения процесса мы воспользовались функцией GetExitCodeProcess: GetExitCodeProcess(pi.hProcess, &dwExitCode); Через первый параметр этой функции передается идентификатор завершившегося процесса, а через второй - адрес переменной типа DWORD, в которую будет записан код завершения. После отображения кода завершения идентификатор процесса освобождается функцией CloseHandle XE "CloseHandle" . В том случае, когда процесс был запущен без ожидания его завершения, сразу после запуска функция StartProcess закрывает идентификаторы процесса и его главной задачи: CloseHandle(pi.hProcess); CloseHandle(pi.hThread); При выполнении функции CreateProcess возможно возникновение ошибок. Например, вы можете выбрать не программный, а текстовый файл и попытаться запустить его на выполнение. Возможно, для запуска процесса не хватит виртуальной памяти или других ресурсов. В этом случае приложение PSTART с помощью функции GetLastError XE "GetLastError" получает код ошибки и отображает его на экране. Функция DlgProcФункция диалога DlgProc обарбатывает сообщения WM_INITDIALOG и WM_COMMAND, поступающие от диалоговой панели Start Options. Для обработки этих сообщений она вызывает, соответственно, функции DlgProc_OnInitDialog и DlgProc_OnCommand. Функция DlgProc_OnInitDialogЭта функция обрфабатывает сообщение WM_INITDIALOG XE "WM_INITDIALOG" , поступающее в функцию диалога при инициализации последнего. Задачей обработчика сообщения WM_INITDIALOG является установка органов управления, расположенных в диалоговой панели, в начальное состояние. Мы устанавливаем во включенное состояние переключатель Normal, который определяет нормальный класс приоритета для запускаемых процессов. Установка переключателей выполняется макрокомандой CheckDlgButton XE "CheckDlgButton" , подробно описанной в 12 томе “Библиотеки системного программиста”: CheckDlgButton(hdlg, IDC_NORMAL, 1); Функция DlgProc_OnCommandЗадачей функции DlgProc_OnCommand является обработка сообщения WM_COMMAND, поступающего в функцию диалогоа от органов управления, расположенных в диалоговой панели Start Options. Когда пользователь нажимает кнопку OK, функция DlgProc_OnCommand определяет текущее состояние органов управления и устанавливает соответствующим образом содержимое двух глобальных переменных с именами dwCreationFlags и fWaitTermination. В переменную dwCreationFlags записывается выбранный класс приоритета запускаемого процесса. Состояние переключателя с зависимой фиксацией, отвечающего за выбор класса приоритета, определяется с помощью макрокоманды IsDlgButtonChecked XE "IsDlgButtonChecked" . Что же касается глобальной переменной fWaitTermination, то ее значение устанавливается в соответствии с сотоянием переключателя с независимой фиксацией Wait process termination. После того как содержимое глобальных переменных будет установлено, диалоговая панель удаляется функцией EndDialog XE "EndDialog" . Если пользователь отменяет работу с диалоговой панели, то происходит удаление последней без изменения содержимого глобальных переменных dwCreationFlags и fWaitTermination. |