Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Delphi: WinAPI и системное программирование > Как послать сообщение сервису?


Автор: danilsl 30.8.2007, 23:44
Привет Всем.
Я пишу системный сервис, который будет размножать потоки. О завершении потоков я хочу уведомлять основной поток сервиса через системные сообщения. Если я правильно понял смысл системных сообщений, ловить их могут только окна, неважно видимые или нет. С помощью проги WinSigh я нашёл, что процесс сервиса создаёт два каких-то окна (Скриншот в прикреплённом файле, 33 кб). В оба этих окна я пытался слать сообщения (из внешней проги), однако эффекта ноль. Вот сам код сервиса:
Код
unit Main;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, SvcMgr, Dialogs;

const
  WM_TEST = WM_APP+1;

type
  TTestSrv = class(TService)
  private
    { Private declarations }
    procedure ProcessMsg(var Msg:TMessage); message WM_TEST;
  public
    function GetServiceController: TServiceController; override;
    { Public declarations }
  end;

var
  TestSrv: TTestSrv;

implementation

{$R *.DFM}

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

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

procedure TTestSrv.ProcessMsg(var Msg: TMessage);
begin
  ShowMessage('Словил');
end;

end.

Сервис интерактивный, так что ShowMessage вылезет. Сообщения слал тремя способами: SendMessage, PostMessage, PostThreadMessage. Кроме того перебрал все хэндлы, которые нашёл в объектах Application и сервиса.
Возможно ли вообще заставить сервис ловить эти сообщения? Или всё равно придётся создавать скрытое окно?

Вот в этом варианте сообщения ловятся, но это дополнительное окно smile  smile 
Код

unit Main;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, SvcMgr, Dialogs;

type
  TTestSrv = class(TService)
    procedure ServiceStart(Sender: TService; var Started: Boolean);
  private
    { Private declarations }
  public
    function GetServiceController: TServiceController; override;
    { Public declarations }
  end;

var
  TestSrv: TTestSrv;

implementation

{$R *.DFM}

uses
  MsgHookForm;

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

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

procedure TTestSrv.ServiceStart(Sender: TService; var Started: Boolean);
begin
  PostMessage(Form1.Handle, WM_APP+1, 0, 0);
end;

end.

Код

unit MsgHookForm;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs;

const
  WM_TEST = WM_APP+1;

type
  TForm1 = class(TForm)
  private
    { Private declarations }
    procedure ProcessMsg(var Msg:TMessage); message WM_TEST;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.ProcessMsg(var Msg: TMessage);
begin
  ShowMessage('Словил');
end;

end.


Автор: NiJazz 31.8.2007, 07:44
danilsl, лучше избегать создания интерактивных сервисов. Если нужен диалог с пользователем, то лучше для этого создать отдельное GUI-приложение, которое будет общаться с сервисом посредством каких-либо стандартных механизмов (pipes, sockets). При такой архитектуре и проблем взаимодействия с сервисом не будет и у самого сервиса будет меньше проблем (особенно в поздних версиях винды).

Автор: NiJazz 31.8.2007, 11:40
Services running in an elevated security context, such as the LocalSystem account, should not create a window on the interactive desktop, because any other application that is running on the interactive desktop can interact with this window. This exposes the service to any application that a logged-on user executes. Also, services that are running as LocalSystem should not access the interactive desktop by calling the OpenWindowStation or GetThreadDesktop function.


The following list identifies the two ways that a service can interact with a user without creating an interactive service:

Display a message box by calling the MessageBox function with MB_SERVICE_NOTIFICATION. This is recommended for displaying simple status messages. Do not call MessageBox during service initialization or from the HandlerEx routine, unless you call it from a separate thread, so that you return to the SCM in a timely manner. 
Create a separate hidden GUI application and use the CreateProcessAsUser function to run the application within the context of the interactive user. Design the GUI application to communicate with the service through some method of interprocess communication (IPC), for example, named pipes. The service communicates with the GUI application to tell it when to display the GUI. The client communicates the results of the user interaction back to the service so that the service can take the appropriate action. Note that IPC can expose your service interfaces over the network unless you use an appropriate access control list (ACL). 

Future versions of Windows may not support interactive services. Therefore, it is better to use another approach to support interaction between a user and a service.


Автор: danilsl 31.8.2007, 12:41
Ну собственно интерактивный сервис мне и не нужен. Ну а пример из первого поста интерактивный потому что имхо ShowMessage самый простой способ проконтролировать что сервисЧтотоДелает. В данной теме мне более интересно, как заставить сервис принимать системные сообщения без создания скрытого окна.

