Как написать службу c #, которую я также могу запускать как программу winforms?

StackOverflow https://stackoverflow.com/questions/421516

Вопрос

У меня есть служба Windows, написанная на C #, которая действует как прокси для группы сетевых устройств к внутренней базе данных.Для тестирования, а также для добавления слоя моделирования для тестирования серверной части я хотел бы иметь графический интерфейс для оператора тестирования, чтобы иметь возможность запускать моделирование.Также для полосатой версии для отправки в качестве демо.Графический интерфейс и служба не обязательно должны запускаться одновременно.Каков наилучший способ добиться этой операции дуэли?

Редактировать:Вот мое решение, вычесывающее материал из этот вопрос , Работаю ли я как Сервис и Установите службу .NET Windows без InstallUtil.exe используя этот превосходный код Автор: Марк Гравелл

Он использует следующую строку, чтобы проверить, следует ли запускать графический интерфейс или запускаться как сервис.

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

Вот код.


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;
      }
   }

}
Это было полезно?

Решение

По сути, у вас есть два варианта.Либо предоставьте API в службе, который затем вы можете вызвать из приложения пользовательского интерфейса, либо включите службу для запуска либо как приложение winforms, либо как сервис.

Первый вариант довольно прост - используйте remoting или WCF для предоставления доступа к API.

Второй вариант может быть достигнут путем перемещения "внутренностей" вашего приложения в отдельный класс, затем создайте оболочку service и оболочку win-forms, которые оба вызывают ваш класс "guts".

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);
    }
}

Другие советы

Создайте новое приложение winforms, которое ссылается на сборку вашего сервиса.

Если вы используете приведенный ниже код:

[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
}

Тогда ваш EXE-файл будет запускаться либо как сервис (если он запущен SCM), либо как графический интерфейс пользователя (если он запущен любым другим процессом).

По сути, все, что я здесь сделал, используется Отражатель чтобы выяснить, из чего состоит мясо ServiceBase.Run делает, и продублируйте его здесь (требуется отражение, потому что оно вызывает частные методы).Причина, по которой вы не звоните ServiceBase.Run непосредственно заключается в том, что появляется окно сообщения, чтобы сообщить пользователь что служба не может быть запущена (если она не запущена SCM) и не возвращает ничего, что могло бы сообщить код что служба не может быть запущена.

Поскольку при этом используется отражение для вызова частных методов фреймворка, оно может некорректно функционировать в будущих версиях фреймворка. Будьте осторожны с кодом.

Существует также Огненный Демон.Это позволяет вам запускать любое приложение Windows как службу.

Видишь Работаю ли я как сервис для получения дополнительной полезной информации.

Самое важное, о чем идет речь, - это как надежно определить, работаем ли мы в интерактивном режиме или через сервис.

вы должны внедрить отдельный процесс, который может взаимодействовать с вашим сервисом.Хотя в XP и более ранних системах возможно иметь службу, отображающую пользовательский интерфейс, в Vista и более поздних версиях это больше невозможно.

Другая возможность заключается не в использовании сервиса, а в использовании приложения, которое находится на панели задач (например, Roxio Drag-to-Disc, и, скорее всего, там находится ваше антивирусное программное обеспечение), у которого есть значок внизу рядом с часами, который запускает меню при щелчке правой кнопкой мыши и пользовательский интерфейс при двойном щелчке.

Если ваш сервис модулирован должным образом, вы могли бы разместить сервис либо в исполняемом файле как сервис, либо в исполняемом файле с графическим интерфейсом для тестирования.Мы также используем этот метод с нашим сервисом, автономный исполняемый файл сервиса размещает сервис в продуктивной среде, но у нас также есть консольное приложение для размещения сервиса.

Разделите свой код на разные компоненты:один компонент для управления аспектами обслуживания и один для выполнения фактической бизнес-логики.Создавайте бизнес-логику из компонента service и взаимодействуйте с ней.Для тестирования (вашей бизнес-логики) вы можете создать WinForm или консольное приложение, которое использует компонент бизнес-логики без компонента service.А еще лучше, используйте фреймворк модульного тестирования для основной части вашего тестирования.Многие методы в сервисном компоненте, несомненно, также будут поддаваться модульному тестированию.

Если вы инкапсулируете свою бизнес-логику в классы сервисов, а затем используете шаблон factory для создания этих сервисов, вы можете использовать один и тот же набор сервисов для настольного приложения (desktop factory) и в качестве веб-сервисов (host в WCF).

Определение сервиса:

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

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

}

Фабрика настольных WinForms для получения услуг at для ведения бизнеса:

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

Вы размещаете это либо с помощью класса WCF ServiceHost, либо в IIS.Оба позволяют вам указать, как создавать каждый экземпляр сервиса, чтобы вы могли выполнять инициализацию, например, строки подключения и т.д.

Вы можете создать службу для вызова другого исполняемого файла с аргументом командной строки, чтобы она запускалась без формы.Когда этот exe-файл вызывается без аргумента командной строки, он показывает форму и действует как обычно.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top