如何编写c#服务,我也可以作为winforms程序运行?

时间:2009-01-07 18:44:46

标签: c# winforms windows-services installer service

我有一个用C#编写的Windows服务,它充当后端数据库的一堆网络设备的代理。为了测试并添加模拟层来测试后端,我希望有一个GUI供测试操作员运行模拟。还可以将条带化版本作为演示发送出去。 GUI和服务不必同时运行。实现这种决斗操作的最佳方式是什么?

编辑: 以下是this question使用Am I Running as a ServiceInstall a .NET windows service without InstallUtil.exethis excellent codeMarc 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 个答案:

答案 0 :(得分:17)

你基本上有两个选择。在服务上公开API然后可以从UI应用程序调用,或者使服务能够作为winforms应用程序或服务运行。

第一个选项非常简单 - 使用远程处理或WCF来公开API。

第二个选项可以通过将应用程序的“内容”移动到一个单独的类中来实现,然后创建一个服务包装器和一个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);
    }
}

答案 1 :(得分:2)

创建一个新的winforms应用程序,引用您的服务程序集。

答案 2 :(得分:2)

如果您使用以下代码:

[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(如果由任何其他进程启动)运行。

基本上,我在这里完成的所有操作都使用Reflector来确定ServiceBase.Run的内容是什么,并在此处复制(需要反射,因为它调用私有方法)。不直接调用ServiceBase.Run的原因是它弹出一个消息框告诉用户该服务无法启动(如果没有由SCM启动)并且不返回任何内容告诉代码该服务无法启动。

因为它使用反射来调用私有框架方法,所以在将来的框架修订版中它可能无法正常运行。 警告密码。

答案 3 :(得分:1)

还有FireDaemon。这允许您将任何Windows应用程序作为服务运行。

答案 4 :(得分:1)

有关更多有用信息,请参阅Am I running as a service

最重要的是如何可靠地确定我们是以交互方式运行还是通过服务运行。

答案 5 :(得分:0)

您必须实施可与您的服务进行通信的单独流程。虽然在XP和早期系统上可以使用显示UI的服务,但在Vista及更高版本中不再可能。

答案 6 :(得分:0)

另一种可能性是不使用服务,而是使用驻留在任务栏中的应用程序(想想Roxio Drag-to-Disc,并且很可能是你的防病毒软件住在那里)时钟,在右键单击时启动菜单,在双击时启动UI。

答案 7 :(得分:0)

如果您的服务被正确调制,您可以将服务作为服务存储在可执行文件中,或者使用带有gui的可执行文件来进行测试。 我们也将此方法与我们的服务一起使用,独立服务可执行文件在生产环境中托管服务,但我们也有一个用于托管服务的控制台应用程序。

答案 8 :(得分:0)

将您的代码分成不同的组件:一个组件用于管理服务方面,另一个组件用于执行实际的业务逻辑。从服务组件创建业务逻辑并与之交互。为了测试(您的业务逻辑),您可以创建一个WinForm或控制台应用程序,该应用程序使用没有服务组件的业务逻辑组件。更好的是,在大部分测试中使用单元测试框架。服务组件中的许多方法无疑也可以进行单元测试。

答案 9 :(得分:0)

如果将业务逻辑封装在服务类中,然后使用工厂模式创建这些服务,则可以将同一组服务用于桌面应用程序(桌面工厂)和Web服务(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中托管此文件。两者都允许您指定如何实例化服务的每个实例,以便您可以像连接字符串等那样进行初始化。

答案 10 :(得分:0)

您可以创建服务以使用命令行参数调用另一个可执行文件,以便在没有表单的情况下运行它。在没有命令行参数的情况下调用该exe时,它会显示该表单并正常运行。