Diseño de servicio de Windows de Delphi

Diseño de servicio de Windows de Delphi

Nunca he creado un servicio de Windows, pero he estado leyendo todo lo que he encontrado. Todos los artículos o ejemplos que he encontrado son muy básicos en la implementación y limitados en su scope. No he visto nada que vaya más allá de esta o aquella dirección de escenarios específicos. Entonces, tengo toda la teoría que probablemente voy a encontrar, y ahora estoy listo para sumergirme en este proyecto. Me gusta diseñar mis ideas y obtener retroalimentación sobre lo que piensa la gente. Describiré lo que necesito de la aplicación y cómo pretendo construirlo. Agradecería los comentarios de cualquier persona que tenga experiencia en la construcción de servicios de Windows y cualquier consejo que quisieran compartir.

[ESCENARIO] Ahora mismo tengo una aplicación (la llamaré UPDATEAPPLICATION) que proporciona actualizaciones para todas nuestras otras aplicaciones. Para ejecutar cualquiera de nuestras aplicaciones, primero debe ejecutar este progtwig UPDATEAPPLICATION y pasarle un parámetro de la aplicación deseada. UPDATEAPPLICATION llama a un servicio web que devuelve información XML sobre si la aplicación deseada tiene alguna actualización.

Si hay una actualización, UPDATEAPPLICATION descarga la actualización en formato EXE o ZIP y reemplaza los archivos apropiados para actualizar la aplicación seleccionada. Después, UPDATEAPPLICATION hace un ShellExecute para iniciar la aplicación deseada y luego se cierra la aplicación UPDATEAPPLICATION.

Es un proceso bastante básico que ha funcionado bien a lo largo de los años. El progtwig UPDATEAPPLICATION es una aplicación Delphi, nuestras otras aplicaciones son mixtas: Delphi, VB6, MS Access, .NET.

[EL PROBLEMA] Con el cambio a Vista y Windows 7, la seguridad ha cambiado drásticamente. Debido a la naturaleza de UPDATEAPPLICATION UAC no permitirá que la aplicación se ejecute sin el acceso de administrador o el UAC completamente desactivado. Estamos en el proceso de actualizar muchas de nuestras aplicaciones a .NET y durante este proceso me gustaría que las aplicaciones y UPDATEAPPLICATION sean compatibles con UAC. De lo que he investigado, la única forma de hacerlo es creando UPDATEAPPLICATION como servicio de Windows. Entonces, esencialmente, necesito duplicar la funcionalidad de UPDATEAPPLICATION en una architecture de servicio de Windows.

[MI DISEÑO] Estoy usando DelphiXE2. Mi diseño constará de 3 partes para formar una única solución: un servicio de Windows, una pequeña aplicación de bandeja para interactuar con el servicio de Windows y mis aplicaciones rediseñadas que enviarán mensajes al servicio de Windows.

  1. Mi Servicio de Windows (que llamaré UPDATESERVICE) se ejecutará como un Servicio de Windows y creará un servidor TCP para escuchar las solicitudes.
  2. La aplicación de la bandeja (que llamaré TRAYAPP) usará el Cliente TCP para configurar / administrar el ACTUALIZAR SERVICIO.
  3. Mi APLICACIÓN DE USUARIO, cuando se inicia, enviará un mensaje TCP a UPDATESERVICE que dice “ESTA APLICACIÓN” ha comenzado.

[UPDATESERVICE] Escuchará los mensajes. Si recibe un mensaje de que una APLICACIÓN DE USUARIO ha comenzado, llamará al servicio web para ver si hay actualizaciones. Si los hay, se notificará al usuario que cierre la aplicación y permita que el ACTUALIZACIÓN actualice la aplicación. El UPDATESERVICE descargará los archivos apropiados y actualizará la aplicación.

Ahora que he explicado los conceptos básicos de lo que trato de hacer, puedo formular mis preguntas específicas que necesito respuestas. Todos estos tienen que ver con la forma en que debería construir mi servicio de Windows. También planeo usar OmniThread para mi gestión de hilos.

Cuando se inicia mi servicio, necesito crear el servidor TCP.

  1. ¿Debería crearse el servicio TCP en su propio hilo?
  2. Si el servicio TCP es su propio hilo, ¿cómo puedo mantener el hilo vivo? De lo contrario, puedo iniciar el servicio TCP, pero no estoy seguro de qué código usaría dentro de la unidad de servicio TCP para mantener el hilo en funcionamiento.
  3. ¿Qué evento de servicios de Windows debería crear el servicio TCP? OnExecute? OnStart? ¿OnCreate? Después de todo lo que he leído, no está claro qué evento debería usarse.
  4. Cuando el servicio TCP recibe un mensaje para hacer algo, ¿debería ejecutarse el trabajo dentro del hilo de servicio TCP o un nuevo hilo generado en el ACTUALIZACIÓN DE SERVICIO principal? Por ejemplo:
    • si el servicio TCP recibe un mensaje para verificar si hay una actualización utilizando HTTP si el hilo del servicio TCP engendra un nuevo hilo para hacer este trabajo
    • O bien, si el hilo del servicio TCP envía un mensaje al UPDATESERVICE para engendrar un nuevo hilo para hacer este trabajo
    • ¿Incluso importa?
  5. ¿Es posible iniciar / detener / registrar / cancelar el registro de un servicio de Windows en el código Delphi?

