Шаг 63.
Создание процесса

    На этом шаге мы рассмотрим создание процесса при многозадачном программировании.

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

    Данное определение весьма ясно раскрывает суть понятия процесс. Но для большинства рассматриваемых здесь проблем, достаточно было бы дать и более простое определение. Например, такое: всякий исполняемый модуль (EXE), запущенный в операционной системе Windows, становится процессом.

    Теперь что касается подпроцесса. Смысл его достаточно прост: каждый процесс в отведенном для него адресном пространстве может порождать еще процессы. Эти процессы выполняются независимо друг от друга и от порождающего их процесса. Однако порождающий процесс может при желании принудительно завершить выполнение любого из порожденных им процессов. Такие процессы называют еще потоками, а также цепочками или нитями. Теперь мы понимаем, что в предыдущих шагах при помощи таймера мы создавали что-то наподобие потоков. Однако в операционной системе Windows для создания потоков есть и специальные средства, речь о которых пойдет дальше.

    Теперь немного поговорим о типах многозадачности. В старой 16-битной Windows переключение между задачами происходило только тогда, когда задача отдавала управление операционной системе. Такая многозадачность называется невытесняющей. В определенном смысле это было даже хуже, чем в операционной системе MS-DOS. Там элементы многозадачности осуществлялись при помощи так называемых резидентных программ. Они перехватывали прерывание от таймера, клавиатуры или другого устройства и, в результате, получали управление по событиям, связанным с этими устройствами.

    Положение, существовавшее в старой операционной системе Windows, требовало от программиста выполнения джентльменского правила - не захватывать надолго время микропроцессора. Некоторым решением проблемы являлось использование таймеров (в чем мы уже убедились), а также использование функции PeekMessage вместо GetMessage. Функция PeekMessage, в отличие от GetMessage, возвращает управление сразу, даже если в очереди нет ни одного сообщения.

    В 32-битных операционных системах Windows (Windows 9x, Windows NT, Windows 2000) реализована вытесняющая схема многозадачности, в которой переключением между процессами и потоками занимается операционная система. Если процесс слишком долго выполняет некоторую операцию, то курсор над окном процесса преобразуется в песочные часы. При этом другие процессы будут по-прежнему выполняться и вы сможете переключаться на них. А вот доступ к окну данного процесса может оказаться затруднительным. Решить данную проблему можно уже упомянутым способом, заменив в цикле ожидания GetMessage на PeekMessage. Однако более правильным решением будет разбиение процесса на некоторое количество потоков.

    Созданием потоков мы в следующих шагах, а затем остановимся на создании процессов. Ваше приложение может создавать процессы, запустив ту или иную ЕХЕ-программу, которые будут работать независимо от основного приложения. Одновременно ваше приложение может при необходимости удалить запущенное им приложение из памяти. Запустить приложение (создать процесс) можно при помощи функции CreateProcess. Сейчас мы дадим описание этой функции. Ниже объясняются ее параметры.

    Основное отличие дескриптора от идентификатора заключается в том, что дескриптор уникален лишь в пределах данного процесса, идентификатор же является глобальной величиной. Посредством идентификатора может быть найдена область данных текущего процесса. У читателя, сразу возникнет вопрос: а чем же отличается дескриптор приложения, который мы получаем при помощи функции GetModulHandle, от только что упомянутых величин? Дескриптор приложения, или дескриптор модуля, есть величина локальная, т.е. действующая в пределах данного процесса и, как правило, равная адресу загрузки модуля в виртуальное адресное пространство. Дескриптор модуля имеется у любого модуля, загруженного в память, в том числе и у подчиненных DLL-библиотек.

    Рассмотрим теперь структуру, на которую указывает 9-й параметр функции CreateProcess. Вот эта структура:

;Структура для CreateProcess.
STARTUP  STRUC		
         cb               DD  0
         lpReserved       DD  0
         lpDesktop        DD  0
         lpTitle          DD  0
         dwX              DD  0
         dwY              DD  0
         dwXSize          DD  0
         dwYSize          DD  0
         dwXCountChars    DD  0
         dwYCountChars    DD  0
         dwFillAttribute  DD  0
         dwFlags          DD  0
         wShowWindow      DW  0
         cbReserved2      DW  0
         lpReserved2      DD  0
         hStdInput        DD  0
         hStdOutput       DD  0
         hStdError        DD  0
STARTUP  ENDS		

    Итак, разберем смысл полей этой структуры.

    Следующий пример представляет собой простейший пример создания процесса. В качестве программы, порождающей процесс, взят редактор MS Word (WINWORD.EXE). Для проверки правильности работы примера вам придется указать путь к MS Word на вашем компьютере (переменная PATH). Обратите внимание на то, что приложение появляется на экране в свернутом виде и как это достигается.

    Файл pr63_1.rc:

