Link to home
Start Free TrialLog in
Avatar of elfield
elfield

asked on

How to send a message to an NT service?

How can I send a message to my program, which is running
as a service under NT?

Background:

My program runs as an NT service (managed by SRVANY.EXE), and it doesn't have a window -- but it does have a window procedure.

I'd like another of my programs to send a user message to the window procedure of my service program.  But since it doesn't have a window, it doesn't have a window handle -- so I don't know how to send to it.  

NT knows about the window procedure, because it calls it sometimes.

I'm able to obtain the process ID of my service program, but I don't know how to get from the process ID to the window procedure.


Any ideas?

Thanks in advance, Mark

Avatar of y96andha
y96andha

Have you looked at the PostThreadMessage function?
ASKER CERTIFIED SOLUTION
Avatar of ete
ete

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of elfield

ASKER

Dear ETE,

Thanks.  I'll try your FindWindow() suggestion (in C) and I'll
get back to you -- with luck, by tommorrow.  (I didn't realize
FindWindow would be able to see a service.)  
Avatar of elfield

ASKER

Dear ETE,

I tried your suggestion, but it didn't work when the process was
running as a service.  It does work when the process is started from
the command line.

Here's the startup code of program A (which may run as a service):

int     PASCAL
WinMain(ih, ph, cl, sh)
      HINSTANCE ih;                   /* instance handle */
        HINSTANCE ph;                   /* previous instance */
        LPSTR   cl;                     /* command line */
      int     sh;                     /* initial window display */
{
static  char    b[81];
      char    a[81];                  /* initial command line */
        char    *p;
      char    *q;
        WNDCLASS w;                     /* window class */

        strcpy(a, _argv[0]);            /* save program name */
      strlwr(a);                      /* to lower case */
        for (p = a; (q = strpbrk(p, ":/\\")) != NULL; p = q + 1)
            ;                       /* find start of command name */
        strcpy(b, p);
      p = strchr(b, '.');             /* exclude .exe from class name */
        if (p)
            *p = 0;
        w.style = CS_BYTEALIGNCLIENT | CS_HREDRAW | CS_VREDRAW;
      w.lpfnWndProc   = wpr;
        w.cbClsExtra    = 0;
      w.cbWndExtra    = 0;
        w.hInstance     = ih;
      w.hIcon         = LoadIcon(ih, "TICO_MI");
        if (w.hIcon == NULL) /* if not found, use Windows icon */
            w.hIcon = LoadIcon(NULL, IDI_APPLICATION);
        w.hCursor       = LoadCursor(NULL, IDC_ARROW);
      w.hbrBackground = GetStockObject(WHITE_BRUSH);
        w.lpszMenuName  = NULL;
      w.lpszClassName = b;
      RegisterClass(&w);
      ...
}

Here's the logic of program B, which wants to send a WM_CLOSE
message to program A:

{
      char    *b;
        HWND    w;

      b = the lower-case program name (no path, no .exe)
      w = FindWindow(b, NULL);
      if (w)                  /* if found */
            PostMessage(w, WM_CLOSE, 0, 0); /* close the process*/
}

The FindWindow() call finds Program A when it's started from the
command line, but not when it's started as a service.

Any ideas?

Avatar of elfield

ASKER

(Here it is again, perhaps less garbled -- but some comments
are truncated or lack */)

Dear ETE,

I tried your suggestion, but it didn't work when the process was
running as a service.  It does work when the process is started from the command line.

Here's the startup code of program A (which may run as a service):

int     PASCAL
WinMain(ih, ph, cl, sh)
      HINSTANCE ih;                   /* instance handle */
        HINSTANCE ph;                   /* previous instance */
        LPSTR   cl;                     /* command line */
      int     sh;                     /* initial window display */
{
static  char    b[81];
      char    a[81];                  /* initial command line */
        char    *p;
      char    *q;
        WNDCLASS w;                     /* window class */

        strcpy(a, _argv[0]);            /* save program name */
      strlwr(a);                      /* to lower case */
        for (p = a; (q = strpbrk(p, ":/\\")) != NULL; p = q + 1)
            ;                       /* find start of name */
        strcpy(b, p);
      p = strchr(b, '.');             /* exclude .exe from name
        if (p)
            *p = 0;
        w.style = CS_BYTEALIGNCLIENT | CS_HREDRAW | CS_VREDRAW;
      w.lpfnWndProc   = wpr;
        w.cbClsExtra    = 0;
      w.cbWndExtra    = 0;
        w.hInstance     = ih;
      w.hIcon         = LoadIcon(ih, "TICO_MI");
        if (w.hIcon == NULL) /* if not found, use Windows icon */
            w.hIcon = LoadIcon(NULL, IDI_APPLICATION);
        w.hCursor       = LoadCursor(NULL, IDC_ARROW);
      w.hbrBackground = GetStockObject(WHITE_BRUSH);
        w.lpszMenuName  = NULL;
      w.lpszClassName = b;
      RegisterClass(&w);
      ...
}