Esta es todas mis preguntas. Probablemente no haya una respuesta correcta / incorrecta para esto, sino simplemente una preferencia basada en la experiencia. Si ha creado servicios con Delphi, probablemente tenga alguna información que me resulte útil. Si tienes un proyecto que es más robusto que un básico “inicia un servicio y duerme” y estás dispuesto a compartirlo, incluso si no ejecuto o solo código de psuedo, estoy seguro de que sería invaluable. Gracias por leer mi larga pregunta. Si puede pensar en una mejor manera de hacerlo, por favor comparta sus ideas. Añadiré que varias de nuestras aplicaciones pueden ser descargadas y ejecutadas por el público en general, por lo que no tengo control completo sobre los entornos esperados. Cualquier consejo / comentario / ayuda sería apreciado.

respuestas rápidas:

1 y 3) Sí. Como regla general, no implemente el evento de servicio OnExecute. Genere su propio hilo del evento del servicio OnStart. El hilo se puede terminar cuando recibe el evento del servicio OnStop.

2) mantienes vivo tu hilo así (ejecuta el método):

while not Terminated do begin // do something end; 

4) normalmente, cada conexión de cliente vivirá en su propio hilo. (es decir, el servidor TCP genera un nuevo hilo para cada cliente). Use una stack conocida como Indy o ICS. Con respecto a la actualización de HTTP, puede hacer esto en el hilo de conexión del cliente generado.

5) sí, tenga en cuenta que necesita derechos elevados para hacer esto.

He realizado bastantes servicios en mi carrera y siempre utilizo el mismo esqueleto para la aplicación de servicio hasta ahora:

 unit u_svc_main; interface uses // Own units u_globals, u_eventlog, u_MyThread, // Third party units // Delphi units Windows, Messages, Registry, SysUtils, Classes, SvcMgr; type TMyService = class(TService) procedure ServiceCreate(Sender: TObject); procedure ServiceAfterUninstall(Sender: TService); procedure ServiceAfterInstall(Sender: TService); procedure ServiceShutdown(Sender: TService); procedure ServiceStop(Sender: TService; var Stopped: Boolean); procedure ServiceStart(Sender: TService; var Started: Boolean); private { Private declarations } MyThread : TMyThread; public { Public declarations } function GetServiceController: TServiceController; override; end; var MyService : TMyService; implementation {$R *.DFM} procedure ServiceController(CtrlCode: DWord); stdcall; begin MyService.Controller(CtrlCode); end; function TMyService.GetServiceController: TServiceController; begin Result := ServiceController; end; procedure TMyService.ServiceCreate(Sender: TObject); begin DisplayName := 'myservice'; end; procedure TMyService.ServiceAfterInstall(Sender: TService); var Reg : TRegistry; ImagePath : string; begin // create needed registry entries after service installation Reg := TRegistry.Create; try Reg.RootKey := HKEY_LOCAL_MACHINE; // set service description if Reg.OpenKey(STR_REGKEY_SVC,False) then begin ImagePath := Reg.ReadString(STR_REGVAL_IMAGEPATH); Reg.WriteString(STR_REGVAL_DESCRIPTION, STR_INFO_SVC_DESC); Reg.CloseKey; end; // set message resource for eventlog if Reg.OpenKey(STR_REGKEY_EVENTMSG, True) then begin Reg.WriteString(STR_REGVAL_EVENTMESSAGEFILE, ImagePath); Reg.WriteInteger(STR_REGVAL_TYPESSUPPORTED, 7); Reg.CloseKey; end; // set installdir if ImagePath <> '' then if Reg.OpenKey(STR_REGKEY_FULL,True) then begin Reg.WriteString(STR_REGVAL_INSTALLDIR, ExtractFilePath(ImagePath)); Reg.CloseKey; end; finally FreeAndNil(Reg); end; end; procedure TMyService.ServiceAfterUninstall(Sender: TService); var Reg : TRegistry; begin Reg := TRegistry.Create; try // delete self created registry keys Reg.RootKey := HKEY_LOCAL_MACHINE; Reg.DeleteKey(STR_REGKEY_EVENTMSG); finally FreeAndNil(Reg); end; end; procedure TMyService.ServiceShutdown(Sender: TService); var Stopped : boolean; begin // is called when windows shuts down ServiceStop(Self, Stopped); end; procedure TMyService.ServiceStart(Sender: TService; var Started: Boolean); begin Started := False; try MyThread := TMyThread.Create; MyThread.Resume; NTEventLog.Add(Eventlog_Success, STR_INFO_SVC_STARTED); Started := True; except on E : Exception do begin // add event in eventlog with reason why the service couldn't start NTEventLog.Add(Eventlog_Error_Type, Format(STR_INFO_SVC_STARTFAIL, [E.Message])); end; end; end; procedure TMyService.ServiceStop(Sender: TService; var Stopped: Boolean); begin try Stopped := True; // always stop service, even if we had exceptions, this is to prevent "stuck" service (must reboot then) MyThread.Terminate; // give MyThread 60 seconds to terminate if WaitForSingleObject(MyThread.ThreadEvent, 60000) = WAIT_OBJECT_0 then begin FreeAndNil(MyThread); NTEventLog.Add(Eventlog_Success,STR_INFO_SVC_STOPPED); end; except on E : Exception do begin // add event in eventlog with reason why the service couldn't stop NTEventLog.Add(Eventlog_Error_Type, Format(STR_INFO_SVC_STOPFAIL, [E.Message])); end; end; end; end.