Дизайн сервисов Windows Delphi
Дизайн разработки Windows Delphi
Я никогда не создавал службу Windows, но читал все, что нашел. Все статьи или примеры, с которыми я столкнулся, очень просты в реализации и ограничены по своему охвату. Не видели ничего, что выходит за рамки того или иного конкретного сценария. Итак, у меня есть вся теория, которую я, вероятно, найду, и теперь я готов погрузиться в этот проект. Мне нравится планировать свои идеи и получать отзывы о том, что думают люди. Я опишу, что мне нужно от приложения, и как я собираюсь его построить. Я был бы признателен за комментарии от тех, у кого есть опыт создания служб Windows и любых советов, которые они хотели бы поделиться.
[СЦЕНАРИЙ]
Сейчас у меня есть приложение (я назову это UPDATEAPPLICATION), которое предоставляет обновления для всех наших других приложений. Чтобы запустить любое из наших приложений, вам сначала нужно запустить эту программу UPDATEAPPLICATION и передать ей параметр желаемого приложения. UPDATEAPPLICATION вызывает WebService, который возвращает XML-информацию о том, имеет ли требуемое приложение какие-либо обновления.
Если есть обновление, UPDATEAPPLICATION загружает обновление в формате EXE или ZIP и заменяет соответствующие файлы для обновления целевого приложения. После этого UPDATEAPPLICATION выполняет ShellExecute для запуска нужного приложения, а затем UPDATEAPPLICATION закрывается.
Это довольно простой процесс, который хорошо работал на протяжении многих лет. Программа UPDATEAPPLICATION является приложением Delphi, другие наши приложения смешанны: Delphi, VB6, MS Access,.NET.
[ПРОБЛЕМА]
С переходом на Vista и Windows 7 безопасность резко изменилась. Из-за характера UPDATEAPPLICATION UAC не позволит программе запускаться без доступа администратора или полностью отключенного UAC. Мы находимся в процессе обновления многих наших приложений до .NET, и в течение этого процесса я хотел бы, чтобы приложения, а также UPDATEAPPLICATION были совместимы с UAC. Из того, что я исследовал, единственный способ сделать это - создать UPDATEAPPLICATION как службу Windows. Поэтому, по сути, мне нужно дублировать функциональность UPDATEAPPLICATION в архитектуре службы Windows.
[МОЙ ДИЗАЙН]
Я использую DelphiXE2. Мой проект будет состоять из трех частей, чтобы сформировать единое решение: Windows Service, небольшое приложение для приложения для взаимодействия с Windows Service и мои измененные приложения, которые будут отправлять сообщения службе Windows.
- Моя служба Windows (которую я вызову UPDATESERVICE) будет запущена в качестве службы Windows и создаст TCP-сервер для прослушивания запросов.
- Приложение лотка (которое я вызову TRAYAPP) будет использовать TCP Client для настройки/управления UPDATESERVICE.
- My USERAPPLICATION, при запуске, отправит TCP-сообщение в UPDATESERVICE, в котором говорится, что "ЭТО ЗАЯВЛЕНИЕ" запустилось.
[UPDATESERVICE]
Будут слушать сообщения. Если он получит сообщение о запуске USERAPPLICATION, он вызовет веб-службу, чтобы узнать, есть ли обновления. Если есть, пользователь будет уведомлен о закрытии приложения и позволит UPDATESERVICE обновить приложение. UPDATESERVICE загрузит соответствующие файлы и обновит приложение.
Теперь, когда я объяснил основы того, что я пытаюсь сделать, я могу задать свои конкретные вопросы, на которые мне нужно ответить. Все это связано с тем, как я должен создать свою службу Windows. Я также планирую использовать OmniThread для управления потоками.
Когда начинается моя служба, мне нужно создать TCP-сервер.
- Должна ли создаваться служба TCP в ее собственном потоке?
- Если служба TCP - это собственный поток, как сохранить поток в живых? В противном случае я могу запустить службу TCP, но я не уверен, какой код я бы использовал в блоке службы TCP, чтобы поддерживать поток.
- Какое событие служб Windows должно создать службу TCP? OnExecute? OnStart? OnCreate? В конце концов, я прочитал, что неясно, какое событие следует использовать.
- Когда служба TCP получает сообщение, чтобы что-то делать, должна ли выполняться работа в потоке службы TCP или новый поток, созданный с основного UPDATESERVICE? Например:
- если служба TCP получает сообщение для проверки обновления с использованием HTTP, если поток службы TCP порождает новый поток для выполнения этой работы.
- Или, если поток службы TCP отправит сообщение в UPDATESERVICE, чтобы создать новый поток для выполнения этой работы.
- Это даже имеет значение?
- Возможно ли запустить/остановить/зарегистрировать/отменить регистрацию службы Windows в коде Delphi?
Это все мои вопросы. Вероятно, для этого нет правильного/неправильного ответа, а просто предпочтения, основанного на опыте. Если вы создали сервисы с Delphi, у вас, вероятно, есть вклад, который мне пригодится. Если у вас есть более надежный проект, то базовый "начать сервис и спать" и готов поделиться им - даже если я не запускаю или просто код psuedo - я уверен, что это было бы неоценимо. Спасибо, что прочитал мой длинный вопрос. Если вы можете подумать о лучшем способе этого, пожалуйста, поделитесь своими мыслями. Я добавлю, что некоторые из наших приложений могут быть загружены и запущены широкой публикой, поэтому я не имею полного контроля над ожидаемыми средами. Любые советы/комментарии/помощь будут оценены.
Ответы
Ответ 1
быстрые ответы:
1 & 3) Да. Как правило, не выполняйте событие службы OnExecute. Создайте собственный поток из события службы OnStart. Поток может быть прерван при получении события службы OnStop.
2) вы сохраняете свой поток живым, как это (метод выполнения):
while not Terminated do
begin
// do something
end;
4) обычно каждое клиентское соединение будет жить на собственной ветке. (т.е. TCP-сервер порождает новый поток для каждого клиента). Используйте хорошо известный стек, например Indy или ICS. Что касается обновления HTTP, вы можете сделать это в порожденном потоке клиентского подключения.
5) да, имейте в виду, что для этого вам нужны повышенные права.
В моей карьере я сделал немало услуг, и до сих пор я всегда использую один и тот же скелет для сервисного приложения:
unit u_svc_main;
interface
uses
// Own units
u_globals, u_eventlog, u_MyThread,
// Third party units
// Delphi units
Windows, Messages, Registry, SysUtils, Classes, SvcMgr;
type
TMyService = class(TService)
procedure ServiceCreate(Sender: TObject);
procedure ServiceAfterUninstall(Sender: TService);
procedure ServiceAfterInstall(Sender: TService);
procedure ServiceShutdown(Sender: TService);
procedure ServiceStop(Sender: TService; var Stopped: Boolean);
procedure ServiceStart(Sender: TService; var Started: Boolean);
private
{ Private declarations }
MyThread : TMyThread;
public
{ Public declarations }
function GetServiceController: TServiceController; override;
end;
var MyService : TMyService;
implementation
{$R *.DFM}
procedure ServiceController(CtrlCode: DWord); stdcall;
begin
MyService.Controller(CtrlCode);
end;
function TMyService.GetServiceController: TServiceController;
begin
Result := ServiceController;
end;
procedure TMyService.ServiceCreate(Sender: TObject);
begin
DisplayName := 'myservice';
end;
procedure TMyService.ServiceAfterInstall(Sender: TService);
var
Reg : TRegistry;
ImagePath : string;
begin
// create needed registry entries after service installation
Reg := TRegistry.Create;
try
Reg.RootKey := HKEY_LOCAL_MACHINE;
// set service description
if Reg.OpenKey(STR_REGKEY_SVC,False) then
begin
ImagePath := Reg.ReadString(STR_REGVAL_IMAGEPATH);
Reg.WriteString(STR_REGVAL_DESCRIPTION, STR_INFO_SVC_DESC);
Reg.CloseKey;
end;
// set message resource for eventlog
if Reg.OpenKey(STR_REGKEY_EVENTMSG, True) then
begin
Reg.WriteString(STR_REGVAL_EVENTMESSAGEFILE, ImagePath);
Reg.WriteInteger(STR_REGVAL_TYPESSUPPORTED, 7);
Reg.CloseKey;
end;
// set installdir
if ImagePath <> '' then
if Reg.OpenKey(STR_REGKEY_FULL,True) then
begin
Reg.WriteString(STR_REGVAL_INSTALLDIR, ExtractFilePath(ImagePath));
Reg.CloseKey;
end;
finally
FreeAndNil(Reg);
end;
end;
procedure TMyService.ServiceAfterUninstall(Sender: TService);
var
Reg : TRegistry;
begin
Reg := TRegistry.Create;
try
// delete self created registry keys
Reg.RootKey := HKEY_LOCAL_MACHINE;
Reg.DeleteKey(STR_REGKEY_EVENTMSG);
finally
FreeAndNil(Reg);
end;
end;
procedure TMyService.ServiceShutdown(Sender: TService);
var
Stopped : boolean;
begin
// is called when windows shuts down
ServiceStop(Self, Stopped);
end;
procedure TMyService.ServiceStart(Sender: TService; var Started: Boolean);
begin
Started := False;
try
MyThread := TMyThread.Create;
MyThread.Resume;
NTEventLog.Add(Eventlog_Success, STR_INFO_SVC_STARTED);
Started := True;
except
on E : Exception do
begin
// add event in eventlog with reason why the service couldn't start
NTEventLog.Add(Eventlog_Error_Type, Format(STR_INFO_SVC_STARTFAIL, [E.Message]));
end;
end;
end;
procedure TMyService.ServiceStop(Sender: TService; var Stopped: Boolean);
begin
try
Stopped := True; // always stop service, even if we had exceptions, this is to prevent "stuck" service (must reboot then)
MyThread.Terminate;
// give MyThread 60 seconds to terminate
if WaitForSingleObject(MyThread.ThreadEvent, 60000) = WAIT_OBJECT_0 then
begin
FreeAndNil(MyThread);
NTEventLog.Add(Eventlog_Success,STR_INFO_SVC_STOPPED);
end;
except
on E : Exception do
begin
// add event in eventlog with reason why the service couldn't stop
NTEventLog.Add(Eventlog_Error_Type, Format(STR_INFO_SVC_STOPFAIL, [E.Message]));
end;
end;
end;
end.