前日因为系统遗留问题,不得不重新打开已经N久没有使用的Borland C++ Builder 6,编写一个Windows服务。最初设想是编写一个能够根据指定参数,设置诸如服务名称、显示名称、描述、配置文件路径的东西,以一个服务程序作为多种不同服务内容的外壳,能够在Windows的服务管理器中分别控制。由于BCB的服务模板未考虑定制的情况,所以需要费点周折。
BCB中用户实现的服务对象继承自TService,这里暂定为TMyService,当使用全局对象Svrmgr::Application(TServiceApplication)来创建服务对象时,用户的服务对象自动成为Application的组件之一。
通常这样来创建服务对象:
- Application->CreateForm(__classid(TMyService), &MyService);
BCB的SvrMgr.hpp其实是Delphi实现的申明。查看SvrMgr.pas,可以见到创建服务的代码:
- Svc := CreateService( SvcMgr,
- PChar(Name),
- PChar(DisplayName),
- SERVICE_ALL_ACCESS,
- GetNTServiceType,
- GetNTStartType,
- GetNTErrorSeverity,
- PChar(Path),
- PChar(LoadGroup),
- PTag,
- PChar(GetNTDependencies),
- PSSN,
- PChar(Password));
这里可以看到,当使用/install参数来注册服务时,BCB的实现是使用TMyService的Name属性作为服务名称,DisplayName作为服务显示名称。如果要定制这两个名称,我们可以增加额外的参数,设为/name和/displayname,分别用于指定服务名称和服务显示名称。示例如下:
MyService /install /name:"ThisIsAnotherService" /displayname:"显示名称"
/name和/displayname后面跟一个":"来分隔后继的字符串,在参数解析时,系统会将双引号中的字符串作为一个整体对待。不了解这一点和对引号有不同看法的请参考Windows提供的帮助:命令提示符。
BCB只处理/install和/uninstall参数,增加的参数需要自己处理,这里要用到两个BCB提供的函数:ParamCount()和ParamStr(),具体使用方法请参考BCB的Help。如下是示例代码:
- bool isInstall = FindCmdLineSwitch("install", true);
- bool isUninstall = FindCmdLineSwitch("uninstall", true);
- AnsiString serviceName;
- AnsiString serviceDispName;
- if (ParamCount() > 1 && (isInstall || isUninstall))
- {
-
- Application->CreateForm(__classid(TMyService), &MyService);
- if ( isInstall || isUninstall )
- {
- for(int i = 1; i <= ParamCount(); i++)
- {
- AnsiString param = ParamStr(i).LowerCase();
- int pos;
- if (param.Pos("/name:"))
- {
- pos = param.Pos(":");
- serviceName = param.SubString(pos + 1, param.Length() - pos);
- if ((pos = serviceName.Pos(" ")) > 0)
- {
- serviceName.Delete(pos, 1);
- }
- }
- else
- if (param.Pos("/dispname:"))
- {
- pos = param.Pos(":");
- serviceDispName = param.SubString(pos + 1, param.Length() - pos);
- }
- }
- if (serviceName.Length() > 0)
- Application->Components[0]->Name = serviceName;
- else
- serviceName = Application->Components[0]->Name;
- if (serviceDispName.Length() == 0)
- serviceDispName = serviceName;
-
- ((TService *)Application->Components[0])->DisplayName = serviceDispName;
- }
- }
- Application->Run();
现在,编译并注册我们的服务,可以看到它按指定的方式显示在服务列表中,让我们试着启动它。。。。。。等等。。。。。启动失败!!!!OOOOOOOH! SHIT!!!!!
点解!?!?
再来查看SvrMgr.pas,BCB(或Delphi?)是这样启动服务滴:
- begin
- Forms.Application.OnException := OnExceptionHandler;
- ServiceCount := 0;
- for i := 0 to ComponentCount - 1 do
- if Components[i] is TService then Inc(ServiceCount);
- SetLength(ServiceStartTable, ServiceCount + 1);
- FillChar(ServiceStartTable[0], SizeOf(TServiceTableEntry) * (ServiceCount + 1), 0);
- J := 0;
- for i := 0 to ComponentCount - 1 do
- if Components[i] is TService then
- begin
- ServiceStartTable[J].lpServiceName := PChar(Components[i].Name);
- ServiceStartTable[J].lpServiceProc := @ServiceMain;
- Inc(J);
- end;
- StartThread := TServiceStartThread.Create(ServiceStartTable);
-
- procedure TServiceStartThread.Execute;
- begin
- if StartServiceCtrlDispatcher(FServiceStartTable[0]) then
- ReturnValue := 0
- else
- ReturnValue := GetLastError;
- end;
Windows API StartServiceCtrlDispatcher()使用服务入口表启动服务,该操作是名称相关的,而我们已经使用了指定的名称安装服务,所以当系统(TServiceApplication)使用原有的名称(这里是MyService)来启动服务时,会找不到名为MyService的服务(我们已经指定其名称为ThisIsAnotherService),导致启动失败。由于系统在启动服务时,没有提供关于服务名称的上下文,因此我们需要作一点手脚,创造这个上下文。简单的方法是:在安装服务时,修改服务的启动路径记录,添加服务名称作为参数。在服务启动时,解析这个参数,并使用该参数修改TMyService->Name,这样服务应能顺利启动。代码示例如下:
先添加额外的参数,保存服务名称:
- Application->Run();
- if (isInstall)
- {
-
- TRegistry* reg = new TRegistry();
- AnsiString key = "//System//CurrentControlSet//Services//" + serviceName;
- reg->RootKey = HKEY_LOCAL_MACHINE;
- if (reg->OpenKey(key, false))
- {
- AnsiString imagePath = reg->ReadString("ImagePath");
- reg->WriteString("ImagePath", imagePath + " -" + serviceName);
- }
- reg->CloseKey();
- delete reg;
- }
再添加对参数的处理:
- if (paramCount() > 1 && (isInstall || isUninstall))
- {
-
- }
- else
- {
-
- if (ParamCount() > 0)
- {
- AnsiString extraParam = ParamStr(1).LowerCase();
- AnsiString specifiedServiceName;
- if (extraParam.Pos("-"))
- {
- int pos = extraParam.Pos("-");
- specifiedServiceName = extraParam.SubString(pos + 1, extraParam.Length() - pos);
- }
- if (!specifiedServiceName.Length())
- {
- Application->CreateForm(__classid(TMyService), &MyService);
- }
- else
- {
- Application->CreateForm(__classid(TMyService), &MyService);
- MyService->Name = specifiedServiceName;
- }
- }
- }
- Application->Run();
现在,编译程序,重新安装服务,再试着启动一下。。。。。。
如果没有错误的话,服务应该能够顺利启动。(废话)
BCB没有提供服务的描述属性,这也可以通过修改注册表的方法实现,操作很简单,这里不在多言。
此乃末技。。。。。