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

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

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

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

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

}

11 ответов


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

первый вариант довольно прост в использовании удаленного взаимодействия или WCF для предоставления API.

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

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) и не возвращает ничего, чтобы сказать код, что служба не может быть запущена.

поскольку это использует отражение для вызова методов private framework, оно может неправильно функционировать в будущих версиях framework. нюанс codor.


есть еще FireDaemon. Это позволяет запускать любое приложение windows в качестве службы.


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

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


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


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


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


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


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

сервис определения:

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

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

}

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

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 вызывается без аргумента командной строки, он показывает форму и действует как обычно.