Конференция "WinAPI" » Консольное приложение и сервис [WinXP]
 
  • Андрей2711 (27.11.12 19:12) [0]
    Добрый вечер.
    Подскажите, пож-та, как сделать консольное приложение, чтобы его можно было запустить из cmd и установить как сервис Windows. Например, с oracle.exe можно так делать. И видел еще примеры таких программ. Спасибо
  • Rouse_ © (27.11.12 19:19) [1]
    Через ключи командной строки, где определяйся как работать будешь, либо как консоль, либо как сервис через вызов StartServiceCtrlDispatcher().
  • Андрей2711 (27.11.12 20:48) [2]
    Т.е. пишу консольное приложение. При запуске смотрю список параметров переданных в прогу? Например, если параметр -service то делать, что вы написали выше. Так?
    Можете поподробнее описать. Спасибо.
  • Rouse_ © (27.11.12 20:51) [3]
    Вот тебе заготовочка: http://rouse.drkb.ru/files/twoservicedemo.zip
    Осталось только дописать код чтоб она запускалась как консолька...
  • Eraser © (27.11.12 20:53) [4]
    FindCmdLineSwitch('service', ['-', '\', '/'], True)

  • Андрей2711 (27.11.12 21:15) [5]
    Из вашего примера. Мне нужен только один сервис, так что все, что связано со вторым можно удалить.
    Еще вопрос: приложение в режиме сервиса должно будет слушать порт TCP. Что-то нужно будет писать в FirstMainProc?
  • han_malign (28.11.12 08:46) [6]

    > Через ключи командной строки, где определяйся как работать
    > будешь, либо как консоль, либо как сервис

    - помимо ключей можно проверить, что приложение запущено не под SCM - причем для консольного все гораздо проще - например GetConsoleWindow() <> 0, да и StartServiceCtrlDispatcher практически мгновенно ERROR_FAILED_SERVICE_CONTROLLER_CONNECT возвращает...
    А в запущенных detached - больше минуты SCM по сусекам ищет, приходится по жетону SID-ы проверять(http://win32.mvps.org/security/is_svc.txt)...
  • Rouse_ © (28.11.12 10:39) [7]

    > так что все, что связано со вторым можно удалить.

    угу


    > Что-то нужно будет писать в FirstMainProc?

    ну да, это считай тот-же begin..end который у консоли, в который ты тоже будеш писать код...
  • Андрей2711 (28.11.12 14:40) [8]

    > - помимо ключей можно проверить, что приложение запущено
    > не под SCM - причем для консольного все гораздо проще -
    > например GetConsoleWindow() <> 0, да и StartServiceCtrlDispatcher
    > практически мгновенно ERROR_FAILED_SERVICE_CONTROLLER_CONNECT
    > возвращает...

    Это посмотрю. Спс.


    > Что-то нужно будет писать в FirstMainProc?
    >
    > ну да, это считай тот-же begin..end который у консоли, в
    > который ты тоже будеш писать код...


    Вы немного не поняли, или я неправильно выразился.

    Для прослушки порта буду использовать Indy 10 (idTCPServer). При запуске сервиса начинаю слушать порт, при остановке перестаю слушать порт.
    Получается, что в коде FirstMainProc надо только извещение о том, что сервис работает, т.к. в Indy есть событие OnExecute.

    Верно?
  • Rouse_ © (28.11.12 14:43) [9]
    Эмм, грубо, как только произойдет выход из функции FirstMainProc работа сервиса завершится. Как ты будешь делать - решать тебе.
    Я обычно делаю отдельный модуль, в который выношу саму логику работы, а в основном цикле создаю пайп для управления сервисом и вишу на нем ожидая управляющих команд.
  • Андрей2711 (28.11.12 21:11) [10]
    Стал потихоньку переделывать Ваш пример.
    В самом начале добавил:
    {$APPTYPE CONSOLE} и все ShowMsg заменил на WriteLn.
    Главную процедуру изменил на:
    procedure MainProc(ArgCount: DWORD; var Args: array of PChar); stdcall;
    var
      Context: DWORD;
    begin
     Context := Context;
     StatusHandle := RegisterServiceCtrlHandlerEx(Name, @ServicesCtrlHandler, @Context);
     if (StatusHandle <> 0) and Initialize and NotifyIsRunning then
    //    while Status.dwCurrentState <> SERVICE_STOP do
         try
            // Собственно работа сервиса
    //         Sleep(10);
         except
              // Обработка ошибок сервиса
         end;
     ExitThread(0);
    end;



    //    while Status.dwCurrentState <> SERVICE_STOP do


    Дает 99% загрузки проца.

    Служба устанавливается и запускается (процесс в диспетчере задач видно).
    Проблема с остановкой. Нажимаю остановить процесс убивается и долго-долго винда пытается его остановить, после чего сообщение:

    Не удалось остановить службу TSvcTest на Локальный компьютер.
    Ошибка 1053: Служба не ответила на запрос своевременно.

    Что я сделал не так?
  • Rouse_ © (29.11.12 00:10) [11]
    Странно, загрузки быть не должно, а по поводу остановки, попробуй SERVICE_STOP заменить на SERVICE_STOP_PENDING в данном коде.
    ЗЫ: вообще по хорошему вопрос у тебя интересный, надо демку написать как время освободится...

    Завтра я болею - послезавтра свяжись со мной по аське в профиле, если не получится реализовать как надо. Попробуем разрулить ситуацию...
  • DVM © (29.11.12 14:32) [12]
    Я использую примерно такой код для создания программ, которые я называю гибридными, т.е. которые могут запускаться как сервис или как консоль:

    http://dvmuratov.narod.ru/uServices.pas

    Консоль создается классом

    http://dvmuratov.narod.ru/uConsole.pas

    Класс TSCManager просто управляет сервисами

    Чаще всего я консоль использую только для вывода логов, поэтому ее функционал по вводу данных минимальный, она просто создается и в ней надо нажать CTRL+Q для выхода в случае по-умолчанию.

    Как использовать THybridApplication:


    type

     TMyService = class (THybridApplication)
     private
       FServiceMonitor: TServiceMonitorThread;
     public
       procedure Init; override;
       procedure Done; override;
       function GetServiceController: TServiceController; override;
       function GetServiceMainProc: TServiceMainProc; override;
     end;

    var
     MyApp: TMyService;

    implementation

    //------------------------------------------------------------------------------

    procedure ServiceController(CtrlCode: DWord); stdcall;
    begin
     MyApp.Controller(CtrlCode);
    end;

    //------------------------------------------------------------------------------

    procedure ServiceMainProc(ArgCount: DWORD; var Args: array of PChar); stdcall;
    begin
     MyApp.MainProc(ArgCount, Args);
    end;

    //------------------------------------------------------------------------------

    function TMyService.GetServiceController: TServiceController;
    begin
     Result := ServiceController;
    end;

    //------------------------------------------------------------------------------

    function TMyService.GetServiceMainProc: TServiceMainProc;
    begin
     Result := ServiceMainProc;
    end;

    //------------------------------------------------------------------------------

    procedure TMyService.Init;
    begin
     StartLog;
     try
       // тут стартуем все потоки выполняющие полезную работу
     except
       on e: Exception do
         Logger.Error(e.ClassName + ' : ' + e.Message);
     end;
    end;

    //------------------------------------------------------------------------------

    procedure TMyService.Done;
    begin
     try
       // тут останавливаем все потоки выполняющие полезную работу
     except
       on e: Exception do
         Logger.Error(e.ClassName + ' : ' + e.Message);
     end;
     StopLog;
    end;

  • DVM © (29.11.12 14:34) [13]
    Ну а в файле проекта так:


     MyApp := TMyService.Create(rsServiceName,
                                                rsServiceDisplayName,
                                                rsServiceDesc);
     try
       MyApp .Run;
     finally
       MyApp .Free;
     end;

  • Андрей2711 (29.11.12 20:54) [14]
    Попробовал следующее:
    В RAD 2009 создал новый проект Service Application. И сделал код такой:

    program myprog;

    {$APPTYPE CONSOLE}

    uses
     SysUtils,
     SvcMgr,
     SvcUnit in 'SvcUnit.pas' {Service1: TService},
     Console in 'Console.pas';

    {$R *.RES}

    begin
     if FindCmdLineSwitch('service', ['-'], True) then
       begin
         if not Application.DelayInitialize or Application.Installing then
           Application.Initialize;
         Application.CreateForm(TListener, Listener);
         Application.Run;
       end
     else
         begin
           Work('MYPROG');
         end;
    end.



    unit Console;

    interface

    uses
       SysUtils;

    procedure Work(const Invit: String);

    implementation

    procedure Work(const Invit: String);
    var
      Terminated: Boolean;
      Input: String;
    begin
     Terminated := False;
     while not Terminated do
       begin
         Write(Invit + '> ');
         ReadLn(Input);
         if ANSILowerCase(Input) = 'exit' then
           Terminated := True;
       end;
    end;

    end.



    Вроде работает и так и так.
  • Андрей2711 (29.11.12 21:17) [15]
    Работает на Win XP и 2003. На Win 7 не устанавливается как сервис.
  • DVM © (29.11.12 22:31) [16]

    > Андрей2711   (29.11.12 21:17) [15]


    > На Win 7 не устанавливается как сервис.

    а если {$APPTYPE CONSOLE} убрать?
  • Андрей2711 (30.11.12 14:04) [17]
    Мне на Win 7 не очень то и надо, это я так просто для инфы скинул. Мне кажется, что если убрать {APPTYPE CONSOLE}, то работать как консольное приложение не будет. Проверю вечером.
  • DVM © (30.11.12 15:07) [18]

    > Андрей2711   (30.11.12 14:04) [17]


    > Мне кажется, что если убрать {APPTYPE CONSOLE}, то работать
    > как консольное приложение не будет.

    Конечно не будет. Консоль надо вручную создавать, смотри, что я выше писал.
  • anatoly podgoretsky © (30.11.12 15:31) [19]
    program myprog;

    uses
     Windows;

    begin
      WriteLn('Test');
      ReadLn;
    end.

  • DVM © (30.11.12 21:26) [20]

    > anatoly podgoretsky ©   (30.11.12 15:31) [19]

    EInOutError будет, даже проверять не надо.
  • Андрей2711 (30.11.12 21:39) [21]
    Так выдает ошибку на WriteLn
    project myprog.exe raised exception class EInOutError with message 'I/O error 105'.

  • DVM © (30.11.12 21:48) [22]

    > Андрей2711   (30.11.12 21:39) [21]

    Я ж тебе дал практически готовый рабочий вариант, простых путей не будет.
  • Андрей2711 (01.12.12 20:13) [23]

    > Я ж тебе дал практически готовый рабочий вариант, простых
    > путей не будет.


    Спасибо. Но ввиду того, что работает на Xp и 2003, то меня устроит вариант, который я написал выше. Ибо на Win 7 Vista и пр. не пофигу :)

    Просто так получается по проще. :)
  • Андрей2711 (02.12.12 22:43) [24]
    Кстати, создал пустой сервис и попытался установить на Win7.
    Получаю: Exception EOSError in module Project1.exe at 000112A5. System Error. Code: 5. Отказано в доступе.
  • DVM © (02.12.12 23:39) [25]

    > Андрей2711   (02.12.12 22:43) [24]


    > System Error. Code: 5. Отказано в доступе.

    А подумать? Запускай от имени администратора консоль и оттуда запускай установку с ключом /install (или какой там у тебя).
  • Андрей2711 (03.12.12 09:39) [26]
    Вот блин. Спасибо.
 
Конференция "WinAPI" » Консольное приложение и сервис [WinXP]
Есть новые Нет новых   [134430   +2][b:0][p:0.004]