Delphi中拥有相同程序的多个NT服务

问题描述:

我正在寻找Delphi示例代码来开发可多次安装(具有不同名称)的Win32 Windows服务。 这个想法是为每个要安装的服务提供1个注册表项和1个注册表项。 我使用exe来安装/运行许多服务,每个服务从他的注册表子项中获取他的参数。Delphi中拥有相同程序的多个NT服务

有没有人有示例代码?

+0

相关/重复:http://*.com/questions/612587 – 2010-05-24 12:39:04

+0

所以它是!那么,将我的编辑答案添加到选项列表中 - 无论如何,它与接受的答案基本上是一样的。 – shunty 2010-05-26 08:47:51

我们通过创建一个TService后代并添加一个'InstanceName'属性来完成这项工作。这会在命令行上传递,如... instance =“MyInstanceName”,并在SvcMgr.Application.Run之前检查并设置(如果存在)。

例如 Project1.dpr:

program Project1; 

uses 
    SvcMgr, 
    SysUtils, 
    Unit1 in 'Unit1.pas' {Service1: TService}; 

{$R *.RES} 

const 
    INSTANCE_SWITCH = '-instance='; 

function GetInstanceName: string; 
var 
    index: integer; 
begin 
    result := ''; 
    for index := 1 to ParamCount do 
    begin 
    if SameText(INSTANCE_SWITCH, Copy(ParamStr(index), 1, Length(INSTANCE_SWITCH))) then 
    begin 
     result := Copy(ParamStr(index), Length(INSTANCE_SWITCH) + 1, MaxInt); 
     break; 
    end; 
    end; 
    if (result <> '') and (result[1] = '"') then 
    result := AnsiDequotedStr(result, '"'); 
end; 

var 
    inst: string; 

begin 
    Application.Initialize; 
    Application.CreateForm(TService1, Service1); 
    // Get the instance name 
    inst := GetInstanceName; 
    if (inst <> '') then 
    begin 
    Service1.InstanceName := inst; 
    end; 
    Application.Run; 
end. 

1单元(一个TService后裔)

unit Unit1; 

interface 

uses 
    Windows, SysUtils, Classes, SvcMgr, WinSvc; 

type 
    TService1 = class(TService) 
    procedure ServiceAfterInstall(Sender: TService); 
    private 
    FInstanceName: string; 
    procedure SetInstanceName(const Value: string); 
    procedure ChangeServiceConfiguration; 
    public 
    function GetServiceController: TServiceController; override; 
    property InstanceName: string read FInstanceName write SetInstanceName; 
    end; 

var 
    Service1: TService1; 

implementation 

{$R *.DFM} 

procedure ServiceController(CtrlCode: DWord); stdcall; 
begin 
    Service1.Controller(CtrlCode); 
end; 

procedure TService1.ChangeServiceConfiguration; 
var 
    mngr: Cardinal; 
    svc: Cardinal; 
    newpath: string; 
begin 
    // Open the service manager 
    mngr := OpenSCManager(nil, nil, SC_MANAGER_ALL_ACCESS); 
    if (mngr = 0) then 
    RaiseLastOSError; 
    try 
    // Open the service 
    svc := OpenService(mngr, PChar(Self.Name), SERVICE_CHANGE_CONFIG); 
    if (svc = 0) then 
     RaiseLastOSError; 
    try 
     // Change the service params 
     newpath := ParamStr(0) + ' ' + Format('-instance="%s"', [FInstanceName]); // + any other cmd line params you fancy 
     ChangeServiceConfig(svc, SERVICE_NO_CHANGE, // dwServiceType 
           SERVICE_NO_CHANGE, // dwStartType 
           SERVICE_NO_CHANGE, // dwErrorControl 
           PChar(newpath), // <-- The only one we need to set/change 
           nil,    // lpLoadOrderGroup 
           nil,    // lpdwTagId 
           nil,    // lpDependencies 
           nil,    // lpServiceStartName 
           nil,    // lpPassword 
           nil);    // lpDisplayName 
    finally 
     CloseServiceHandle(svc); 
    end; 
    finally 
    CloseServiceHandle(mngr); 
    end; 