Автор: NiJazz 31.8.2007, 14:01
danilsl, лучше всего так: отправил запрос, получил ответ, но по каналу, без помощи окон.

Автор: danilsl 31.8.2007, 16:12
Если можно, по-подробнее, по какому каналу?
Мне нужно всего лишь, что бы потоки тупо информировали основной поток моего сервиса о своём завершении. Что бы информация о количестве и состоянии порождённых потоков обрабатывалась в контексте одного основного потока, а остальные потоки ничего об этом не знали и в эти данные не лезли. А это в свою очередь мне нужно, что бы не изобретать велосипед с синхронизацией, так как потоков планируется много, и как минимум трёх типов. Так что в этом свете мне кажется проще создать таки отдельное окошко, обработчики которого будут выполняться в контексте основного потока и можно будет спокойно лесть к структурам, содержащим инфу о потоках. К тому же, используя этот способ я смогу спокойно вытаскивать из потоков обработаную инфу, опять же не заморачиваясь с синхронизацией.
С окошком я уже поигрался, с отключенным заголовком, границами кнопочками и остальными прибамбасами сервис отъел от памяти всего лишь 200кб, да и экзэшник вырос не на много. Так что в принципе довольно неплохой вариант. Во всяком случае заготовки потоков рождаются и мрут именно так, как я хочу.
Кстати. Говоря о каналах, уж не pipe ли имеются в виду? У меня книжечка есть "Программирование в сетях Windows" 2002 года (Microsoft Press кстати), так там каналы описываются в части "Устаревшие сетевые API". Что ж делать со следующими версиями Windows? Ведь как и всё устаревшее они рано или поздно перестанут поддерживаться виндой...

Автор: MetalFan 1.9.2007, 10:32
Цитата(danilsl @  31.8.2007,  16:12 Найти цитируемый пост)
Устаревшие сетевые API

ну тут не сетевое, а между процессами взаимодействие...  вполне подойдет имхо)

Автор: Alix 21.9.2007, 16:15
Мне интересно, а это ограничение на создание окон распространяется на все окна или окна с HWND_MESSAGE в качестве родителя в CreateWindow прокатывают? Вот у меня не получилось, но, быть может, я что-то не то делаю...
Вообще хотелось бы чтобы сервер получал именно сообщения на окно, его разве никак нельзя создать чтобы оно не было on the interactive desktop

Автор: Virtuals 24.9.2007, 08:28
какое ограничение?. 
выше написано что не стоит создавать окна сервисов в контексте 
рабочего стола - оконной станции пользователя 
(иначе любой пользователь может взаимодействовать с вашим окном, в том числе и прибить его)
что похоже не знали создатели многих файрволов и антивирусов smile //postmessage(FindWindow('имя класса антивируса',nil),WM_QUIT,0,0);

если данное окно нужно только для взаимодействия между потоками и вашим приложением, то создавайте в свое удовольствие, при условии что сервис не интерактивный (винда создаст для вашего процесса, оконную станцию с произвольным именем, что изолирует вашы окна от пользователя). все ваши потоки будут на одном и том же станции/столе что обеспечит нормальное взаимодействие

окошко рекомендую создать на чистом апи а не тянуть с собой forms.

вот вам пример заготовка
Код


uses
    Messages, Windows, SysUtils;

procedure WNxl; stdcall;
  const
  TITLE = 'MyClassWxl';
  SCREEN_WIDTH = 2;
  SCREEN_HEIGHT = 2;
const id_Hot = 0;

var
  Handle: hWnd;
function WindowProcedure(Window: hWnd; Message, wParam: Word; lParam: LongInt): LongInt; stdcall;
begin
    Result:=0;
 case Message of
  WM_CREATE:
   begin
   end;
  WM_SIZE:
   begin
   end;
  WM_CLOSE:
   begin
    PostQuitMessage(0);
    Result:=0;
    Exit;
   end;
  WM_KEYDOWN:
   begin
   end;
  WM_KEYUP:
   begin
   end;
 else Result := DefWindowProc(Window, Message, wParam, lParam);
 end;
end;

// Create the window
function CreateTheWindow(Width, Height: Integer): hWnd;
var WindowClass: TWndClass;
    hInstance: HINST;
    Style: Cardinal;
begin
 hInstance := GetModuleHandle(nil);
 ZeroMemory(@WindowClass, SizeOf(WindowClass));
 with WindowClass do
  begin
   Style := CS_SAVEBITS;;
   lpfnWndProc := @WindowProcedure;
   cbClsExtra := 0;
   cbWndExtra := 0;
   hInstance := hInstance;
   hIcon := 0;
   hCursor := 0;
   hbrBackground := 0;
   lpszMenuName := '';
   lpszClassName := TITLE;
  end;
 RegisterClass(WindowClass);
 Style := WS_OVERLAPPEDWINDOW;
 Result := CreateWindowEx(0, TITLE, TITLE, Style, 0, 0, Width, Height, 0, 0, hInstance, nil);
