3. Процессы

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

При необходимости организации параллельной обработки данных в отдельном адресном пространстве приложение может запустить отдельный процесс XE "процесс". Разумеется, процесс требует намного больше ресурсов, чем задача, и, кроме того, возникает проблема организации обмена данными. Так как дочерний процесс работает в своем адресном пространстве, родительский процесс не может использовать для передачи данных, например, глобальные переменные, - в адресном пространстве дочернего процесса они будут недоступны.

Тем не менее, передача данных между различными процессами возможна, например, с использованием динамической передачи данных DDE, файлов, отображаемых в память и так далее. В дальнейшем в одной из наших следующих книг мы подробно рассмотрим средства взаимодействия процессов, встроенные в операционную систему Microsoft Windows NT, в том числе процессов, запущенных на различных компьютерах в сети.

Предметом же этой главы будет изучение способов запуска процессов и управления процессами.

Запуск процесса

Пользователь запускает процесс при помощи приложений Program Manager и File Manager. Он может также воспользоваться командной строкой в системном приглашении консоли Microsoft Windows NT.

Что же касается приложения, то оно может выполнить эту задачу как при помощи уже известных вам из программирования для Microsoft Windows версии 3.1 функций WinExec XE "WinExec" и LoadModule XE "LoadModule", так и при помощи функции CreateProcess XE "CreateProcess", специально предназначенной для запуска процессов. Заметим, что с помощью этой функции в середе Microsoft Windows NT версии 3.51 для платформы Intel вы можете запустить как 32-разрядные, так и 16-разрядные приложения Windows, а также программы MS-DOS и 16-разрядные консольные приложения OS/2 (напомним, что первые версии операционной системы OS/2 разрабатывались совместно фирмами Microsoft и IBM).

Параметры функции CreateProcess

Функция CreateProcess имеет много параметров, однако ей не так сложно пользоваться, как это может показаться на первый взгляд:


BOOL CreateProcess(
  LPCTSTR lpApplicationName,  // указатель на имя исполняемого 
                              // модуля 
  LPTSTR  lpCommandLine,      // указатель на командную строку
  LPSECURITY_ATTRIBUTES lpProcessAttributes, // указатель на 
                        //          атрибуты защиты процесса
  LPSECURITY_ATTRIBUTES lpThreadAttributes,  // указатель на 
                        //          атрибуты защиты задачи
  BOOL bInheritHandles, // флаг наследования идентификатора 
  DWORD dwCreationFlags,// флаги создания процесса 
  LPVOID lpEnvironment, // указатель на блок среды выполнения 
  LPCTSTR lpCurrentDirectory,  // указатель на имя текущего 
                               // каталога 
  LPSTARTUPINFO lpStartupInfo, // указатель на структуру 
                               //  STARTUPINFO 
  LPPROCESS_INFORMATION lpProcessInformation); // указатель на 
                            // структуру PROCESS_INFORMATION  

Если функция CreateProcess завершается успешно, она возвращает значение TRUE. В противном случае возвращается значение FALSE. Код ошибки вы можете получить, вызвав функцию GetLastError XE "GetLastError".

lpApplicationName

lpCommandLine

Параметры lpApplicationName и lpCommandLine используются для указания имя программного файла и параметров запуска процесса. Вы можете использовать один или оба этих параметра.

Через параметр lpApplicationName вы можете передать указатель на строку, закрытую двоичным нулем и содержащую полный либо частичный путь к программному файлу. При этом параметр lpCommandLine может иметь значение NULL либо указывать на строку параметров запуска приложения. Если это потребуется, приложение может извлечь адрес строки параметров через параметы функции WinMain, либо при помощи функции GetCommandLine, не имеющей параметров. Последняя возвращает адрес искомой строки.

Вы можете также передать через параметр lpApplicationName значение NULL, указав адрес строки пути к программному файлу через параметр lpCommandLine. В этом случае можно передать параметры процессу, добавив их в этой строке к пути файла через пробел. Таким образом, в параметре lpCommandLine можно указать адрес командной строки для запуска процесса.

lpProcessAttributes

lpThreadAttributes

Параметры lpProcessAttributes и lpThreadAttributes указывают атрибуты защиты, соответвтвенно, процесса и его главной задачи, которая получает управление при запуске процесса. В наших примерах мы будем указывать оба этих параметра как NULL, используя значения атрибутов защиты, принятые по умолчанию.

bInheritHandles