end; 

function TService1.GetServiceController: TServiceController; 
begin 
    Result := ServiceController; 
end; 

procedure TService1.ServiceAfterInstall(Sender: TService); 
begin 
    if (FInstanceName <> '') then 
    begin 
    ChangeServiceConfiguration; 
    end; 
end; 

procedure TService1.SetInstanceName(const Value: string); 
begin 
    if (FInstanceName <> Value) then 
    begin 
    FInstanceName := Value; 
    if (FInstanceName <> '') then 
    begin 
     Self.Name := 'Service1_' + FInstanceName; 
     Self.DisplayName := Format('Service1 (%s)', [FInstanceName]); 
    end; 
    end; 
end; 

end. 

用法:
PROJECT1.EXE /安装
PROJECT1。EXE /安装-instance =“MyInstanceName”
PROJECT1.EXE /卸载[-instance =“MyInstanceName]
它实际上并没有做任何事情 - 这是给你写的启动/停止服务器位等

ChangeServiceConfiguration调用用于更新服务管理器在启动时调用的真实命令行,您可以直接编辑注册表,但至少这是'正确的'API方式。

这允许任何数量的要同时运行的服务实例,它们将作为“MyService”,“MyService(Inst1)”,“MyService(AnotherInstance)”等出现在服务管理器中等。

+0

您可以上传/发送示例项目以显示完整的代码吗? 谢谢 – Claudio 2010-05-25 10:44:35

+0

不知道我同意写一个完整的工作项目 - 也许你会希望我们也写这个应用程序:-) 无论如何 - 我更新了这篇文章。 – shunty 2010-05-26 08:46:20

+0

很多很多非常感谢! 感谢您的代码,我解决了这个问题。 这对每个与服务有同样问题的人来说都是黄金。 再次感谢! – Claudio 2010-05-27 14:33:30