end;

procedure DestroyTheWindow(Wnd: hWnd);
begin
 DestroyWindow(Wnd);
 UnRegisterClass(TITLE, hInstance);
end;

function WinMain(hInstance: HINST; hPrevInstance: HINST; lpCmdLine: PChar; nCmdShow: Integer): Integer; stdcall;
var Msg : TMsg;
    Finished : Boolean;
begin
 Finished := False;
 while not Finished do
  begin
   if PeekMessage(Msg, 0, 0, 0, PM_REMOVE) then
    begin
     if Msg.Message = WM_QUIT then Finished := True
                              else
       begin
        TranslateMessage(msg);
        DispatchMessage(msg);
       end;
    end
   else
    begin
    WaitMessage;
    end;
  end;
 Result := Msg.wParam;
end;

begin
Handle:= CreateTheWindow(SCREEN_WIDTH, SCREEN_HEIGHT);
if Handle=0 then halt(0);
 WinMain(hInstance, 0, nil, 0);
 DestroyTheWindow(Handle);
end;

end.
......
//это вызвать при старте сервиса (хотя можно и не в отдельном потоке)
hThread2:=CreateThread(nil,0,@WNxl,nil,0,ThID2);

Автор: Alix 24.9.2007, 10:01
Да нее... Создавать окна и сервисы я умею, можно было без кода. Проблема как раз в том, что сервис должен был общаться с внешним приложением, а не со своими потоками. И по ряду причин всякие pipes и mailslots не подходят.

Автор: Virtuals 24.9.2007, 10:22
с внешним приложением не значит с пользователем!
речь идет о том что для взаимодействия между окнами они должны находится на одном станции/столе 
тоесть если приложение запустиш из сервиса (с параметрами по умолчанию) то оно окажется на одном станции/столе с сервисом и все будет работать

ЗЫ
пользовательский: Winsta0\Default
winlogon: Winsta0\Winlogon
сервис: blablabla\Default

ЗЫЗЫ кстати винда сама активно работает с сервисами именно через pipes  smile 

Код

BOOL
WINAPI
CreateProcessWithLogonW(
      LPCWSTR lpUsername,
      LPCWSTR lpDomain,
      LPCWSTR lpPassword,
      DWORD dwLogonFlags,
      LPCWSTR lpApplicationName,
      LPWSTR lpCommandLine,
      DWORD dwCreationFlags,
      LPVOID lpEnvironment,
      LPCWSTR lpCurrentDirectory,
      LPSTARTUPINFOW lpStartupInfo,
      LPPROCESS_INFORMATION lpProcessInformation)

//.
//.
//.
            hpipe = CreateFileW(L"\\\\.\\pipe\\SecondaryLogon",
                  GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_EXISTING, 
                  FILE_FLAG_OVERLAPPED, NULL);
            if(hpipe == INVALID_HANDLE_VALUE) 
            {
                LastError = GetLastError();
                __leave;
            }

         }
         else
            __leave;
      }

      DWORD cbBytesTransferred;
      OVERLAPPED    Overlapped;
      SECURITY_ATTRIBUTES   SecurityAttr;

      Overlapped.Offset = 0;
      Overlapped.OffsetHigh = 0;

      SecurityAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
      SecurityAttr.lpSecurityDescriptor = NULL;
      SecurityAttr.bInheritHandle = FALSE;

      Overlapped.hEvent = CreateEvent( &SecurityAttr, FALSE, FALSE, NULL );
      if(Overlapped.hEvent == NULL) __leave;

      if (!WriteFile(hpipe, &cbBytesToSend, sizeof(cbBytesToSend),
                     &cbBytesTransferred, &Overlapped))
      {
//.
//.
//.

(С) М$ да простят меня великие и богатые

Автор: Alix 24.9.2007, 13:25
Цитата
тоесть если приложение запустиш из сервиса (с параметрами по умолчанию) то оно окажется на одном станции/столе с сервисом и все будет работать

имхо, это плохой способ, т.к. тогда это приложение получит права учетной записи под которой запущен сервис, а это чаще всего System

Автор: Virtuals 24.9.2007, 13:44
Код

LogonUser (лох ушастый)
CreateProcessAsUser(прога от имени лоха ушастого)
 smile  smile 

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)