Флаг наследования идентификатора процесса и задачи bInheritHandles определяет, как это видно из названия, возможность использования данных идентификаторов в порожденных процессах и задачах. Если такая возможность вам необходима, указите для этого параметра значение TRUE. В наших приложениях мы будем указывать значение FALSE, отменяющее наследование.

dwCreationFlags

Рассмотрим теперь параметр dwCreationFlags.

Этот параметр определяет способ, которым будет запущен процесс, а также класс приоритета процесса. Соответственно, для параметра dwCreationFlags можно использовать флаги создания процесса, объединяя их между собой оператором логического ИЛИ, а также с одним из значений, определяющих класс приоритета процесса.

Флаги создания процесса перечислены ниже:

Сразу после создания процесса его главная задача будет находиться в приостановленном состоянии. Работу этой задачи можно возобновить при помощи функции ResumeThread XE "ResumeThread". Этот флаг может быть использован при отладке процесса.

Флаг DEBUG_PROCESS используется отладчиками, которые создают процесс для отладки. Если указан этот флаг, родительский процесс (то есть отладчик) информируется о различных событиях, возникающих в отлаживаемом процессе.

Аналогично предыдущему, однако отладчик извещается о тех событиях, которые происходят только в отлаживаемом процессе, но не в процессах, запущенных отлаживаемым процессом.

Этот флаг используется в том случае, если для блока среды процесса, адрес которого передается через параметр lpEnvironment, используется кодировка Unicode. В противном случае предполагается, что для блока среды используются символы в коде ANSI.

Рассмотрение кодировки Unicode выходит за рамки этой книги, однако мы, возможно, расскажем вам о ней в одной из следующих наших книг, посвященных операционной системе Microsoft Windows NT.

Используется для консольных процессов. Если указан флаг CREATE_NEW_CONSOLE, для нового процесса создается новая консоль. Консольные процессы мы не будем пока рассматривать для экономии места в книге. Этот флаг несовместим с флагом DETACHED_PROCESS.

Используется для консольных процессов. Если указан этот флаг, новый консольный процесс не имеет доступа к родительской консоли. При необходимости он может создать новую консоль. Этот флаг несовместим с флагом CREATE_NEW_CONSOLE.

Используется для консольных процессов. Новый процесс будет корневым для группы процессов.

Используется для запуска 16-разрядных приложоений Microsoft Windows. Если установлен флаг CREATE_SEPARATE_WOW_VDM, для работы приложения создается отдельная виртуальная машина DOS. Если произойдет ошибка в этом приложении, то она не скажется на работе остальных 16-разрядных приложений Microsoft Windows, работающих на других виртуальных машинах (так как последние находятся в другом адресном пространстве).

Новый процесс не наследует режим обработки ошибок, установленный родительским процессом при помощи функции SetErrorMode, и должен устанавливать этот режим самостоятельно.

Приведенные выше четыре флага указывают класс приоритета нового процесса. Обычно вы должны использовать значение NORMAL_PRIORITY_CLASS XE "NORMAL_PRIORITY_CLASS".

lpEnvironment

Наряду с регистрационной базой данных, в которой приложения могут хранить необходимые им для выполнения программы, операционная система Microsoft Windows NT сохранила такой атавизм MS-DOS, как среда выполнения программ (очевидно, сохранила для обратной совместимости с этой операционной системой). В MS-DOS переменные среды устанавливались при помощи команд в файле autoexec.bat. Операционная система Microsoft Windows NT версии 3.51 позволяет это сделать через приложение Control Panel.

Если дочерний процесс должен использовать родительский блок среды выполнения, через параметр lpEnvironment необходимо передать значение NULL.

Родительский процесс может также подготовить новый блок среды, состоящий из расположенных друг за другом символьных строк, закрытых двоичным нулем. Последняя из этих строк должна быть закрыта двумя двоичными нулями.

При необходимости процесс может получить адрес блока среды при помощи функции GetEnvironmentStrings, не имеющей параметров. Функция GetEnvironmentVariable позовляет узнать значение отдельных переменных блока среды. Описание этой функции вы найдете в SDK.

lpCurrentDirectory

Параметр lpCurrentDirectory задает путь к символьной строке, закрытой двоичным нулем, содержащей путь к каталогу, который будет использован как рабочий при запуске процесса. Если указать этот параметр как NULL, в качестве рабочего для нового процесса будет использован рабочий каталог родительского процесса.

lpStartupInfo

Через параметр lpStartupInfo вы должны передать функции CreateProcess XE "CreateProcess" указатель на структуру типа STARTUPINFO, определяющую внешний вид окна, создаваемого для процесса:


