На этом шаге мы рассмотрим создание процесса при многозадачном программировании.
Под процессом будем понимать объект, создаваемый операционной системой Windows обычно при загрузке исполняемого модуля и получающий в единоличное пользование:
Данное определение весьма ясно раскрывает суть понятия процесс. Но для большинства рассматриваемых здесь проблем, достаточно было бы дать и более простое определение. Например, такое: всякий исполняемый модуль (EXE), запущенный в операционной системе Windows, становится процессом.
Теперь что касается подпроцесса. Смысл его достаточно прост: каждый процесс в отведенном для него адресном пространстве может порождать еще процессы. Эти процессы выполняются независимо друг от друга и от порождающего их процесса. Однако порождающий процесс может при желании принудительно завершить выполнение любого из порожденных им процессов. Такие процессы называют еще потоками, а также цепочками или нитями. Теперь мы понимаем, что в предыдущих шагах при помощи таймера мы создавали что-то наподобие потоков. Однако в операционной системе Windows для создания потоков есть и специальные средства, речь о которых пойдет дальше.
Теперь немного поговорим о типах многозадачности. В старой 16-битной Windows переключение между задачами происходило только тогда, когда задача отдавала управление операционной системе. Такая многозадачность называется невытесняющей. В определенном смысле это было даже хуже, чем в операционной системе MS-DOS. Там элементы многозадачности осуществлялись при помощи так называемых резидентных программ. Они перехватывали прерывание от таймера, клавиатуры или другого устройства и, в результате, получали управление по событиям, связанным с этими устройствами.
Положение, существовавшее в старой операционной системе Windows, требовало от программиста выполнения джентльменского правила - не захватывать надолго время микропроцессора. Некоторым решением проблемы являлось использование таймеров (в чем мы уже убедились), а также использование функции PeekMessage вместо GetMessage. Функция PeekMessage, в отличие от GetMessage, возвращает управление сразу, даже если в очереди нет ни одного сообщения.
В 32-битных операционных системах Windows (Windows 9x, Windows NT, Windows 2000) реализована вытесняющая схема многозадачности, в которой переключением между процессами и потоками занимается операционная система. Если процесс слишком долго выполняет некоторую операцию, то курсор над окном процесса преобразуется в песочные часы. При этом другие процессы будут по-прежнему выполняться и вы сможете переключаться на них. А вот доступ к окну данного процесса может оказаться затруднительным. Решить данную проблему можно уже упомянутым способом, заменив в цикле ожидания GetMessage на PeekMessage. Однако более правильным решением будет разбиение процесса на некоторое количество потоков.
Созданием потоков мы в следующих шагах, а затем остановимся на создании процессов. Ваше приложение может создавать процессы, запустив ту или иную ЕХЕ-программу, которые будут работать независимо от основного приложения. Одновременно ваше приложение может при необходимости удалить запущенное им приложение из памяти. Запустить приложение (создать процесс) можно при помощи функции CreateProcess. Сейчас мы дадим описание этой функции. Ниже объясняются ее параметры.
PROCINF STRUC hProcess DD ? ; Дескриптор созданного процесса. hThread DD ? ; Дескриптор главного потока нового процесса. Idproc DD ? ; Идентификатор созданного процесса. idThr DD ? ; Идентификатор главного потока нового процесса. PROCINF ENDS
Основное отличие дескриптора от идентификатора заключается в том, что дескриптор уникален лишь в пределах данного процесса, идентификатор же является глобальной величиной. Посредством идентификатора может быть найдена область данных текущего процесса. У читателя, сразу возникнет вопрос: а чем же отличается дескриптор приложения, который мы получаем при помощи функции 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
Итак, разберем смысл полей этой структуры.
Флаг | Константа | Назначение |
---|---|---|
Разрешить поле dwShowWindow. | ||
Разрешить dwXSize и dwYSize. | ||
Разрешить dwX и dwY. | ||
Разрешить dwXCountChars и dwYCountChars. | ||
Разрешить dwFillAttribute. | ||
Включить возврат курсора. | ||
Выключить возврат курсора | ||
Разрешить kStdInput. |
Следующий пример представляет собой простейший пример создания процесса. В качестве программы, порождающей процесс, взят редактор 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
Трансляция программы:
ML /с /coff /DMASM pr63_1.asm RC pr63_1.rc LINK /SUBSYSTEM:WINDOWS pr63_1.obj pr63_1.res
TASM32 /ml pr63_1.asm BRCC32 pr63_1.rc TLINK32 -aa pr63_1.obj,,,,,pr63_1.res
На следующем шаге мы рассмотрим создание и использование потока.