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

二次信任 提交于 2019-11-28 19:54:06

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

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

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.

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

Ash

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.

you have to implement a separate process that can communicate with your service. While it is possible on XP and earlier systems to have a service showing an UI, that's no longer possible on Vista and later.

Another possibility is to NOT use a service, but to use an application which resides in the Taskbar (think Roxio Drag-to-Disc, & most likely your Anti-virus software lives down there) which has an icon down by the clock, which launches a menu, when it is right-clicked, and a UI when double-clicked.

If your service is modulated properly, you could host the service either in a executable as a service, or with an executable with gui for the test. We use this method with our service too, the standalone service-executable hosts the service in productive environment, but we have a console-app for hosting the service, too.

Separate your code into different components: one component to manage the service aspects and one to perform the actual business logic. Create and interact with the business logic from the service component. For testing (of your business logic) you can create a WinForm or console application that uses the business logic component without the service component. Better yet, use a unit testing framework for the bulk of your testing. Many of the methods in the service component will undoubtedly be unit testable as well.

If you encapsulate your business logic in service classes and then use a factory pattern to create those services, you can use the same set of services for a desktop application (desktop factory) and as web services (host in WCF).

Service definition:

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

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

}

Factory for desktop WinForms to get at services to do business:

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

You host this either with the WCF ServiceHost class, or in IIS. Both allow you the ability to specify how to instantiate each instance of the service so that you can do initialization like connection strings, etc.

You can create the service to call another executable with a command line argument so it is run without the form. When that exe is called without the command line argument it shows the form and act as normal.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!