Pregunta

Tengo un servicio de Windows escrito en C # que actúa como un proxy para un grupo de dispositivos de red en la base de datos de back-end. Para probar y también para agregar una capa de simulación para probar el back-end me gustaría tener una GUI para que el operador de prueba pueda ejecutar la simulación. También para una versión a rayas para enviar como una demostración. La GUI y el servicio no tienen que ejecutarse al mismo tiempo. ¿Cuál es la mejor manera de lograr esta operación de duelo?

Editar: Aquí está mi solución que combina cosas de esta pregunta , Estoy ejecutando como un servicio y Instale un servicio de Windows .NET sin InstallUtil.exe usando este excelente código por

Utiliza la siguiente línea para probar si ejecutar la interfaz gráfica de usuario o ejecutar como servicio.

 if (arg_gui || Environment.UserInteractive || Debugger.IsAttached)

Aquí está el código.


using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.ComponentModel;
using System.ServiceProcess;
using System.Configuration.Install;
using System.Diagnostics;

namespace Form_Service
{
   static class Program
   {
      /// 
      /// The main entry point for the application.
      /// 
      [STAThread]
      static int Main(string[] args)
      {
         bool arg_install =  false;
         bool arg_uninstall = false;
         bool arg_gui = false;
         bool rethrow = false;
         try
         {
            foreach (string arg in args)
            {
               switch (arg)
               {
                  case "-i":
                  case "-install":
                     arg_install = true; break;
                  case "-u":
                  case "-uninstall":
                     arg_uninstall = true; break;
                  case "-g":
                  case "-gui":
                     arg_gui = true; break;
                  default:
                     Console.Error.WriteLine("Argument not expected: " + arg);
                     break;
               }
            }
            if (arg_uninstall)
            {
               Install(true, args);
            }
            if (arg_install)
            {
               Install(false, args);
            }
            if (!(arg_install || arg_uninstall))
            {
               if (arg_gui || Environment.UserInteractive || Debugger.IsAttached)
               {
                  Application.EnableVisualStyles();
                  Application.SetCompatibleTextRenderingDefault(false);
                  Application.Run(new Form1());
               }
               else
               {
                  rethrow = true; // so that windows sees error... 
                  ServiceBase[] services = { new Service1() };
                  ServiceBase.Run(services);
                  rethrow = false;
               }
            }
            return 0;
         }
         catch (Exception ex)
         {
            if (rethrow) throw;
            Console.Error.WriteLine(ex.Message);
            return -1;
         }
      }

      static void Install(bool undo, string[] args)
      {
         try
         {
            Console.WriteLine(undo ? "uninstalling" : "installing");
            using (AssemblyInstaller inst = new AssemblyInstaller(typeof(Program).Assembly, args))
            {
               IDictionary state = new Hashtable();
               inst.UseNewContext = true;
               try
               {
                  if (undo)
                  {
                     inst.Uninstall(state);
                  }
                  else
                  {
                     inst.Install(state);
                     inst.Commit(state);
                  }
               }
               catch
               {
                  try
                  {
                     inst.Rollback(state);
                  }
                  catch { }
                  throw;
               }
            }
         }
         catch (Exception ex)
         {
            Console.Error.WriteLine(ex.Message);
         }
      }
   }

   [RunInstaller(true)]
   public sealed class MyServiceInstallerProcess : ServiceProcessInstaller
   {
      public MyServiceInstallerProcess()
      {
         this.Account = ServiceAccount.NetworkService;
      }
   }

   [RunInstaller(true)]
   public sealed class MyServiceInstaller : ServiceInstaller
   {
      public MyServiceInstaller()
      {
         this.Description = "My Service";
         this.DisplayName = "My Service";
         this.ServiceName = "My Service";
         this.StartType = System.ServiceProcess.ServiceStartMode.Manual;
      }
   }

}
¿Fue útil?

Solución

Básicamente tienes dos opciones. Exponga una API en el servicio que luego puede llamar desde la aplicación de interfaz de usuario O permita que el servicio se ejecute como una aplicación winforms o un servicio.

La primera opción es bastante fácil: use la comunicación remota o WCF para exponer la API.

La segunda opción se puede lograr moviendo " tripas " de su aplicación en una clase separada, a continuación, cree un envoltorio de servicio y un envoltorio de formularios de ganancia que ambos llamen a su " clase.

static void Main(string[] args)
{
    Guts guts = new Guts();

    if (runWinForms)
    {
        System.Windows.Forms.Application.EnableVisualStyles();
        System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false);

        FormWrapper fw = new FormWrapper(guts);

        System.Windows.Forms.Application.Run(fw);
    }
    else
    {
        ServiceBase[] ServicesToRun;
        ServicesToRun = new ServiceBase[] { new ServiceWrapper(guts) };
        ServiceBase.Run(ServicesToRun);
    }
}

Otros consejos

