如何创建带参数的Windows服务?

14
我已经编写了一个 Windows 服务,每个客户需要运行一个实例。这是因为每个客户都有自己的具有相同架构的数据库;Windows 服务之间唯一的区别在于它们将各自具有不同的参数,对应于它们指定要服务的客户端数据库。(而且我不能使用多个工作线程来运行一个服务,因为数据库连接使用静态变量,我无法跨线程进行操作。)
我找到了这个小而精致的教程,关于如何制作一个 Windows 服务,但它只向我展示了如何设置单个服务。我想要设置 n 个服务实例,每个实例都有包含客户名的显示名称,并使用表示客户 ID 的命令行参数运行。
上面链接的教程有一个名为 MyWindowsServiceInstaller 的类,它在本地系统上安装 Windows 服务,我猜这可能是一个合适的地方,在所有客户端中设置一个 foreach 循环,为每个客户端设置一个服务。但是我找不到任何提供的接口,可以让我为新服务设置命令行参数。
你该怎么做?

我正在撤下我的回答,因为有些人比我更了解这个问题。 - Binary Worrier
@Binary Worrier - 我希望你没有删除你的答案!里面有一些非常有前途的想法... - Shaul Behr
回答未被删除:我将其标记为CW,它是不完整的,我们在某个地方有一张“如何”说明单,可以让多个相同服务实例运行,但我找不到它,也没有时间去挖掘,抱歉Shaul。 - Binary Worrier
5个回答

18

9
Wil Peck写了一篇好文章,介绍如何在单个框中安装多个Windows服务的实例。基本思路是通过给它们不同的名称来欺骗安装程序,使其认为它们是不同的服务。
话虽如此,重新设计数据库连接代码以支持多个工作线程似乎更容易(并且更易于维护)。

1
如果能提供链接就加1分,如果建议在一个服务中使用多个工作线程则再加1分。您可以始终拥有一个GUI远程连接到服务并显示运行线程的信息。 - Binary Worrier

2

您可以使用installutil传递参数给您的安装程序,例如ServiceNameDisplayName

ProjectInstaller.cs

public partial class ProjectInstaller : Installer
{
    protected override void OnBeforeInstall(IDictionary savedState)
    {
        SetServiceName();
        base.OnBeforeInstall(savedState);
    }

    protected override void OnBeforeUninstall(IDictionary savedState)
    {
        SetServiceName();
        base.OnBeforeUninstall(savedState);
    }

    private string AppendParameter(string path, char parameter, string value)
    {
        if (!path.StartsWith("\""))
            path = $"\"{path}\"";

        if (value.Contains(" "))
            value = $"\"{value}\"";

        return $"{path} -{parameter}{value}";
    }

    private void SetServiceName()
    {
        if (Context.Parameters.ContainsKey("ServiceName"))
            serviceInstaller.ServiceName = Context.Parameters["ServiceName"];

        if (Context.Parameters.ContainsKey("DisplayName"))
            serviceInstaller.DisplayName = Context.Parameters["DisplayName"];

        Context.Parameters["assemblypath"] = AppendParameter(Context.Parameters["assemblypath"], 's', serviceInstaller.ServiceName);
    }
}

这将向存储在服务中的路径添加一个参数,例如:

之前:"C:\Service.exe"

之后:"C:\Service.exe" -s"Instance 1"

然后,您可以在启动服务时读取此参数并传递给您的服务构造函数。

Program.cs

static void Main(string[] args)
{
    string serviceName = args.Single(x => x.StartsWith("-s")).Substring("-s".Length);

    ServiceBase service = new Service(serviceName);
    ServiceBase.Run(service);
}

Service.cs

public partial class Service : ServiceBase
{
    public Service(string serviceName)
    {
        InitializeComponent();

        ServiceName = serviceName;
    }
}

使用方法

installutil /ServiceName="Instance 1" /DisplayName="Instance 1 Service" "C:\Service.exe"
installutil /ServiceName="Instance 2" /DisplayName="Instance 2 Service" "C:\Service.exe"

1
据我所知,使用ServiceInstallerServiceProcessInstallerinstallutil都无法提供启动参数。但是,可以使用advapi.dll(请查看左侧菜单)中的一些COM API提供启动参数。所需调用的完整集合可以在此处找到。有一个名为ServiceInstaller的类(也包含所需的外部方法和一些实用方法)。
您需要使用实用程序方法InstallAndStart。它接受服务名称、显示名称和表示Windows服务的可执行文件的路径。您可以像这样调用它:
InstallAndStart("MyService", "My Service For User 1",
                "c:\\pathtoexe\MyService.exe user1");

如果您有以下服务,则参数startupParam将接收值user1
class Program : ServiceBase
{
    private string startupParam;

    static void Main(string[] args)
    {
        string arg = args[0];
        ServiceBase.Run(new Program(arg));
    }

    public Program(string startupParam)
    {
        this.ServiceName = "MyService";
        this.startupParam = startupParam;
    }
    ...
}

嗯...我刚试了一下,好像installutil不允许你传递参数...?我有什么遗漏吗? - Shaul Behr
嗯,看来我说错了。对此我感到抱歉,请忘记installutil部分。我自己使用一个自定义的ServiceInstaller类,它使用COM api来启动和停止服务。这个类允许传递参数,我以为installutil也是一样的。我会更新我的回答... - Ronald Wildenberg
Ronald,我有一个客户需要使用系统帐户在系统上执行操作的Windows服务,因为我的客户没有权限。如果我想让客户端告诉Windows服务要做什么,我应该每次停止和启动吗?像这样传递参数?我发现OnCustomCommand功能不太有用,因为您只能传递整数。还是应该使用数据库或注册表键根据运行的客户端临时设置参数? - Entree
在这种情况下,我会使用WCF在您的Windows服务上公开一个端点,以接收来自客户端的命令。 - Ronald Wildenberg

1

你需要安装服务多次,并使用它的exe.config文件进行自定义设置。

或者,你可以有一个服务为每个客户端运行不同的工作线程。

更新

exe.Config是应用程序配置文件

我不知道如何使用安装程序组件来安装多个服务实例,我不知道你可以这样做。

当我们需要在一台机器上运行多个服务实例时,我们实际上只安装一次,然后复制已安装的文件夹并更改第二个实例的exe名称。然后,第二个实例将在其自己的应用程序配置文件中进行配置。


在我上面链接的教程中,有一个名为MyWindowsServiceInstaller的类,它负责安装工作。看起来那应该是设置所有客户端循环的地方,但我完全找不到任何地方来设置每个服务的命令行参数。你如何设置这些参数?还有,请原谅我的无知,exe.config是什么? - Shaul Behr
我已经更新了我的问题,以澄清你提到的一些点。 - Shaul Behr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接