typedef struct _STARTUPINFO 
{  
  DWORD  cb;              // размер структуры в байтах
  LPTSTR lpReserved;      // зарезервировано
  LPTSTR lpDesktop;    // рабочий стол и станция для процесса
  LPTSTR lpTitle;      // заголовок окна консольного процесса
  DWORD  dwX;             // координата угла окна в пикселах
  DWORD  dwY;             // координата угла окна в пикселах
  DWORD  dwXSize;         // ширина окна в пикселах
  DWORD  dwYSize;         // высота окна в пикселах
  DWORD  dwXCountChars;   // ширина консольного окна
  DWORD  dwYCountChars;   // высота консольного окна
  DWORD  dwFillAttribute; // атрибуты текста консольного окна
  DWORD  dwFlags;         // заполненные поля структуры
  WORD   wShowWindow;     // размеры окна по умолчанию
  WORD   cbReserved2;     // зарезервировано
  LPBYTE lpReserved2;     // зарезервировано
  HANDLE hStdInput;       // консольный буфер ввода
  HANDLE hStdOutput;      // консольный буфер вывода
  HANDLE hStdError; // консольный буфер вывода сообщений 
                    // об ошибках
} STARTUPINFO, *LPSTARTUPINFO;

Несмотря на внушительный размер этой структуры, ее заполнение не вызовет у вас особых трудностей, так как большинство полей можно не использовать.

cb

Поле cb должно содержать размер структуры STARTUPINFO в байтах.

dwFlags

Поле dwFlags содержит флаги, определяющие, содержимое каких полей структуры STARTUPINFO необходимо учитывать при запуске нового процесса, а также три дополнительных флага:

Значение Используемые поля
STARTF_USESHOWWINDOW wShowWindow
STARTF_USEPOSITION dwX, dwY
STARTF_USESIZE dwXSize, dwYSize
STARTF_USECOUNTCHARS dwXCountChars, dwYCountChars
STARTF_USEFILLATTRIBUTE dwFillAttribute
STARTF_USESTDHANDLES hStdInput, hStdOutput и hStdError

Дополнительными являются флаги STARTF_FORCEONFEEDBACK, STARTF_FORCEOFFFEEDBACK и STARTF_SCREENSAVER.

Использование первого из этих флагов приводит к тому что при запуске приложения курсор мыши принимает форму песочных часов со стрелкой. При этом пользователь может работать с другими приложениями, зная, что процесс запуска данного приложения еще не завершен. Как только приложение будет запущено и станет активным, курсор примет свою обычную форму.

Если же указан флаг STARTF_FORCEOFFFEEDBACK, в процессе запуска приложения курсор имеет обычную форму.

Флаг STARTF_SCREENSAVER используется для создания приложений, предохраняющих экран монитора от преждеверменного выгорания. Этот флаг вызывает снижение класса приоритета после запуска до IDLE_PRIORITY_CLASS XE "IDLE_PRIORITY_CLASS", несмотря на то что при инициализации класс приоритета был NORMAL_PRIORITY_CLASS XE "NORMAL_PRIORITY_CLASS". Если пользователь активизирует окно такого приложения, его класс приоритета автоматически повышается.

lpDesktop

Поле lpDesktop используется для выбора рабочего стола и рабочей станции, где будет запущен процесс. Если вы собираетесь использовать текущий рабочий стол и текущую рабочую станцию, укажите в этом поле значение NULL.

lpTitle

Для консольного процесса (и только для него) вы можете указать заголовок окна в поле lpTitle. Если же в этом поле задать значение NULL, для заголовка будет использовано имя файла.

dwX
dwY

Поля dwX и dwY определяют, соответственно, координаты X и Y левого верхнего угла окна графического приложения в пикселах. Эти значения используются для позиционирования главного окна приложения только в том случае, если при создании окна функцией CreateWindow расположение окна было указано как CW_USEDEFAULT XE "CW_USEDEFAULT".

dwXSize
dwYSize

Аналогично, поля dwXSize и dwYSize определяют, соответственно, ширину и высоту окна графического приложения в пикселах. Эти значения используются в том случае, если при создании окна функцией CreateWindow размыры окна были указаны как CW_USEDEFAULT XE "CW_USEDEFAULT".

dwXCountChars
dwYCountChars

Поля dwXCountChars и dwYCountChars задают, соответственно, ширину и высоту окна консольного приложения в символах.

