Прим.пер.: эта статья была написана относительно давно. Некоторые возможности, упомянутые в ней, с тех пор стали документированы. В этом случае я заменил придуманные автором имена на те, которые сейчас используются в документации 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 делаете?
Удалить