//Определение констант.
#define WS_SYSMENU         0x00080000L
#define WS_POPUP           0x80000000L
#define DS_3DLOOK          0x0004L
//Ресурс - меню.
MENUP  MENU
{
  POPUP "&3апуск Word"
  {
    MENUITEM "&3апустить", 1
    MENUITEM "&Удалить ", 2
    MENUITEM "Выход из &программы", 3
  }
}
//Определение диалогового окна.
DIAL1 DIALOG 0, 0, 240, 120
STYLE WS_POPUP | WS_SYSMENU | DS_3DLOOK
CAPTION "Пример немодального диалогового окна"
FONT 8, "Arial"
{
}

    Файл pr63_1.inc:

;Константы.
STARTF_USESHOWWINDOW equ 1h
SW_SHOWMINIMIZED     equ 2
;Сообщение приходит при закрытии окна.
WM_CLOSE      equ 10h
WM_INITDIALOG equ 110h
WM_COMMAND    equ 111h
;Прототипы внешних процедур.
IFDEF MASM
    EXTERN  TerminateProcess@8:NEAR 
    EXTERN  CreateProcessA@40:NEAR 
    EXTERN  DialogBoxParamA@20:NEAR 
    EXTERN  EndDialog@8:NEAR 
    EXTERN  MessageBoxA@16:NEAR 
    EXTERN  ExitProcess@4:NEAR 
    EXTERN  GetModuleHandleA@4:NEAR 
    EXTERN  LoadMenuA@8:NEAR 
    EXTERN  SetMenu@8:NEAR 
    EXTERN  TranslateMessage@4:NEAR
ELSE
    EXTERN  TerminateProcess:NEAR
    EXTERN  CreateProcessA:NEAR
    EXTERN  DialogBoxParamA:NEAR
    EXTERN  EndDialog:NEAR
    EXTERN  MessageBoxA:NEAR
    EXTERN  ExitProcess:NEAR
    EXTERN  GetModuleHandleA:NEAR
    EXTERN  LoadMenuA:NEAR
    EXTERN  SetMenu:NEAR
    EXTERN  TranslateMessage:NEAR
    TerminateProcess@8 = TerminateProcess
    CreateProcessA@40  = CreateProcessA
    DialogBoxParamA@20 = DialogBoxParamA
    EndDialog@8        = EndDialog
    MessageBoxA@16     = MessageBoxA
    ExitProcess@4      = ExitProcess
    GetModuleHandleA@4 = GetModuleHandleA
    LoadMenuA@8        = LoadMenuA
    SetMenu@8          = SetMenu
    TranslateMessage@4 = TranslateMessage
ENDIF
;Структуры.
;Структура сообщения.
MSGSTRUCT  STRUC	
           MSHWND     DD ? ;Идентификатор окна, получающего сообщение.
           MSMESSAGE  DD ? ;Идентификатор сообщения.
           MSWPARAM   DD ? ;Доп. информация о сообщении.
           MSLPARAM   DD ? ;Доп. информация о сообщении.
           MSTIME     DD ? ;Время посылки сообщения.
           MSPT       DD ? ;Положение курсора во время посылки сообщения.
MSGSTRUCT ENDS	
;Структура для CreateProcess.
STARTUP  STRUC		
         cb               DD  0
         lpReserved       DD  0
         lpDesktop        DD  0
         lpTitle          DD  0
         dwX              DD  0
         dwY              DD  0
         dwXSize          DD  0
         dwYSize          DD  0
         dwXCountChars    DD  0
         dwYCountChars    DD  0
         dwFillAttribute  DD  0
         dwFlags          DD  0
         wShowWindow      DW  0
         cbReserved2      DW  0
         lpReserved2      DD  0
         hStdInput        DD  0
         hStdOutput       DD  0
         hStdError        DD  0
STARTUP  ENDS		
;Структура - информация о процессе.
PROCINF STRUC		
         hProcess DD ?		
         hThread  DD ?		
         Idproc   DD ? 
         idThr    DD ?
PROCINF ENDS 

    Файл pr63_1.asm:

.386P
;Плоская модель.
.MODEL FLAT, STDCALL
include pr63_1.inc
;Директивы компоновщику для подключения библиотек.
IFDEF MASM
;Для компоновщика  LINK.EXE.
    includelib \masm32\lib\user32.lib
    includelib \masm32\lib\kernel32.lib
