How to write c# service that I can also run as a winforms program?

22,092

Solution 1

You basically have two choices. Either expose an API on the service which you can then call from the UI app OR enable the service to run either as a winforms app or a service.

The first option is pretty easy - use remoting or WCF to expose the API.

The second option can be achieved by moving the "guts" of your app into a separate class then create a service wrapper and a win-forms wrapper that both call into your "guts" class.

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

Solution 2

Create a new winforms app the references the assembly of your service.

Solution 3

If you use the below code:

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

Then your EXE will run as either a service (if launched by the SCM) or as a GUI (if launched by any other process).

Essentially, all I've done here is used Reflector to figure out what the meat of ServiceBase.Run does, and duplicate it here (reflection is required, because it calls private methods). The reason for not calling ServiceBase.Run directly is that it pops up a message box to tell the user that the service cannot be started (if not launched by the SCM) and doesn't return anything to tell the code that the service cannot be started.

Because this uses reflection to call private framework methods, it may not function correctly in future revisions of the framework. Caveat codor.

Solution 4

See Am I running as a service for some further useful information.

The most important thing covered is how to reliably determine whether we are running interactively or via a service.

Solution 5

There is also FireDaemon. This allows you to run any windows application as a service.

Share:
22,092
Rex Logan
Author by

Rex Logan

Rex Logan uɐƃoן xǝᴚ uɐƃoן xǝᴚ ☃ @RexLogan linkedin

Updated on July 09, 2022

Comments

  • Rex Logan
    Rex Logan almost 2 years

    I have a windows service written in C# that acts as a proxy for a bunch of network devices to the back end database. For testing and also to add a simulation layer to test the back end I would like to have a GUI for the test operator to be able run the simulation. Also for a striped down version to send out as a demo. The GUI and service do not have to run at the same time. What is the best way to achieve this duel operation?

    Edit: Here is my solution combing stuff from this question , Am I Running as a Service and Install a .NET windows service without InstallUtil.exe using this excellent code by Marc Gravell

    It uses the following line to test if to run the gui or run as service.

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

    Here is the code.

    
    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;
          }
       }
    
    }
    
  • Rex Logan
    Rex Logan over 15 years
    Has to be a service not my choice.
  • meowmeow
    meowmeow over 15 years
    It is a possibility to meet the needs as described.. I wasn't necessarily recommending that course of action.
  • Matthew Goodwin
    Matthew Goodwin about 8 years
    Came across this answer and am interest in method two that you've got this code sample for. Do you perhaps have any example of how to make this wrapper classes and pass in this instance of the "guts". I'm a little confused how you do this.