8

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

编辑:这是我的解决方案,结合了这个问题的内容,我是作为服务运行并在没有 InstallUtil.exe 的情况下使用Marc Gravell编写 的优秀代码安装 .NET Windows 服务

它使用以下行来测试是运行 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;
      }
   }

}
4

11 回答 11

17

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

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

第二个选项可以通过将你的应用程序的“guts”移动到一个单独的类中来实现,然后创建一个服务包装器和一个 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);
    }
}
于 2009-01-07T19:02:49.680 回答
2

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

于 2009-01-07T18:46:49.667 回答
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是它会弹出一个消息框告诉用户服务无法启动(如果不是由单片机启动),并且没有返回任何内容告诉代码服务无法启动。

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

于 2009-01-07T19:15:46.347 回答
1

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

于 2009-01-07T18:48:05.513 回答
1

有关更多有用信息,请参阅我作为服务运行。

涵盖的最重要的事情是如何可靠地确定我们是在交互运行还是通过服务运行。

于 2009-01-08T18:28:16.723 回答
0

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

于 2009-01-07T18:48:17.807 回答
0

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

于 2009-01-07T18:50:08.940 回答
0

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

于 2009-01-07T18:51:46.020 回答
0

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

于 2009-01-07T18:56:08.977 回答
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 中托管它。两者都允许您指定如何实例化服务的每个实例,以便您可以进行初始化,如连接字符串等。

于 2009-01-07T21:52:37.623 回答
0

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.

于 2009-01-10T18:25:30.450 回答