Вопрос о запуске графического приложения из службы Windows является неоднозначным с точки зрения дизайна системы, так как служба по определению не должна взаимодействовать с пользовательским интерфейсом. Однако, в некоторых случаях, например, при запуске утилит для обслуживания системы, это может быть необходимым требованием. В статье рассмотрим типичную проблему, с которой сталкиваются разработчики при использовании функции CreateProcessAsUser и предложим решение, основанное на реальном опыте и лучших практиках.
Проблема
Разработчик столкнулся с ошибкой доступа к памяти при попытке запустить приложение notepad.exe из службы Windows. Используя код из различных источников, он собрал процедуру TMyService.RunApp, которая должна была выполнить задачу. В результате выполнения CreateProcessAsUser возникала ошибка, несмотря на различные настройки, которые он пробовал. Ошибка доступа указывала на адрес 766BA61D в модуле KERNELBASE.dll, с сообщением о записи по адресу 005E6312.
Анализ проблемы
Адрес 00000000 обычно указывает на попытку доступа к неинициализированному участку памяти (nil), в то время как доступ к памяти с другими адресами может означать, что попытка обращения к памяти происходит, но данные в этом блоке памяти не соответствуют ожидаемому типу. Также возможно, что адрес, ранее используемый корректно, был освобожден и переиспользован для других данных. В данном случае, ошибка указывала на конкретный адрес, что давало разработчику подсказку о том, что искать.
Подтвержденное решение
После дополнительного анализа было выяснено, что проблема заключалась в том, что третий параметр функции CreateProcessAsUser должен быть указателем на изменяемую строку. Это требование документации MSDN не было учтено разработчиком. Так как литерал строки хранится в памяти только для чтения, использование константной строки может привести к ошибке доступа. Для решения проблемы нужно изменить тип переменной CmdLine на изменяемый, используя функцию UniqueString(CmdLine).
Пример кода
procedure TMyService.RunApp;
var
SessionID: DWORD;
UserToken: THandle;
CmdLine: string;
si: _STARTUPINFOW;
pi: _PROCESS_INFORMATION;
begin
SessionId:= WtsGetActiveConsoleSessionID;
if SessionID = $FFFFFFFF then Exit;
if WTSQueryUserToken(SessionID, UserToken) then begin
CmdLine:= 'notepad.exe';
UniqueString(CmdLine); // делаем строку изменяемой
ZeroMemory(@si, SizeOf(si));
si.cb := SizeOf(si);
SI.lpDesktop := PChar('winsta0\Default');
SI.dwFlags := STARTF_USESHOWWINDOW;
SI.wShowWindow := SW_SHOWNORMAL;
ZeroMemory(@pi, SizeOf(pi));
if not CreateProcessAsUser(UserToken, nil, PChar(CmdLine), nil, nil, False,
0, nil, nil, si, pi) then
// обработка ошибки
CloseHandle(UserToken);
end else begin
// обработка ошибки получения токена пользователя
end;
end;
Заключение
При работе с функциями, взаимодействующими с системными ресурсами, важно внимательно изучать документацию и следовать рекомендациям. В данном случае, внимание к деталям и понимание того, как работают строки в памяти, помогли устранить проблему и запустить приложение, как было задумано.
Разработчик столкнулся с ошибкой доступа к памяти при попытке запустить графическое приложение из службы Windows из-за неверного использования строки в функции `CreateProcessAsUser`.
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.