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

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

编辑:这是我的解决方案,从这个问题 , 我运行即服务和安装.NET Windows服务没有InstallUtil.exe使用这个优秀的代码 Marc 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; } } } 

你基本上有两个选择。 在服务上公开API,然后可以从UI应用程序调用该API,或者使服务能够作为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); } } 

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

如果您使用以下代码:

 [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.Runfunction,并在这里复制它(需要reflection,因为它调用私有方法)。 不直接调用ServiceBase.Run的原因是它弹出一个消息框告诉用户该服务无法启动(如果没有由SCM启动)并且没有返回任何内容告诉代码服务不能是开始。

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

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

请参阅我作为服务运行以获取更多有用信息。

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

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

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

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

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

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

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