Как написать сервис на c #, который я также могу запускать как программу winforms? - PullRequest
7 голосов
/ 07 января 2009

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

Edit: Вот мое решение, объединяющее вещи из этого вопроса , Работаю ли я как служба и Установите службу Windows .NET без 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;
      }
   }

}

Ответы [ 11 ]

17 голосов
/ 07 января 2009

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

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

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

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);
    }
}
2 голосов
/ 07 января 2009

Если вы используете следующий код:

[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) или как GUI (если запущен любым другим процессом).

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

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

2 голосов
/ 07 января 2009

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

1 голос
/ 08 января 2009

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

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

1 голос
/ 07 января 2009

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

0 голосов
/ 10 января 2009

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

0 голосов
/ 08 января 2009

Если вы инкапсулируете свою бизнес-логику в классы сервисов, а затем используете фабричный шаблон для создания этих сервисов, вы можете использовать тот же набор сервисов для настольного приложения (фабрика рабочего стола) и в качестве веб-сервисов (хост в 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. Оба позволяют вам указать, как создавать экземпляры каждого экземпляра службы, чтобы вы могли выполнять инициализацию, например строки подключения и т. Д.

0 голосов
/ 07 января 2009

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

0 голосов
/ 07 января 2009

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

0 голосов
/ 07 января 2009

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

...