Прим.пер.: эта статья была написана относительно давно. Некоторые возможности, упомянутые в ней, с тех пор стали документированы. В этом случае я заменил придуманные автором имена на те, которые сейчас используются в документации MSDN. И в любом случае, стандартное предупреждение - использовать хаки только как последнее средство.
Этот мануал является частью новой серии, которая будет сосредоточена на некоторых не-GUI вопросах, связанных с программированием в Windows. Предметом этого мануала будет Win32 API функция
CreateProcess
. Эта статья разделена на несколько секций, каждая из которых описывает приятный факт о CreateProcess
, который можно использовать в своих интересах. То, что я буду описывать, нельзя найти в документации Microsoft, но эти вещи были обнаружены многими людьми на протяжении многих лет путём множества экспериментов. Вся информация, собранная здесь, была найдена в различных источниках - особенно в старых публикациях таких изданий, как "Windows Developer Journal", начиная с середины 90-х годов, а также старых сообщениях USENET.Прежде чем я начну говорить про недокументированные штуки, я хотел бы кратко рассказать про то, что делает
CreateProcess
, и как её использовать в вашем коде. Если вы знакомы с CreateProcess
, то просто пропустите эту секцию
function CreateProcess( lpApplicationName: PChar; // имя исполняемого модуля для запуска lpCommandLine: PChar; // командная строка lpProcessAttributes: PSecurityAttributes; // SD lpThreadAttributes: PSecurityAttributes; // SD fInheritHandles: BOOL; // опции наследования описателей dwCreationFlags: DWORD;, // флаги создания lpEnvironment: Pointer;, // новый блок переменных окружения lpCurrentDirectory: PChar; // имя текущей папки var lpStartupInfo: TStartupInfo; // стартовая информация var lpProcessInformation: TProcessInformation; // информация процесса ): BOOL; stdcall;Функция может оказаться немного сложна для понимания на первый взгляд. К счастью, большинство параметров в
CreateProcess
можно опустить и стандартный способ создания нового процесса выглядит следующим образом:
var SI: TStartupInfo; PI: TProcessInformation; Exe: String; begin FillChar(SI, SizeOf(SI), 0); SI.cb := SizeOf(SI); Exe := 'cmd.exe'; UniqueString(Exe); if CreateProcess(nil, PChar(Exe), 0, 0, False, 0, 0, 0, SI, PI) then begin // Опционально: ждём завершения процесса WaitForSingleObject(pi.hProcess, INFINITE); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); end;Пример выше просто запускает новый экземпляр cmd.exe. Однако у функции есть множество опций, позволяющих контролировать то, как запускается программа. Некоторые из этих настроек указываются непосредственно в параметрах функции, но большинство из них передаются в записи
TStartupInfo
:
type TStartupInfo = record cb: DWORD; lpReserved: PChar; lpDesktop: PChar; lpTitle: PChar; dwX: DWORD; dwY: DWORD; dwXSize: DWORD; dwYSize: DWORD; dwXCountChars: DWORD; dwYCountChars: DWORD; dwFillAttribute: DWORD; dwFlags: DWORD; wShowWindow: Word; cbReserved2: Word; lpReserved2: PByte; hStdInput: THandle; hStdOutput: THandle; hStdError: THandle; end;Запись
TStartupInfo
также документирована в MSDN, но часть интересной информации не указана - её-то мы сейчас и будем изучать. Вся статья будет в основном вращаться вокруг записи TStartupInfo
и её недокументированных полей: lpReserved
, lpReserved2
и cbReserved2
.Чуть позже я объясню, для чего на самом деле используются эти зарезервированные поля, но сначала давайте посмотрим на поле
dwFlags
и то, что он делает. Тут будет подходящим заглянуть в MSDN:
STARTF_USESHOWWINDOW = $01Эти девять значений (или битовых флагов) могут быть указаны по отдельности или вместе - через OR. Таблица выше довольно скучна, потому что кроме флага
Если это значение не указано, то полеwShowWindow
игнорируется.
STARTF_USESIZE = $02
Если это значение не указано, то поляdwXSize
иdwYSize
игнорируются.
STARTF_USEPOSITION = $04
Если это значение не указано, то поляdwX
иdwY
игнорируются.
STARTF_USECOUNTCHARS = $08
Если это значение не указано, то поляdwXCountChars
иdwYCountChars
игнорируются.
STARTF_USEFILLATTRIBUTE = $10
Если это значение не указано, то полеdwFillAttribute
игнорируется.
STARTF_RUNFULLSCREEN = $20
Указывает, что процесс следует запускать в полноэкранном режиме. В противном случае - в оконном.
STARTF_FORCEONFEEDBACK = $40
Указывает, что курсор сохраняет форму песочных часов до двух секунд после вызоваCreateProcess
.
STARTF_FORCEOFFFEEDBACK = $80
Указывает, что курсор не меняет форму во время запуска процесса. Используется обычный курсор.
STARTF_USESTDHANDLES = $100
Устанавливает описатели стандартного ввода, вывода и канала ошибок на указанные в поляхhStdInput
,hStdOutput
иhStdError
записиTStartupInfo
.
START_USESTDHANDLES
там нет ничего особо интересного. Однако, посмотрев на диапазон значений (от $01 до $100), мы увидим, что используется только 9 флагов (битов) из 32 возможных - что оставляет 23 флага, которые ещё не определены.Для начала нам хватит вводной информации, давайте посмотрим на что-то более интересное.
Определяем, запущены ли мы через ярлык (shortcut)
OK, первый трюк, который я вам покажу - это определение, запущены ли мы через ярлык (т.е. двойным щелчком по .lnk файлу) или напрямую - через Проводник Windows, диалог Run или программно. Это настолько просто, что мне удивительно, почему это не документировано.
Существует недокументированный флаг, который я назову
STARTF_TITLEISLINKNAME
(прим.пер.: сейчас флаг документирован, имя изменено. В оригинале - STARTF_TITLESHORTCUT
). Этот битовый флаг имеет числовое значение $800. Windows устанавливает его, когда приложение запускается через ярлык. Так что любая программа может узнать, как её запустили - анализом своей собственной записи TStartupInfo
:
// Возвращает True, если нас запустили через ярлык; False - в противном случае // Также возвращает имя файла ярлыка (если доступно) function GetShortcutName(out ALinkName: String): Boolean; const STARTF_TITLEISLINKNAME = $800; var SI: TStartupInfo; begin FillChar(SI, SizeOf(SI), 0); SI.cb := SizeOf(SI); GetStartupInfo(SI); if (si.dwFlags and STARTF_TITLEISLINKNAME) <> 0 then begin ALinkName := SI.lpTitle; Result := True; end else Result := False; end;В общем-то, тут всё сводится к проверке флага, но надо пояснить один момент: когда установлен флаг
STARTF_TITLEISLINKNAME
, поле lpTitle
записи TStartupInfo
указывает на строку, содержащую полный путь к файлу ярлыка, который использовался для запуска вашего приложения. Представьте, что на вашем рабочем столе есть ярлык, который запускает Блокнот (notepad.exe). Когда запускается notepad.exe, то его TStartupInfo.lpTitle
содержит такой текст:
C:\Documents and Settings\James\Desktop\Notepad.lnkОчень клёво, да? Ну, я надеюсь, что я подогрел ваш аппетит, так что мы двигаемся к следующей недокументированной возможности!
Указание, на каком мониторе нужно запускать процесс
Следующая недокументированная возможность - очередной флаг записи
TStartupInfo
. Флаг имеет значение $400 и я назвал его STARTF_MONITOR
.Когда в поле
dwFlags
указан флаг STARTF_MONITOR
, поле hStdOutput
записи TStartupInfo
используется для указания описателя монитора, на котором нужно запускать новый процесс. Вы можете получить описатель монитора от любой функции перечисления экранов (прим.пер.: в Delphi - это свойство Handle
у элементов массива Monitors
объекта Screen
из модуля Forms
).Тут есть определённые ограничения, о которых нужно сказать. Вы можете спросить, как это работает, если поле
hStdOutput
используется для описателя канала вывода. Ответ прост - когда указан недокументированный флаг STARTF_MONITOR
, то флаг STARTF_USESTDHANDLES
игнорируется. Это означает, что эти два флага нельзя использовать одновременно, а поля hStdInput
, hStdOutput
и hStdError
трактуются разными способами, в зависимости от установленных флагов.Следующее ограничение очевидно: когда вы запускаете новый процесс с помощью
CreateProcess
, тут нет никакой концепции мониторов, окон и прочих GUI-вещей. Оконная программа (если она оконная) должна сама явно вызвать CreateWindow
для создания своих окон, своего GUI. Вот тут-то и выходит на сцену ограничение флага STARTF_MONITOR
. Когда процесс вызывает CreateWindow
, он может явно указать, где именно создавать окно - указанием числовых значений координат и размеров окна. Это означает, что одна программа не может указать другой, как ей создавать окна, если только сама программа это явно не позволит - указанием специальных параметров при создании окна (прим.пер.: указанием CW_USEDEFAULT
и CW_USEDEFAULT
для координат в случае WinAPI и poDefaultPosOnly
или poDefault
в свойстве в Position случае VCL).Так что только когда сама программа позволяет системе указывать положение окна, используется монитор, указываемый в
CreateProcess
. К примеру, так работает игра Пасьянс. Но этот подход не сработает с Блокнотом - он, похоже, всегда явно указывает координаты окна.Заметьте, что функция
ShellExecuteEx
использует эту возможность CreateProcess
для реализации своих собственных опций монитора (см. флаг SEE_MASK_HMONITOR
).Запуск хранителя экрана (screensaver)
В старых версиях SDK Microsoft Windows был описан флаг, называемый
STARTF_SCREENSAVER
со значением $80000000. Сейчас этот флаг более не документирован. Когда задаётся этот флаг, то процесс запускается с приоритетом NORMAL_PRIORITY
, но как только этот новый процесс делает первый вызов GetMessage
, то его приоритет автоматически опускается до IDLE_PRIORITY
. Эта функциональность может быть немного полезна для хранителей экрана и полностью бесполезна для большинства приложений. Кажется, это поведение было спроектировано для быстрого старта хранителя экрана и последующего "нормального" его выполнения, без заметного влияния на систему.Кажется, что только процессу WinLogon (winlogon.exe) позволено использовать флаг
STARTF_SCREENSAVER
во время активации хранителя экрана, так что этот флаг бесполезен в других сценариях.Устаревшая функциональность Диспетчера программ
Вы помните Диспетчер программ (Program Manager) из Windows 3.1? Он существует даже сегодня (в Windows XP): зайдите в командную строку или диалог "Выполнить" и наберите "progman" - запустится знакомая оболочка Диспетчера программ. Даже во времена Windows 3.1 (редакций home и NT) у
CreateProcess
существовала недокументированное поведение. Я собираюсь поделиться с вами этой информацией - даже хотя сегодня она практически бесполезна, но узнать о ней будет интересно.Если вы посмотрите на определение
TStartupInfo
, то вы увидите поле lpReserved
. Это поле вообще-то постоянно используется, но только Диспетчером программ, когда он запускает программы.Это поле указывает на строковый буфер в таком формате:
dde.#,hotkey.#,ntvdm.#Каждый раз, когда программа запускается Диспетчером программ, она вызывает
GetStartupInfo
, чтобы узнать о параметрах запуска. И в этом случае поле lpReserved
будет содержать строку с тремя полями, разделёнными запятыми, с #, указывающими hex-значения:
- Часть "dde." указывает идентификатор DDE, который дочерний процесс может использовать для общения с Диспетчером программ. Когда дочерний процесс отправляет progman-у сообщение
WM_DDE_REQUEST
с этим ИД, то progman отвечает сообщениемWM_DDE_DATA
. Это сообщение содержит, среди всего прочего, описание progman, его индекс иконки и рабочую папку для дочернего процесса.
Более подробно об этом механизме можно почитать в статье Knowledge Base номер 105446. - Часть "hotkey." указывает на комбинацию hot-key Диспетчера программ, которая была использована для запуска программы. Это 16-битное hex число, в младшем байте которого находится ASCII код hot-key, а в старшем - комбинация значений
HOTKEYF_xxx
. Я понятия не имею, зачем дочернему процессу могла понадобится такая информация. - Часть "ntvdm." используется для информирования процесса NTVDM о свойствах программы в Диспетчере программ. Это простое hex-поле, которое представляет собой комбинацию битовых флагов. К примеру, значение 1 указывает, что задан текущий каталог, значение 2 - что у программы есть hot-key, значение 4 - что указан заголовок программы.
lpReserved
. Сегодня это бесполезная возможность - даже хотя вы всё ещё можете использовать поле lpReserved
в вызове CreateProcess
, ни одна (современная) программа никогда не будет его читать. Вы можете посмотреть на действие этого поля, запуская ваше приложение из Диспетчера программ.Конечно же, если у вас есть два ваших приложения, разработанных специально для взаимодействия друг с другом, и вы хотите передавать второму приложению данные (по какой-то причине) иным способом, нежели через командную строку, то поле
lpReserved
будет вам полезным.Устаревшая функциональность
ShellExecuteEx
Функция
ShellExecuteEx
появилась аж в Windows NT 3.1 (но не в "обычной" 3.1). Она принимает единственный параметр - указатель на запись TShellExecuteInfo
. Вы можете посмотреть определение этой структуры в MSDN (довольно скучно!). Но в ней есть несколько полей, которые тоже не документированы.Во-первых, это поле
hMonitor
. ShellExecuteEx
использует это поле для контроля монитора, на котором следует появиться запускаемому процессу. Для реализации этой возможности ShellExecuteEx
вызывает CreateProcess
, указывая обсуждаемый выше флаг STARTF_MONITOR
.Следующее интересное поле - это поле
hIcon
. В нём указывается описатель иконки открываемого файла, если поле флагов содержит флаг SEE_MASK_ICON
со значением $10. Мне не удалось найти информацию по применению этой возможности - всё, что я могу сказать, так это то, что эту возможность использовали консольные приложения Windows NT 3.1/3.5 (новые программы всегда игнорируют иконку). Для реализации этой возможности ShellExecuteEx
использует недокументированный флаг в CreateProcess
, который я назову STARTF_ICON
. Странно, но этот флаг численно равен флагу STARTF_MONITOR
: $400. Видимо, подразумевается, что иконка передаётся консольным программам, а монитор - визуальным.Последнее интересное поле - это
dwHotKey
(используемого только при наличии флага SEE_MASK_HOTKEY
). Оно предполагается для назначения hot-key дочернему процессу, так что вы можете активировать приложение в любое время нажатием этой комбинации. Однако мне не удалось заставить работать эту возможность. Может быть, она была удалена из системы. И снова, ShellExecuteEx
использует недокументированный флаг CreateProcess
- это флаг STARTF_USEHOTKEY
со значением $200. Когда указывается этот флаг, поле hStdInput
должно содержать значение hot-key вместо канала ввода (см. WM_SETHOTKEY).И иконка и hot-key являются странными возможностями
CreateProcess
, во-первых потому, что эта функциональность, кажется дублируется параметром lpReserved
и, во-вторых, она убрана из современных версий Windows. Если кто-то владеет другой информацией по этой теме - я буду счастлив её услышать!Передача произвольных данных дочернему процессу
Последний недокументированный трюк несколько отличается от упомянутых выше, так что я решил оставить его под конец. Запись
TStartupInfo
содержит два поля lpReserved2
и cbReserved2
. Эти два поля предоставляют возможность передачи произвольных данных от одного процесса к запускаемому без необходимости вызова VirtualAllocEx
/ WriteProcessMemory
(прим.пер.: помните, что это хак; в 99% случаев намного предпочтительнее передавать данные через командную строку или анонимную память). Поле cbReserved2
является 16-битным целым и указывает размер буфера, на который указывает lpReserved2
. Это означает, что lpReserved2
может иметь размер до 65535 байт.Пример ниже иллюстрирует возможность передачи произвольного буфера от одного процесса другому. Когда процесс-B запускается процессом-A, он показывает MessageBox, говорящий "Hello from Process A!":
program ProcessA; uses Windows, SysUtils; var SI: TStartupInfo; PI: TProcessInformation; Buf: array[0..4095] of Char; begin FillChar(Buf, SizeOf(Buf), 0); Buf := 'Hello from Process A!'; FillChar(SI, SizeOf(SI), 0); SI.cb := SizeOf(SI); // Передача данных SI.lpReserved2 := @Buf; SI.cbReserved2 := SizeOf(Buf); if CreateProcess(nil, 'ProcessB.exe', 0, 0, 0, 0, 0, 0, SI, PI) then begin CloseHandle(PI.hProcess); CloseHandle(PI.hThread); end; end.
program ProcessB; uses Windows, SysUtils; var SI: TStartupInfo; begin FillChar(SI, SizeOf(SI), 0); SI.cb := SizeOf(SI); GetStartupInfo(SI); // Покажем, что послал нам процесс A MessageBox(0, PChar(SI.lpReserved2), 'Process B', MB_OK); end.Пока всё хорошо - мы узнали, про приятный способ передачи произвольных параметров между приложениями без необходимости использовать командную строку. Но тут есть проблема. В примере выше процесс B обязан быть собран абсолютно без поддержки C run-time (прим.пер.: как несложно сообразить, это применимо лишь к программам на MS VS, но не программам Delphi, если, конечно же, вы зачем-то вручную будете её включать в ваши программы). Причина этого довольно сложна, но я попытаюсь её объяснить.
Microsoft C run-time (включая Visual Studio.NET) использует эту возможность
lpReserved2
для реализации C функций exec
, system
и spawn
. Когда этими подпрограммами создаётся новый процесс, C run-time нужно передать дочернему процессу копии открытых файловых описателей (открытых через fopen
/open
, а не через CreateFile
).lpReserved2
используется как механизм для передачи этих файловых описателей между программами, использующими MSVC. До вызова CreateProcess
подготавливается буфер для lpReserved2
, который имеет такой формат (псевдо-код):
type TArgs = record count: DWORD; flags: array[1..count] of Byte; handles: array[1..count] of THandle; end;Первое поле в буфере
lpReserved2
является 32-битным целым числом передаваемых описателей. Сразу за ним идёт массив байт с этим числом элементов - флаги описателей. Эти флаги представляют собой файловый атрибуты, которые были использованы, когда файлы были открыты - т.е. вещи вроде "read-only", "write-append", "text-mode", "binary-mode" и т.п. А за этим массивом следует массив собственно описателей.После этой структуры могут следовать любые данные - до 65536 байт. Поле
cbReserved2
должно содержать суммарный размер передаваемых данных в байтах. Вот алгоритм, по которому строится буфер:
- Перечисляются все открытые "run-time" описатели файлов.
- Описатель Win32 каждого такого файла отмечается как "наследуемый" (inheritable).
- Число найденных описателей записывается в первое поле буфера для
lpReserved2
. - Атрибуты каждого описателя записываются друг за другом в буфер.
- В буфер записываются файловые описатели.
- Наконец, вызывается
CreateProcess
с установленным вTrue
полемbInheritHandles
.
main()
(прим.пер.: аналог главному begin
/end
в .dpr файле Delphi) инициализируется поддержка I/O. Как часть этого, вызывается GetStartupInfo
и производится проверка, не указывает ли lpReserved2
на буфер. Если да, то C run-time, предполагая описанную выше структуру буфера, извлекает из неё описатели файлов - так что эти описатели становятся доступными новому процессу.
- Вызывается
GetStartupInfo
и проверяется, чтоlpReserved2
указывает на буфер. - Извлекается первое 32-битное число - это будет число переданных описателей (размерность массивов).
- Текущее состояние ввода-вывода процесса инициализируется полученным числом описателей открытых файлов.
- Цикл по флагам восстанавливает состояние описателей.
- Цикл по описателям восстанавливает таблицу открытых файлов.
lpReserved2
- не удивительно, что это будут делать 90% C/C++ программ, написанных под Windows. Поле lpReserved2
также может использоваться и другими компиляторами для похожих целей.Если вы хотите использовать
lpReserved2
в ваших собственных программах, то вам нужно быть очень осторожными и точно убедиться, что либо вы используете описанный выше формат, либо не используете C run-time. Иначе дочерний процесс вылетит (или станет нестабилен) - потому что он будет ожидать буфер lpReserved2
в определённом формате.Обойти эту проблему не сложно. Просто установите первые 4 байта буфера в ноль - указывая, что массив описателей имеет нулевую длину. Любая C run-time-подобная логика будет опущена. А вы можете расположить свои реальные данные после этого нулевого маркера.
Примечание: очевидно, этот метод не работает под 64-битной Windows Vista.
Итоги по
CreateProcess
Это была скорее полноценная статья, чем мануал. Надеюсь, вы нашли что-то полезное или узнали о
CreateProcess
кое-что новое, что вы не знали раньше.Я не прочь услышать любые ваши комментарии по этой статье. Если вы заметили мои ошибки или неточности - поправьте меня. И если у вас есть больше информации по этой теме - поделитесь ею!
[Добавлено 05.0.3.2014]: больше материала по CreateProcess.
Александр, Здраствуйте.
ОтветитьУдалитьПодскажите.
Как определить, запущен ли чужой процесс через ярлык (shortcut), и узнать путь к файлу ярлыка, который использовался для запуска чужого процесса?
или Как узнать данные TStartupInfo.lpTitle чужого процесса?
и возможно ли такое?
Документированного способа нет. Если процесс ваш - налаживайте IPC. Если не ваш - остаются хаки. Внедряйтесь в процесс, вызывайте GetStartupInfo, передавайте куда надо.
УдалитьАльтернативно, посмотрите машинную реализацию GetStartupInfo - возможно, вам удастся обойтись GetThreadContext + ReadProcessMemory. Этот вариант, само собой, будет иметь высокие шансы поломаться в будущих версиях Windows.
Спасибо за исчерпывающий ответ.
ОтветитьУдалитьДобрый день!
ОтветитьУдалитьПередо мной следующая задача:
Из программы №1 (написана на делфи) запускается другая программа (№2, написана на c#), причем программа №1 остается доступной, т.е. пользователь может свернуть программу №2 и продолжить работать в №1. (нет WaitForInputIdle(hProcess, INFINITE);)
Подскажите, пожалуйста, можно ли, чтобы программа №2 закрывалась при закрытии программы №1? В данный момент закрытие происходит с помощью команды TerminateProcess, которая запускается по событию закрытия приложения №1 (программа хранит hProcess открытого приложения №2), но это убийство процесса, что не устраивает. Потому что в приложении №2 могут происходить какие-либо изменения, и если закрыть приложение №2 обычным способом, сработает событие, выйдет диалоговое окно, предупреждающее, что "программа закрывается, а у вас есть несохраненные данные". Убийство же процесса не даст сработать событию на закрытие приложения №2.
Можно ли сделать это автоматически, настроив правильно открытие приложения №2? Или же нужно каким-то образом получить Hadle окна приложения №2 и закрыть его с помощью SendMessage(hnd,WM_CLOSE,0,0);
Подскажите, в какую сторону копать.
Спасибо!
Никто не вызываемому процессу получить PID своего родителя, открыть этот процесс и ждать его завершения.
УдалитьДобрый день, возможно ли получить данные которые возвращает запускаемая программа?
ОтветитьУдалитьМожно. Обмен файлами, реестр, IPC (пайпы, общая память), консольный ввод-вывод.
Удалить// собираю данные о запущенных процессах одного класса
ОтветитьУдалитьSTART EnumWindows
pID potokID hProc hWnd___ winTitle Class
3264 3040 288 12716312 L Afx:00400000:0
752 3420 288 5511126 L Afx:00400000:0
328 4056 288 6687750 L Afx:00400000:0
1188 2180 288 1706418 S Afx:00400000:0
4292 5468 288 660214 A Afx:00400000:0
END EnumWindows
Запускаю выше приведенным кодом еще одно такое ЖЕ приложение
hThread:: 288
hProcess:: 284
// собственно задача понять какой процесс я только что создал
// и вот полученные числа как бы ни к чему не привязать
// что они вообще значат? И ведь как-то WaitForInputIdle и CloseHandle с ними работают ((( ?
START EnumWindows // сразу после запуска +1го приложения
сканирую все процессы:::
pID_ potokID hProc hWnd___ name____
3264 3040 284 12716312 L2Walker
3136 7056 284 11016064 L2Walker
,,,,
752 3420 284 5511126 L2Walker
END EnumWindows
/// ОПА !!! номер потока у всех стал 284 как hProcess у только что запущенной программы
/// запускаю еще один экземпляр
hThread:: 284
hProcess:: 288
/// номера трида и процесса поменялись метами
hThread:: 288
hProcess:: 284
// еще раз
ну допустим я в коде сбора инфы о всех копиях напортачил
но код запуска я практически не трогал
FillChar(StartUpInfo, SizeOf(TStartUpInfo), 0);
Удалитьwith StartUpInfo do
begin
cb := SizeOf(TStartUpInfo);
dwFlags := STARTF_USESHOWWINDOW or STARTF_FORCEONFEEDBACK;
wShowWindow := SW_SHOWNORMAL;
end;
Rlst := CreateProcess(nil, PChar('c:\L8\L82\kor.exe'), nil, nil, false, NORMAL_PRIORITY_CLASS, nil, PChar('c:\L8\L82\'), StartUpInfo, ProcessInfo);
if Rlst then
with ProcessInfo do begin
sleep(1000);
WaitForInputIdle(hProcess, INFINITE); // ждем завершения инициализации
Memo1.Text:=Memo1.Text+sLineBreak+'hThread:: '+inttostr(hThread);
Memo1.Text:=Memo1.Text+sLineBreak+'hProcess:: '+inttostr(hProcess);
CloseHandle(hThread); // закрываем дескриптор процесса
CloseHandle(hProcess); // закрываем дескриптор потока
end
else Error := GetLastError;
Кстати!: полный путь к запускаемому файлу можно ставить и в первую и во вторую переменную
Удалитьразницы не обнаружил.
А вот в третью с конца позицию - пришлось поставить рабочую папку проекта. Иначе оно свои конфиги не видит.
Колонка hProc - это что такое? Это, типа, OpenProcess делаете?
Удалить