ELSE
;Для компоновщика  TLINK32.EXE.
    includelib c:\tasm32\lib\import32.lib
ENDIF
;------------------------------------------------
;Сегмент данных. 
_DATA SEGMENT DWORD PUBLIC USE32 'DATA'
     NEWHWND    DD   0
     MSG        MSGSTRUCT <?>
     STRUP      STARTUP   <?>
     INF        PROCINF   <?>
     HINST      DD 0 ;Дескриптор приложения.
     PA         DB "DIAL1",0
     PMENU      DB   "MENUP",0
     PATH       DB  "C:\Program Files\Microsoft Office\Office10\WINWORD.EXE",0
_DATA ENDS 
;Сегмент кода.
_TEXT SEGMENT DWORD PUBLIC USE32 'CODE'
START: 
;Получить дескриптор приложения.
     PUSH  0
     CALL  GetModuleHandleA@4
     MOV   [HINST], EAX
;-------------------------------------
;Создать диалоговое окно.
     PUSH  0
     PUSH  OFFSET WNDPROC
     PUSH  0
     PUSH  OFFSET PA
     PUSH  [HINST] 
     CALL  DialogBoxParamA@20 
     CMP   EAX,-1 
     JNE   KOL 
;Здесь можно разместить сообщение об ошибке.
KOL:
;-------------------------------------
     PUSH  0
     CALL  ExitProcess@4
;-------------------------------------
;-------------------------------------
;Процедура окна.
;Расположение параметров в стеке: 
;[ЕВР+014Н] LPARAM 
;[ЕВР+10Н] WAPARAM 
;[ЕВР+0СН] MES 
;[ЕВР+8] HWND 
WNDPROC PROC
        PUSH EBP
        MOV  EBP, ESP
        PUSH EBX
        PUSH ESI
        PUSH EDI
;-------------------------------------
;Сообщение при закрытии окна.
        CMP  DWORD PTR [EBP+0CH],WM_CLOSE
        JNE  L1
;Закрыть диалоговое окно.
        JMP  L5 
L1: 
;Сообщение при инициализации окна.
        CMP  DWORD PTR [EBP+0CH], WM_INITDIALOG
        JNE  L3 
;Загрузить меню.
        PUSH OFFSET PMENU
        PUSH [HINST]
        CALL LoadMenuA@8 
;Установить меню.
        PUSH EAX
        PUSH DWORD PTR [EBP+08H]
        CALL SetMenu@8
        JMP  FINISH 
;Проверяем, не случилось ли чего с пунктами
;меню в диалоговом окне. 
L3:
        CMP  DWORD PTR [EBP+0CH],WM_COMMAND
        JNE  FINISH
        CMP  WORD PTR [EBP+10H], 3 
        JE   L5
        CMP  WORD PTR [EBP+10H], 2 
        JE   L7
        CMP  WORD PTR [EBP+10H], 1 
        JE   L6 
        JMP  FINISH
;Закрыть диалоговое окно. 
L5:
        PUSH 0
        PUSH DWORD PTR [EBP+08H] 
        CALL EndDialog@8 
        JMP  FINISH
;Запустить программу WORD. 
L6:
;Заполняем структуру для запуска. 
;Окно должно появляться в свернутом виде. 
        MOV  STRUP.cb,68 
        MOV  STRUP.lpReserved,0 
        MOV  STRUP.lpDesktop,0 
        MOV  STRUP.lpTitle,0
        MOV  STRUP.dwFlags,STARTF_USESHOWWINDOW 
        MOV  STRUP.cbReserved2,0 
        MOV  STRUP.lpReserved2,0
        MOV  STRUP.wShowWindow,SW_SHOWMINIMIZED 
;Запуск приложения Winword. 
        PUSH OFFSET INF 
        PUSH OFFSET STRUP 
        PUSH 0 
        PUSH 0 
        PUSH 0 
        PUSH 0 
        PUSH 0 
        PUSH 0 
        PUSH OFFSET PATH
        PUSH 0 
        CALL CreateProcessA@40
        JMP  FINISH
;Удалить из памяти процесс. 
L7:
        PUSH 0 ;Код выхода.
        PUSH INF.hProcess
        CALL TerminateProcess@8 
FINISH:
        MOV EAX,0
        POP  EDI
        POP  ESI
        POP  EBX
        POP  EBP
        RET  16
WNDPROC ENDP
_TEXT   ENDS	
        END START
Текст этой программы можно взять здесь.

    Трансляция программы:

    На следующем шаге мы рассмотрим создание и использование потока.




Предыдущий шаг Содержание Следующий шаг