We help IT Professionals succeed at work.

How to send a message to an NT service?

elfield
elfield asked
on
478 Views
Last Modified: 2013-12-03
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

Comment
Watch Question

Commented:
Have you looked at the PostThreadMessage function?
Commented:
This one is on us!
(Get your first solution completely free - no credit card required)
UNLOCK SOLUTION

Author

Commented:
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.)  

Author

Commented:
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?

Author

Commented:
(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?

Author

Commented:

(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?

ete

Commented:
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

Author

Commented:
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
ete

Commented:
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
ete

Commented:
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

Author

Commented:
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.

Commented:
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.

Author

Commented:
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.)
ete

Commented:
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


Commented:
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?

Author

Commented:
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.

Gain unlimited access to on-demand training courses with an Experts Exchange subscription.

Get Access
Why Experts Exchange?

Experts Exchange always has the answer, or at the least points me in the correct direction! It is like having another employee that is extremely experienced.

Jim Murphy
Programmer at Smart IT Solutions

When asked, what has been your best career decision?

Deciding to stick with EE.

Mohamed Asif
Technical Department Head

Being involved with EE helped me to grow personally and professionally.

Carl Webster
CTP, Sr Infrastructure Consultant
Empower Your Career
Did You Know?

We've partnered with two important charities to provide clean water and computer science education to those who need it most. READ MORE

Ask ANY Question

Connect with Certified Experts to gain insight and support on specific technology challenges including:

  • Troubleshooting
  • Research
  • Professional Opinions
Unlock the solution to this question.
Join our community and discover your potential

Experts Exchange is the only place where you can interact directly with leading experts in the technology field. Become a member today and access the collective knowledge of thousands of technology experts.

*This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

OR

Please enter a first name

Please enter a last name

8+ characters (letters, numbers, and a symbol)

By clicking, you agree to the Terms of Use and Privacy Policy.