dwFillAttribute

Содержимое поля dwFillAttribute задает цвет текста и фона для окна консольного приложнения.

wShowWindow

Поле wShowWindow определяет значение по умолчанию, которое будет использовано при первом вызове функции ShowWindow для главного окна приложения.

Остановимся на этом подробнее.

Как вы, наверное, помните, в приложениях Microsoft Windows версии 3.1 функция WinMain получала параметр nCmdShow, определяющий, в каком виде должно отображаться окно - в нормальном, минимизированном, максимизированном и так далее.

В среде Microsoft Windows NT параметр nCmdShow функции WinMain всегда имеет значение SW_SHOWDEFAULT XE "SW_SHOWDEFAULT". В этом случае для определения внешнего вида главного окна прилоджения используется содержимое поля wShowWindow структуры STARTUPINFO XE "STARTUPINFO". Здесь, а также в качестве параметров функции ShowWindow, вы можете использовать следующие значения:

Значение Внешний вид окна приложения
SW_MINIMIZE Минимизировано
SW_MAXIMIZE Максимизировано
SW_RESTORE Восстановлено в исходное состояние (это значение используется при восстановлении размеров минимизированного ранее окна)
SW_HIDE Скрыто
SW_SHOW Отображается с использованием текущих размеров и расположения
SW_SHOWDEFAULT Отображается с использованием размеров и расположения, заданных в структуре STARTUPINFO при создании процесса функцией CreateProcess XE "CreateProcess"
SW_SHOWMAXIMIZED Окно активизируется и отображается в максимизированном виде
SW_SHOWMINIMIZED Окно активизируется и отображается в минимизированном виде
SW_SHOWMINNOACTIVE Минимизируется, но не становится активным
SW_SHOWNA Окно отображается в текущем виде, но не активизируется
SW_SHOWNOACTIVATE Устанавливаются размеры и расположение окна, которые оно только что имело. Активизация окна не выполняется
SW_SHOWNORMAL XE "SW_SHOWNORMAL" Окно активизируется и отображается. Минимизированное окно восстанавливается

Вернемся к полям структуры STARTUPINFO.

hStdInput
hStdOutput
hStdError

Поля hStdInput, hStdOutput и hStdError определяют идентификаторы буферов, которые используются консольными приложениями, соответственно, для ввода, вывода обычной информации и вывода сообщений об ошибках.

lpProcessInformation

Перед вызовом функции CreateProcess вы должны передать ей через параметр lpProcessInformation адрес структуры типа PROCESS_INFORMATION XE "PROCESS_INFORMATION", в которую будут записаны идентификаторы и системные номера созданного процесса и его главной задачи:


typedef struct _PROCESS_INFORMATION
{ 
  HANDLE hProcess;    // идентификатор процесса
  HANDLE hThread;     // идентификатор главной задачи процесса
  DWORD  dwProcessId; // системный номер процесса
  DWORD  dwThreadId;  // системный номер главной задачи 
                      // процесса
} PROCESS_INFORMATION;

Пользуясь идентификаторами, полученными из полей hProcess и hThread, родительский процесс может управлять порожденным процессом и его главной задачей, например, изменяя класс приоритета процесса или относительный приоритет его главной задачи.

Важно отметить, что после использования родительский процесс обязан закрыть полученные идентификаторы порожденного процесса и задачи при помощи функции CloseHandle XE "CloseHandle".

Завершение процесса

Для завершения процесса используются функции ExitProcess и TerminateProcess. Первая из этих функций нужна в том случае, если процесс сам желает завершить свою работу. Именно эта функция вызывается библиотекой времени выполнения при возвращении управления функцией WinMain. Функция TerminateProcess используется родительским процессом для завершения своего дочернего процесса или любого другого процесса, идентификатор которого ей известен (и к которому имеется соотвтетствующий доступ).

Функция ExitProcess имеет один параметр - код завершения процесса:


VOID ExitProcess(UINT uExitCode);

Этот код после завершения работы процесса родительский процесс может определить при помощи функции GetExitCodeProcess.

Функции TerminateProcess необходимо передать два параметра - идентификатор завершаемого процесса и код завершения процесса:


BOOL TerminateProcess(
  HANDLE hProcess,   // идентификатор завершаемого процесса 
  UINT   uExitCode); // код завершения процесса

Независимо от способа, при завершении процесса закрываются идентификаторы всех объектов, созданных задачами процесса, а все задачи процесса завершают свое выполнение.

Приложение 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.