Cree una nueva aplicación winforms que haga referencia al montaje de su servicio.

Si usa el siguiente código:

[DllImport("advapi32.dll", CharSet=CharSet.Unicode)]
static extern bool StartServiceCtrlDispatcher(IntPtr services);
[DllImport("ntdll.dll", EntryPoint="RtlZeroMemory")]
static extern void ZeroMemory(IntPtr destination, int length);

static bool StartService(){
    MySvc svc = new MySvc(); // replace "MySvc" with your service name, of course
    typeof(ServiceBase).InvokeMember("Initialize", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.InvokeMethod,
        null, svc, new object[]{false});
    object entry = typeof(ServiceBase).InvokeMember("GetEntry",
        BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.InvokeMethod, null, svc, null);
    int len = Marshal.SizeOf(entry) * 2;
    IntPtr memory = Marshal.AllocHGlobal(len);
    ZeroMemory(memory, len);
    Marshal.StructureToPtr(entry, memory, false);
    return StartServiceCtrlDispatcher(memory);
}

[STAThread]
static void Main(){
    if(StartService())
        return;

    Application.Run(new MainWnd()); // replace "MainWnd" with whatever your main window is called
}

Luego, su EXE se ejecutará como un servicio (si es lanzado por el SCM) o como una GUI (si es lanzado por cualquier otro proceso).

Básicamente, todo lo que he hecho aquí se usa Reflector para descubrirlo ¿Cuál es la carne de ServiceBase.Run hace, y duplicarlo aquí (se requiere reflexión, porque llama a métodos privados). La razón para no llamar directamente a ServiceBase.Run es que aparece un cuadro de mensaje para decirle al usuario que el servicio no puede iniciarse (si SCM no lo inicia) y no devuelve nada para decirle al código que el servicio no se puede iniciar.

Debido a que esto usa la reflexión para llamar a métodos de marco privado, es posible que no funcione correctamente en futuras revisiones del marco. Codificador de advertencia.

También hay FireDaemon . Esto le permite ejecutar cualquier aplicación de Windows como servicio.

Consulte ¿Me estoy ejecutando como un servicio para obtener más información útil? información.

Lo más importante cubierto es cómo determinar de manera confiable si estamos ejecutando de manera interactiva o mediante un servicio.

tiene que implementar un proceso separado que pueda comunicarse con su servicio. Si bien es posible que en los sistemas XP y anteriores tenga un servicio que muestre una interfaz de usuario, eso ya no es posible en Vista y posteriores.

Otra posibilidad es NO usar un servicio, sino usar una aplicación que se encuentre en la barra de tareas (piense en Roxio Arrastrar a disco, y lo más probable es que su software antivirus se encuentre allí) que tenga un ícono abajo. el reloj, que inicia un menú, cuando se hace clic con el botón derecho, y una interfaz de usuario cuando se hace doble clic.

Si su servicio está modulado correctamente, puede hospedarlo en un ejecutable como servicio, o con un ejecutable con gui para la prueba. También utilizamos este método con nuestro servicio, el ejecutable independiente del servicio aloja el servicio en un entorno productivo, pero también tenemos una aplicación de consola para alojar el servicio.

Separe su código en diferentes componentes: un componente para administrar los aspectos del servicio y otro para realizar la lógica de negocios real. Cree e interactúe con la lógica empresarial desde el componente de servicio. Para las pruebas (de su lógica empresarial) puede crear una aplicación WinForm o de consola que utilice el componente de lógica empresarial sin el componente de servicio. Mejor aún, use un marco de pruebas unitarias para la mayor parte de sus pruebas. Sin duda, muchos de los métodos en el componente de servicio también serán verificables por unidad.

Si encapsula su lógica de negocios en clases de servicio y luego usa un patrón de fábrica para crear esos servicios, puede usar el mismo conjunto de servicios para una aplicación de escritorio (fábrica de escritorio) y como servicios web (host en WCF).

Definición del servicio:

[ServiceContract]
public interface IYourBusinessService
{
    [OperationContract]
    void DoWork();
}

public class YourBusinessService : IYourBusinessService
{
    public void DoWork()
    {
        //do some business logic here
    }

}

Fábrica para WinForms de escritorio para obtener servicios para hacer negocios:

public class ServiceFactory
{
    public static IYourBusinessService GetService()
    {
        //you can set any addition info here
        //like connection string for db, etc.
        return new YourBusinessService();
    }
}

Usted hospeda esto con la clase WCF ServiceHost, o en IIS. Ambos le permiten especificar cómo crear una instancia de cada instancia del servicio para que pueda realizar la inicialización como cadenas de conexión, etc.

Puede crear el servicio para llamar a otro ejecutable con un argumento de línea de comando para que se ejecute sin el formulario. Cuando se llama a ese exe sin el argumento de la línea de comando, muestra la forma y actúa de manera normal.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top