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

问题描述:

我有一个用C#编写的Windows服务,它充当一堆网络设备到后端数据库的代理。为了测试并添加一个仿真层来测试后端,我希望有一个GUI让测试操作员能够运行仿真。还可以作为演示发送出去。 GUI和服务不必同时运行。实现这一决斗的最佳方式是什么?如何编写我也可以作为winforms程序运行的c#服务?

编辑: 这里是Marc Gravell

我的解决方案从this questionAm I Running as a ServiceInstall a .NET windows service without InstallUtil.exe使用this excellent code梳理的东西它使用以下行测试,如果运行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; 
     } 
    } 

} 
+0

我该如何运行它作为服务?我从cmd运行我的程序,我编写了c:\ myservice.exe - 安装没有任何问题。如果我只写c:\ myservice.exe打开我的应用程序。我做错了什么。或者我该如何使用它? – 2012-06-18 16:13:28

+0

你有没有理解下面的解决方案?至于创建包装类等? – 2016-02-26 20:31:27

你基本上有两种选择。可以在服务上公开一个API,然后您可以从UI应用程序调用此服务,也可以将服务作为Winforms应用程序或服务运行。

第一个选项是很容易的 - 使用远程或WCF暴露的API。

第二个选项可以通过移动你的应用程序的“胆量”到一个单独的类,然后创建一个服务包装和双赢的形式包装来实现这两个呼叫到您的“胆量”级。

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应用程序引用您的服务的程序集。

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

你必须实现一个单独的进程,可以使用您的服务进行通信。尽管在XP和更早版本的系统上有可能提供显示用户界面的服务,但在Vista和更高版本上不再可能。

另一种可能是不使用服务,而是使用驻留在任务栏中的应用程序(认为Roxio Drag-to-Disc,&最有可能是您的防病毒软件居住在那里)时钟,它在右键单击时启动菜单,双击时则启动UI。

+0

必须是不是我选择的服务。 – 2009-01-07 18:51:45

如果你的服务是否正常调节,你可以在任何一个可执行的托管服务作为一项服务,或者与GUI测试用的可执行文件。 我们也将这种方法与我们的服务一起使用,独立的服务可执行文件托管在生产环境中的服务,但我们也有托管服务的控制台应用程序。

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

如果使用下面的代码:

[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启动)并且不返回任何东西以告知代码该服务无法启动。

由于这种使用反射来调用私有框架方法,它可能无法正确地在框架的未来版本的功能。 警告代码。

如果将业务逻辑封装在服务类中,然后使用工厂模式创建这些服务,则可以为桌面应用程序(桌面工厂)和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。两者都允许你指定如何实例化服务的每个实例,这样就可以做到像初始化连接字符串的能力,等等​​。

一些进一步的有用信息,请参见Am I running as a service

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

您可以创建服务,所以它是没有形式运行调用另一个可执行文件的命令行参数。当这个exe被称为没有命令行参数时,它显示窗体并作为正常。