Here's the logic of program B, which wants to send a WM_CLOSE
message to program A:

{
      char    *b;
        HWND    w;

      b = the lower-case program name (no path, no .exe)
      w = FindWindow(b, NULL);
      if (w)                  /* if found */
            PostMessage(w, WM_CLOSE, 0, 0); /* close process
}

The FindWindow() call finds Program A when it's started from the
command line, but not when it's started as a service.

Any ideas?

Avatar of elfield

ASKER


(One more time ..., if this is hard to read, send me your e-mail
address and I'll e-mail it)

Dear ETE,

I tried your suggestion, but it didn't work when the process was
running as a service.  It does work when the process is started from the command line.

Here's the startup code of program A (which may run as a service):

int     PASCAL
WinMain(ih, ph, cl, sh)
        HINSTANCE ih;                   /* instance handle */
        HINSTANCE ph;                   /* previous instance */
        LPSTR   cl;                     /* command line */
        int     sh;                     /* initial window display */
{
static  char    b[81];
        char    a[81];                  /* initial command line */
        char    *p;
        char    *q;
        WNDCLASS w;                     /* window class */

        strcpy(a, _argv[0]);            /* save program name */
        strlwr(a);                      /* to lower case */
        for (p = a; (q = strpbrk(p, ":/\\")) != NULL; p = q + 1)
                ;                /*find start of command name */
        strcpy(b, p);
        p = strchr(b, '.');    /* exclude .exe from class name */
        if (p)
                *p = 0;
        w.style = CS_BYTEALIGNCLIENT | CS_HREDRAW | CS_VREDRAW;
        w.lpfnWndProc   = wpr;
        w.cbClsExtra    = 0;
        w.cbWndExtra    = 0;
        w.hInstance     = ih;
        w.hIcon         = LoadIcon(ih, "TICO_MI");
        if (w.hIcon == NULL) /* if not found, use Windows icon */
                w.hIcon = LoadIcon(NULL, IDI_APPLICATION);
        w.hCursor       = LoadCursor(NULL, IDC_ARROW);
        w.hbrBackground = GetStockObject(WHITE_BRUSH);
        w.lpszMenuName  = NULL;
        w.lpszClassName = b;
        RegisterClass(&w);
        ...
}

Here's the logic of program B, which wants to send a WM_CLOSE
message to program A:

{
        char    *b;
        HWND    w;

        b = the lower-case program name (no path, no .exe)
        w = FindWindow(b, NULL);
        if (w)                  /* if found */
                PostMessage(w, WM_CLOSE, 0, 0);
}

The FindWindow() call finds Program A when it's started from the
command line, but not when it's started as a service.

Any ideas?

Okay, your code cleared the situation.

When your application is run from the command line, it has the window you specified in your code and it runs as an ordinary application. When you run it through SRVANY.EXE, it runs in an instance context of of SRVANY.EXE.

This means, that SRVANY.EXE is a generic service entry point, which interacts with Windows NT Service Control Manager. It loads your app with LoadLibrary() API, fills SERVICE_TABLE_ENTRY, calls StartServiceCtrlDispatcher() and RegisterServiceCtrlHandler() API's, etc, to create a generic service. Then it creates it's own hidden window and attaches your window procedure on it. That's why FindWindow() never finds the window you are looking for.

If you know the process id of your service process and the process has only one window, the window handle can be found in the following manner:

DWORD pidTemp = your_original_pid;
HWND   hwndSrv = NULL;

//call EnumWindows() with a pointer to the temp var
EnumWindows(
(WNDENUMPROC)YourEnumProc,
(LPARAM)&pidTemp);

if(pidTemp != your_original_pid)
   hwndSrv = (HWND)pidTemp;
.
.
.

BOOL CALLBACK YourEnumProc(
HWND hwnd,
LPARAM lParam)
{
   LPDWORD lpdwPid = (LPDWORD)lParam;
   DWORD dwThisPId = 0;
   
   GetWindowThreadProcessId(hwnd,
                                       (LPDWORD)&dwThisPId);

   //no match for process id , continue enumarating
   if(*lpdwPid != dwThisPId)
       return TRUE;                  

   //match found for the process id,
   //set window handle to the var!!!

   *lpdwPid = (DWORD)hwnd;          

   //because id found, terminate
   return FALSE;
}

Place your process id into a temporary variable. Call EnumWindows() API with ADDRESS of this variable. Your enumeration procedure loops through the top level windows in the system and gets the process id of them. It compares the given process id to the retrieved one. If match found, the window handle is returned in the id variable.

NOTE! This mechanism is not adquite for general puroses. It omits OLE enabled apps, multiple and child windows and window hierarchies in the app, but it should be suitable to your purposes.


Best regards
ETE
Avatar of elfield

ASKER

I already tried the "enumerate windows" method (using GetWindowThreadProcessID).   It gives window
handles for programs launched from the command line, but not for my service program (or for programs launched by my service program).  

If you're willing to give me your e-mail address, I can send you a zipped file that includes a test program (in C) that demonstrates this behavior.




launcwindows, but not for ser
That is a good idea!
I will test your code immediately after receiving it and return the modified source code, if a reasonable solution can be found. I will work on it during the weekend. So the results, good or bad, you will have at the latest on Monday.

My email address:
Esa.Tervo@teleste.fi
Thanks for the source code. Testing revieled, that the original purpose to find the window and send a msg will not work.

But anyway, not a big deal. We can always interact directly with the Windows NT Service Control Manager.

I wrote a library, DxSrv32.dll, which lets you control not only those services run with SRVANY.EXE, but all installed services in the computer. You can also install new ones and remove existing ones.

This library can be called from any application and it gives your programs total control on all the services on the machine.

Library contains following functions:

DxSrvInstallService()
DxSrvRemoveService()
DxSrvStartService()
DxSrvStopService()
DxSrvPauseService()
DxSrvContinueService()
DxSrvIsServiceRunning()

I will email you the comlete source code.

My best regards
ETE
Avatar of elfield

ASKER

Thanks for your response.

However, I don't want to interact with the service program
via DxSrv functions.  The service program launches many other
programs (the sample I sent you launched just one).  I need to
locate these launched programs and send messages to them.
Have you written all these programs yourself? Do you really have to use window messages, or could you use some other means of communication? I would suggest using a named pipe.
Avatar of elfield

ASKER

Thanks for your suggestion.  A named pipe might work if the window procedure (of the program launched by a service) gets a message that there's something in the pipe to read.  Do you know if it will?  (As is, the window procedure receives very few messages -- mainly timer events.)  

(Yes, they're all my programs.)
Hi Mark,
In your instructions you emailed me, there was a simple reason why you cannot interact with your apps with window messages.

You must let the service interact with the desktop (run on the SYSTEM account, check Control Panel, Services, Startup, Logon As..). If the service is not allowed to interact with desktop, there is no window handle available and no messaging, period.

If you allow interaction however, your current tx_xts.exe, ts_wlp.exe, etc are visible. They cannot be closed through Task Manager, but they can be closed through their system menu.

Most propably you would like to avoid this side effect. When your tx_xts.exe is run through SRVANY.EXE, just make your application windows hidden.

The other drawback are the logoff and shutdown events. All interactive windows will be closed by system on those events. However, you can omit closing by disabling WM_QUERYENDSESSION and WM_ENDSESSION messages.

This is the only easy solution to your problem. Only by allowing interactivity you get the handles and the messaging and the functionality you are looking for.

Best regards,
ETE


I do not think that you can have a message sent to you window procedure automatically.

Do you really need to get it through the window procedure? What kind of messages are you expecting? Since you don't have a window, unless you set it to run interactively as the system account, couldn't you use some other kind of loop to wait for whatever you have to wait for?
Avatar of elfield

ASKER

I tried the PostThreadMessage (suggested by y96andha) --
after having the service-launched process identify its
thread is by writing it to a file -- and that didn't work
either.  Interestingly PostThreadMessage returned "successful",
but the receiving process didn't get the message.

So, thanks to everybody for trying.