Announcement

Collapse
No announcement yet.

Windows Service + Why is CreateWindowex not starting the message loop??

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • Windows Service + Why is CreateWindowex not starting the message loop??

    Perhaps CreateWindowex is not the right way to get a message loop going with callback in a service??

    Code:
    '==============================================================================
    '
    '  ServeMe  Copyright (c) 2004  PowerBASIC, Inc.
    '  All Rights Reserved.
    '
    '  ServeMe is an example for PB/CC 3.0 that demonstrates a simple NT service.
    '  An NT service (or Windows service) is a program that runs in the background,
    '  potentially even when no user is logged in. An NT-class OS is required.
    '
    '==============================================================================
    
    %USEMACROS = 1
    #INCLUDE "Win32API.inc"
    
    $SERVICENAME = "ServeMePB"   ' Internal service name. Must be unique.
    
    
    
    GLOBAL g_fRunning      AS LONG     ' whether service is running
    GLOBAL g_hEvent        AS DWORD    ' "service running" event handle
    GLOBAL g_hService      AS DWORD    ' service status handle
    GLOBAL g_sServiceName  AS STRING   ' service name
    GLOBAL g_ServiceStatus AS SERVICE_STATUS  ' service status
    
    GLOBAL hwnd    AS DWORD
    GLOBAL oldproc AS DWORD
    
    
    ' ServiceProc is the thread function that does the real work of the service.
    '
    THREAD FUNCTION ServiceProc (BYVAL unused AS LONG) AS LONG
    
     LOCAL Msg AS tagMsg
     LOCAL hInstance AS DWORD
    
      hwnd    = CreateWindowex(0, "", "", 0, 0, 0, 0, 0, %NULL, %NULL, hInstance, BYVAL 0)
      oldproc = setwindowlong(hwnd, %GWL_WNDPROC, CODEPTR(mainproc))
    
      DIALOG POST hwnd, 1000, 1, 2
    
      OPEN "C:\PBSERVICE\moo.txt" FOR APPEND AS #1
        PRINT #1, "ServiceProc pre loop"
      CLOSE #1
    
      WHILE GetMessage(Msg, %NULL, 0, 0)
        CALL TranslateMessage(Msg)
        CALL DispatchMessage(Msg)
      WEND
    
      OPEN "C:\PBSERVICE\moo.txt" FOR APPEND AS #1
        PRINT #1, "ServiceProc post loop"
      CLOSE #1
    
    END FUNCTION
    '------------------------------------------------------------------------------
    CALLBACK FUNCTION Mainproc()
    
    
      SELECT CASE CB.MSG
    
        CASE %WM_INITDIALOG
         OPEN "C:\PBSERVICE\moo.txt" FOR APPEND AS #1
             PRINT #1, "%WM_INITDIALOG"
          CLOSE #1
    
        CASE %WM_CREATE
         OPEN "C:\PBSERVICE\moo.txt" FOR APPEND AS #1
             PRINT #1, "%WM_CREATE"
         CLOSE #1
    
        CASE %WM_DESTROY
         OPEN "C:\PBSERVICE\moo.txt" FOR APPEND AS #1
             PRINT #1, "%WM_DESTROY"
         CLOSE #1
         setwindowlong(hwnd, %GWL_WNDPROC, oldproc)      'clean up
         postquitmessage(0)
    
        CASE 1000
         OPEN "C:\PBSERVICE\moo.txt" FOR APPEND AS #1
           PRINT #1, "1000 - " + STR$(CB.WPARAM) + " - " + STR$(CB.LPARAM)
         CLOSE #1
    
      END SELECT
    
    
      FUNCTION = callwindowproc(oldproc, CB.HNDL, CB.MSG, CB.WPARAM, CB.LPARAM)
    
    
    END FUNCTION
    
    
    
    ' The ServiceCtrlHandler is called by the Windows Service Control Manager (SCM)
    ' with service control messages. This allows your service to be properly
    ' stopped. You may choose to receive other messages, also.
    '
    SUB ServiceCtrlHandler (BYVAL dControlCode AS DWORD)
    
        g_ServiceStatus.dwCheckpoint = 0
    
        SELECT CASE dControlCode
    
        CASE %SERVICE_CONTROL_STOP, %SERVICE_CONTROL_SHUTDOWN
            ' Tell the SCM we're going to stop.
            g_ServiceStatus.dwCurrentState = %SERVICE_STOP_PENDING
            g_ServiceStatus.dwWaitHint     = 5000
            SetServiceStatus g_hService, g_ServiceStatus
            ' Clear "running" flag and set the termination event.
            g_fRunning = 0
            SetEvent g_hEvent
    
        END SELECT
    
    END SUB
    
    
    
    ' Windows calls the ServiceMain function to start the service. ServiceMain
    ' handles the necessary service initialization calls, starts the service as a
    ' new thread, and then just sits around until the service is stopped.
    '
    ' The parameters to ServiceMain can be used to pass the equivalent of a
    ' command line to the service. This example doesn't bother.
    '
    SUB ServiceMain (BYVAL dwArgc AS DWORD, BYVAL lpszArgv AS DWORD)
    
        LOCAL hThread AS DWORD
        LOCAL nJunk   AS LONG
    
        ' Create a service termination event so we can efficiently do nothing.
        g_hEvent = CreateEvent(BYVAL 0, 1, 0, BYVAL 0)
        IF g_hEvent = 0 THEN EXIT SUB
    
        ' Register the service.
        g_hService = RegisterServiceCtrlHandler(BYVAL STRPTR(g_sServiceName), _
                                                CODEPTR(ServiceCtrlHandler))
        IF g_hService = 0 THEN EXIT SUB
    
        ' Define the type of service.
        g_ServiceStatus.dwServiceType  = %SERVICE_WIN32_OWN_PROCESS
        ' Maximum milliseconds for the SCM to wait between your calls to
        ' SetServiceStatus. You need to call SetServiceStatus within the timeout
        ' period, or the SCM will assume your service initialization has crashed.
        g_ServiceStatus.dwWaitHint = 2000
        ' Define the current status of the service.
        g_ServiceStatus.dwCurrentState = %SERVICE_START_PENDING
        SetServiceStatus g_hService, g_ServiceStatus
        INCR g_ServiceStatus.dwCheckpoint
    
        ' ***
        ' This is where your service initialization code (if any) goes.
        ' Remember to call SetServiceStatus as needed to keep the SCM updated.
        ' Increment g_ServiceStatus.dwCheckpoint each time.
        ' ***
    
    
              OPEN "C:\PBSERVICE\moo.txt" FOR APPEND AS #1
               PRINT #1, "ServiceMain"
              CLOSE #1
    
        ' Start the service.
        THREAD CREATE ServiceProc(0) TO hThread
        IF hThread = 0 THEN EXIT SUB
        THREAD CLOSE hThread TO nJunk
        g_fRunning = -1
    
        ' Define the type of control events that your service can process.
        g_ServiceStatus.dwControlsAccepted = %SERVICE_ACCEPT_STOP _
                                          OR %SERVICE_ACCEPT_SHUTDOWN
        ' Notify the SCM that the service is running.
        g_ServiceStatus.dwCurrentState = %SERVICE_RUNNING
        g_ServiceStatus.dwCheckpoint = 0
        SetServiceStatus g_hService, g_ServiceStatus
    
        ' Microsoft says to use RegisterWaitForSingleObject instead, if the OS is
        ' Windows 2000 or later. That's a lot more involved, though, and the NT
        ' approach still works just fine.
        WaitForSingleObject g_hEvent, %INFINITE
    
        ' Notify the SCM that the service is dead and gone.
        g_ServiceStatus.dwCurrentState = %SERVICE_STOPPED
        SetServiceStatus g_hService, g_ServiceStatus
    
          OPEN "C:\PBSERVICE\moo.txt" FOR APPEND AS #1
             PRINT #1, "END ServiceMain"
          CLOSE #1
    
    END SUB
    
    
    
    FUNCTION PBMAIN () AS LONG
    
        ' The service table entry array needs to be one element larger than the
        ' number of services involved. The last element is left clear, so Windows
        ' can figure out when it's reached the end of the service list.
        DIM ste(0 TO 1) AS LOCAL SERVICE_TABLE_ENTRY
    
        ' The internal service name must be unique.
        g_sServiceName = "ServeMePB!"
    
        ste(0).lpServiceName = STRPTR(g_sServiceName)
        ste(0).lpServiceProc = CODEPTR(ServiceMain)
    
        StartServiceCtrlDispatcher BYVAL VARPTR(ste(0))
    
    
          OPEN "C:\PBSERVICE" FOR APPEND AS #1
             PRINT #1, "END pbmain"
          CLOSE #1
    
    END FUNCTION

  • #2
    In modern Windows versions (I think this started with W2K), Services aren't allowed to create windows/dialogues, except the checkbox "Allow interaction between desktop and service" on the "Logon" tab of the service's property page is checked and dwServiceType flag includes SERVICE_INTERACTIVE_PROCESS

    See https://docs.microsoft.com/en-us/win...service_status for more info on the above flag.

    Comment


    • #3
      I think you are not using PBCC, or you would get a compile time error on CALLBACK FUNCTION.

      PBCC is most direct way to create a service because it has STDIN, STDOUT, etc

      When done troubleshooting your service add #CONSOLE OFF and recompile and install as a service.

      This makes PBCC the better choise for CGI too. (that's Common Gateway Interface, not Color Graphics Interface)

      If you only have PBWin, add STDIN and STDOUT (search forum), no windows and no callback.

      It has been like 15 years since I experimented with services, memory fading, and I can't find my own example code at all.

      So, just "good luck" with this project ...
      Dale

      Comment


      • #4
        Thanks Dale

        How would STDIN AND STDOUT be helpful in a service. I use them all the time for CGI.

        Comment


        • #5
          This is actually working...
          The timer helped prove it...

          Code:
          THREAD FUNCTION ServiceProc (BYVAL unused AS LONG) AS LONG
          
           LOCAL Msg AS tagMsg
           LOCAL hInstance AS DWORD
           LOCAL LER AS DWORD
          
            hwnd    = CreateWindowex(0, "MESSAGE", "", 0, 0, 0, 0, 0, %NULL, %NULL, hInstance, BYVAL 0)
            LER = GETLASTERROR
            oldproc = setwindowlong(hwnd, %GWL_WNDPROC, CODEPTR(mainproc))
          
            DIALOG POST hwnd, %START_UP, 10, 20
          
            OPEN "C:\PBSERVICE\moo.txt" FOR APPEND AS #1
              PRINT #1, "ServiceProc pre loop - " + STR$(HWND) + " - " STR$(LER)
            CLOSE #1
          
            WHILE GetMessage(Msg, %NULL, 0, 0)
              CALL TranslateMessage(Msg)
              CALL DispatchMessage(Msg)
            WEND
          
            OPEN "C:\PBSERVICE\moo.txt" FOR APPEND AS #1
              PRINT #1, "ServiceProc post loop"
            CLOSE #1
          
          END FUNCTION
          '------------------------------------------------------------------------------
          CALLBACK FUNCTION Mainproc()
          STATIC MY_COUNTER AS LONG
          
          
            SELECT CASE CB.MSG
          
              CASE %WM_TIMER
               OPEN "C:\PBSERVICE\moo.txt" FOR APPEND AS #1
                   PRINT #1, "%WM_TIMER"
               CLOSE #1
               INCR MY_COUNTER
               IF MY_COUNTER > 20 THEN
                DIALOG POST hwnd, %WM_DESTROY, 0, 0
               END IF
          
          
              CASE %START_UP
              SETTIMER(hwnd,  %TIMER_1,  1000)
              OPEN "C:\PBSERVICE\moo.txt" FOR APPEND AS #1
                 PRINT #1, "%START_UP
                 PRINT #1, STR$(CB.MSG) + " - " + STR$(CB.WPARAM) + " - " + STR$(CB.LPARAM)
               CLOSE #1
          
              CASE %WM_DESTROY
               OPEN "C:\PBSERVICE\moo.txt" FOR APPEND AS #1
                   PRINT #1, "%WM_DESTROY"
               CLOSE #1
               setwindowlong(hwnd, %GWL_WNDPROC, oldproc)      'clean up
               postquitmessage(0)
          
              CASE 1000
               OPEN "C:\PBSERVICE\moo.txt" FOR APPEND AS #1
                 PRINT #1, "1000 - " '+ str$(CB.WPARAM) + " - " + str$(CB.LPARAM)
               CLOSE #1
          
            END SELECT
          
          
            FUNCTION = callwindowproc(oldproc, CB.HNDL, CB.MSG, CB.WPARAM, CB.LPARAM)
          
          
          END FUNCTION

          Comment


          • #6
            I only use PBWIN for services and have no problems - Indeed Knuth is correct, you cannot interact with a UI in a service.
            There are many recent working examples if you search the forum.

            Your thread 2016
            https://forum.powerbasic.com/forum/u...ervice-program

            Comment


            • #7
              I think my interest at the time was a web server. It would run as a service and need STDOUT and STDIN to work with CGI apps.
              Dale

              Comment


              • #8
                The service could trigger a task. The task can start a GUI app.
                I have not tried it yet. But it would have to work that way.

                Comment


                • #9
                  David,

                  Which messages do you want to access in the Message Loop / Callback?

                  You can have a message loop without needing to create a window (even a hidden / message-only one).
                  You can examine and react to messages within the message loop itself without dispatching them to a windows procedure.

                  As mentioned above, a service can't normally interact with the user directly, to present dialog boxes, ask for key or mouse inputs etc.

                  I think that you have used SetTimer and WM_Timer within the Main Proc in your sample code just for tests. You probably know SetTimer can be used with it's own callback procedure.
                  Rgds, Dave

                  Comment


                  • #10
                    Dave,
                    Do you have any examples of "You can have a message loop without needing to create a window (even a hidden / message-only one)." ?
                    I think that is what I am looking for.
                    Thanks!

                    Comment


                    • #11
                      David, maybe if you are bit more specific as to what you want to achieve, you could be better helped?

                      Comment


                      • #12
                        David,

                        Here's a quick example.
                        Code:
                        #DIM ALL
                        #COMPILE EXE
                        #INCLUDE "WIN32API.INC"
                        DECLARE FUNCTION PostThreadMessage LIB "USER32.DLL" ALIAS "PostThreadMessageA" _                          ' ^ '
                                (BYVAL idThread AS DWORD, BYVAL uMsg AS DWORD, BYVAL wParam AS DWORD, BYVAL lParam AS LONG) AS LONG
                        
                        FUNCTION PBMAIN()
                         STATIC TimerID AS DWORD
                         LOCAL Msg      AS TAGMSG
                            ' Start the timer
                            TimerID = SetTimer(%NULL, %NULL, 1000, CODEPTR(TimerProc))  ' Post %WM_TIMER msg to TimerProc every 1000 msec
                                                                                        ' Loop passes msg on to TimerProc until..
                            WHILE GetMessage(Msg, %NULL, %NULL, %NULL) <> 0             ' %WM_QUIT causes  GetMessage to return '0' ~ end loop
                              DispatchMessage Msg                                       ' DispatchMessage recognises %WM_TIMER msg and
                            WEND                                                        ' passes on to TimerProc.
                        
                        
                          KillTimer %NULL, TimerID                                        ' Kill the timer
                          WinBeep 200,500
                        END FUNCTION
                        '------------------/TimerThread
                        
                        SUB TimerProc(BYVAL hWnd AS Dword, BYVAL uMsg AS Long, BYVAL TimerID AS Long, BYVAL dwTime AS DWORD)
                          STATIC TimerCount AS LONG
                          SELECT CASE uMsg
                            CASE %WM_TIMER                                                ' Timer has 'fired'
                              INCR TimerCount
                              OPEN Exe.Path$ + "Log3.txt" FOR APPEND AS #1
                                PRINT #1, "Count " TimerCount
                              CLOSE #1
                              WinBeep 500,100
                              IF TimerCount = 4 THEN                                      ' Bail out after n intervals
                                PostThreadMessageA (GetCurrentThreadID, %WM_QUIT, 0, 0)   ' Signal Quit to Msg Loop
                              END IF
                          END SELECT
                        END SUB
                        '------------------/TimerProc
                        Rgds, Dave

                        Comment

                        Working...
                        X