有关如何在Delphi中实现服务的问题,使用其他名称不易安装服务(请参阅Quality Central报告#79781)。您可能需要绕过TService/TServiceApplication实现。 要使用不同的名称创建服务,不能简单地使用/ INSTALL命令行参数,但必须使用SCM API或其一个实现(即SC.EXE命令行实用程序)或安装工具。 要告诉服务要读取哪个密钥,您可以在其命令行上将参数传递给服务(它们也有),创建服务时会设置参数。

上下文:通过运行exename.exe/install作为MyService安装服务。服务再次作为MyService2安装。

Delphi不允许使用不同的名称安装两次可执行文件中的服务。参见QC 79781的描述。不同的名称导致服务在“开始”阶段“挂起”(至少根据SCM)。这是因为DispatchServiceMain根据SCM(在启动服务时传入)检查TService实例名称和名称是否相等。当它们不同时DispatchServiceMain不执行TService.Main,这意味着TService的启动代码不会执行。

为了避免这种情况(有点),在Application.Run调用之前调用FixServiceNames过程。

限制:备用名称必须以原始名称开头。如果原始名称是MyService,那么你可以安装MyService1,MyServiceAlternate,MyServiceBoneyHead等。

FixServiceNames所做的是查找所有已安装的服务,检查ImagePath以查看服务是否由此可执行文件实现并收集它们名单。对已安装的ServiceName进行排序。然后检查SvcMgr.Application.Components中的所有TService后代。当以Component.Name(服务的原始名称)开头安装ServiceName时,请使用我们从SCM获取的名称替换它。

procedure FixServiceNames; 
const 
    RKEY_SERVICES = 'SYSTEM\CurrentControlSet\Services'; 
    RKEY_IMAGE_PATH = 'ImagePath'; 
    RKEY_START = 'Start'; 
var 
    ExePathName: string; 
    ServiceNames: TStringList; 
    Reg: TRegistry; 
    i: Integer; 
    ServiceKey: string; 
    ImagePath: string; 
    StartType: Integer; 
    Component: TComponent; 
    SLIndex: Integer; 
begin 
    ExePathName := ParamStr(0); 

    ServiceNames := TStringList.Create; 
    try 
    Reg := TRegistry.Create(KEY_READ); 
    try 
     Reg.RootKey := HKEY_LOCAL_MACHINE; 

     // Openen registry key with all the installed services. 
     if Reg.OpenKeyReadOnly(RKEY_SERVICES) then 
     begin 
     // Read them all installed services. 
     Reg.GetKeyNames(ServiceNames); 

     // Remove Services whose ImagePath does not match this executable. 
     for i := ServiceNames.Count - 1 downto 0 do 
     begin 
      ServiceKey := '\' + RKEY_SERVICES + '\' + ServiceNames[i]; 
      if Reg.OpenKeyReadOnly(ServiceKey) then 
      begin 
      ImagePath := Reg.ReadString(RKEY_IMAGE_PATH); 
      if SamePath(ImagePath, ExePathName) then 
      begin 
       // Only read 'Start' after 'ImagePath', the other way round often fails, because all 
       // services are read here and not all of them have a "start" key or it has a different datatype. 
       StartType := Reg.ReadInteger(RKEY_START); 
       if StartType <> SERVICE_DISABLED then 
       Continue; 
      end; 

      ServiceNames.Delete(i); 
      end; 
     end; 
     end; 
    finally 
     FreeAndNil(Reg); 
    end; 

    // ServiceNames now only contains enabled services using this executable. 
    ServiceNames.Sort; // Registry may give them sorted, but now we are sure. 

    if ServiceNames.Count > 0 then 
     for i := 0 to SvcMgr.Application.ComponentCount - 1 do 
     begin 
     Component := SvcMgr.Application.Components[i]; 
     if not (Component is TService) then 
      Continue; 

     // Find returns whether the string is found and reports through Index where it is (found) or 
     // where it should be (not found). 
     if ServiceNames.Find(Component.Name, SLIndex) then 
      // Component.Name found, nothing to do 
     else 
      // Component.Name not found, check whether ServiceName at SLIndex starts with Component.Name. 
      // If it does, replace Component.Name. 
      if SameText(Component.Name, Copy(ServiceNames[SLIndex], 1, Length(Component.Name))) then 
      begin 
      Component.Name := ServiceNames[SLIndex]; 
      end 
      else 
      ; // Service no longer in executable? 
     end; 
    finally 
    FreeAndNil(ServiceNames); 
    end; 
end; 

注:SO漂亮打印机迷糊在 “服务关键字:= '\' + RKEY_SERVICES + '\' + ServiceNames [I];”行,德尔福(2009)没有问题。

+0

除了像上面那样遍历已安装的服务外,还可以将其与Idsandon的建议结合使用,以便在服务的命令行上传递已安装的名称(在服务安装时)并读取/使用它来替换组件名称以保留Delphi的DispatchServiceMain快乐。 – 2010-05-22 07:52:20

+0

我爱你所有:) 感谢您的回答,在此之前,我没有得到它是德尔福的限制。 我尝试使用您的代码,但它将我的服务“ImagePath”更改为“\ ?? \ C:\ Test \ oppifc8 \ NTService.exe”。 我有Delphi 2006也许这是问题吗?如果我不确定你的代码是否可以从Delphi 2009中得到解决。 Marjan是否可以请你发布一个示例代码,你将如何加入你对ldsandon的建议? – Claudio 2010-05-24 12:31:30

+0

亲爱的Marjan,我试着在Delphi 2009中使用FixServiceNames过程,但仍然无法安装服务两次,也无法理解我在程序中出错的地方。我可以上传我的示例项目的某个地方,并要求你支票吗? 预先感谢您的回答。 – Claudio 2010-05-25 07:38:27