Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations wOOdy-Soft on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

VB program as a service 2

Status
Not open for further replies.

GhostWolf

Programmer
Jun 27, 2003
290
US
We have a VB program running as a service, via SRVANY, on an NT box - but now need to move it to an XP box. The company that created this setup for us is no longer in business, so I've got to learn how this works, quickly.

In my searches I've found some documentation for SRVANY, but I've also found NTSVC.OCX. The documentation I've found for NTSVC leads me to believe that it'll work with XP, but I haven't found anything definite on SRVANY.

That's where I need some advice. Is one of these better suited than the other? Are there "gotchas" I need to watch out for under XP?

(The program monitors an LPD port, traps the report received on the port and sends it to a text file for manipulation later. Fortunately, I have the source code, but this is my first foray into services and sockets.)
 
NTSVC.OCX works on XP, 2003, XP SP2, and 2003 R2. Note that these are actually 4 different OSs. I haven't tested on Vista or 2008 yet but I expect few problems.

To use NTSVC.OCX you actually have some "plumbing" to wire up. You get a cleaner service that way, but SRVANY.EXE as a wrapper process should still work fine within its limitations. We haven't used SRVANY in years here however.
 
Any tips/pointers on the "plumbing" is more than welcome - begged, even.
 
Have you tried googling to ntsvc.ocx? I would start there.
 
That's how I found what I have, (an article by John Andre, from
While it mentions nice little things like threads, dependencies, exe location, etc, it makes me wonder whether there are other eccentricities I need to know about before I try to turn this thing loose. Having never done anything like this before, I'm quickly learning that there's a lot I don't know.
 
John's article linked above is about as detailed an explanation as I think you'll find. There is an MS KB article too, but it does little besides refer you to an article no longer available. However An OLE Control for Creating Win32 Services in Visual Basic may be the same article in an earlier form.

You might also want to look at and and which are all linked from old KB articles on the topic of creating a Windows (formerly known as NT) Service in VB6.
 
>an article no longer available

An article that I had a copy of and managed to stupidly delete ... but then I didn't expect Microsoft to decide spot the dichotomy of explaining how dangerous, silly and impractible creating a VB service was whilst at the same time showing precisely how to do it ...
 
I've begun archiving a lot of things here myself lately. Now I'm hearing the VB6 stuff will be pulled from MSDN Online later this year too.
 
No, it was a full code example of an NT service written in pure VB (well, OK, with some API calls)
 
No, it isn't. It was knowledgebase article Q175948 (which your link acknowledges as a code source)

 
And I've managed to find an early, unabridged version of the article:
Code:
INFO: Running Visual Basic Applications as Windows NT Services

Last reviewed: October 30, 1997
Article ID: Q175948 The information in this article applies to: 

•Microsoft Visual Basic Learning, Professional, and Enterprise Editions for Windows, version 5.0 •Microsoft Visual Basic Standard, Professional, and Enterprise Editions for Windows, version 4.0 







SUMMARY

Microsoft does not currently recommend running Visual Basic applications as Microsoft Windows NT Services because the applications may exhibit unstable behavior when installed and run as Microsoft Windows NT Services. Microsoft Visual Basic 4.0 does not support Callbacks nor is it thread-safe. While Visual Basic 5.0 is apartment-model thread-safe, there is no way to marshal calls back into a Visual Basic 5.0 program through the AddressOf operator. This behavior is by design. This article includes some examples of this unstable behavior, a short explanation of the possible causes of the instability, and a possible workaround. 





MORE INFORMATION





Three Examples of Unstable Behavior



1.When a Microsoft Visual Basic application displays a dialog and then terminates, the name of the .EXE file disappears from the Services applet dialog, but the Visual Basic application dialog leaves behind a gray rectangle on the user interface. 

2.When the Microsoft Visual Basic application is launched from a service, the .EXE file name briefly appears in the Services applet dialog but the Visual Basic application does not seem to launch. 

3.Microsoft Visual Basic applications that are OLE Servers will return read-only properties successfully and, when the OLE Server terminates, the .EXE file name disappears from the Services applet dialog. Yet, subsequent attempts to instantiate that OLE Server fail with OLE automation errors or the Services process will hang. 





Why This Unstable Behavior Occurs

A service in Microsoft Windows NT is a program that is written as a console application and runs whenever the operating system is running. Services are commonly employed to provide facilities such as directory replication, system activity auditing, process monitoring, or communications support. 

Services log on to Microsoft Windows NT under the LocalSystem account, which is a predefined local account used by system processes. 

A service that runs in the context of the LocalSystem account inherits these characteristics: 

•The service cannot open the registry key HKEY_CURRENT_USER. •The service can open the registry key HKEY_LOCAL_MACHINE\SECURITY. •The service has limited access to network resources, such as shares and pipes, because it has no credentials and must connect using a null session. •If the service opens a command window and runs a batch file, the user could hit CTRL+C to terminate the batch file and gain access to a command window with LocalSystem permissions. •The service cannot share objects with other applications unless they are opened using a DACL, which allows a user or group of users access, or NULL DACL, which allows everyone access. Specifying a NULL DACL is not the same as specifying NULL, which means that access is only granted to applications with the same security context. •Service programs that require anything more than a brief moment to perform necessary initialization may be required to create another thread to do the common initialization while the main thread continues with processing. 



By convention, Microsoft Windows NT[ASCII 153] Services are designed to be non-interactive. That is, in most cases, they do their work without any user interaction. Services are usually configured and controlled with an applet installed in the Control Panel. This requires that the programmer add code to provide for these additional requirements: 

•Report warnings and errors in the system or application logs. You typically can't use output to the screen because there may be no user logged on. •Control the service through a separate application or through a control Panel applet. This involves implementing a communication mechanism for your service. •Install and remove the service from the system. 



However, with Microsoft Visual Basic 4.0, the possibility remains that your application will eventually post an unhandled error dialog from the Services process which will hang the Microsoft Visual Basic application. 



The Unattended EXE compile option available with Microsoft Visual Basic 5.0 does address the unexpected dialog problem, but thread safety remains an issue. Visual Basic 5.0 is apartment-model thread-safe unless or until a program uses the AddressOf operator. There is currently no way for Visual Basic to marshal multiple calls back into a Visual Basic 5.0 application through AddressOf. Therefore, it is not currently recommended that you install a Microsoft Visual Basic application as a Microsoft Windows NT Service. 





Workaround

The Microsoft Technical Article, "NT Service: An OLE Control for Creating Windows NT Services in Visual Basic," describes an OLE Control that enables developers to create Visual Basic applications that function as Microsoft Windows NT services. With the NTService control, you can install a service, log events, and respond to start, stop, pause, and continue events. 





Other Technologies

Developers can expect difficulties with efforts to employ Microsoft technologies such as MAPI, ODBC, DCOM, OLE Automation, and DAO in a Microsoft Windows NT Service written in Microsoft Visual Basic. For instance, MAPI contains user interface elements that must be anticipated and suppressed, and may require access to parts of the registry that are either unavailable to a service or require a special security context for use. 

For this reason, and those already noted, Microsoft advises developers to avoid using these technologies in a Microsoft Windows NT Service written in Microsoft Visual Basic. 





Microsoft Visual Basic 5.0

While it is possible to write a Microsoft Windows NT service as an unattended .EXE in Microsoft Visual Basic 5.0 (as the following code sample demonstrates), it is still not advisable to implement services because Microsoft Visual Basic 5.0 is not thread-safe and the possibility still remains that an unexpected dialog may post from within the Visual Basic run- time environment. 

This example provides for installing and uninstalling itself as a service by running the .EXE with a command line parameter as shown here: 



   MyService.exe install
   MyService.exe uninstall




After installing the service, you will need to configure it using the Control Panel Services applet. From the applet, you can start, stop, and pause the service. You can also set it up to start automatically during boot-up. 

Add the following code to a .BAS module: 



   Option Explicit

   Private Const SERVICE_WIN32_OWN_PROCESS = &H10&
   Private Const SERVICE_WIN32_SHARE_PROCESS = &H20&
   Private Const SERVICE_WIN32 = SERVICE_WIN32_OWN_PROCESS + _
                                 SERVICE_WIN32_SHARE_PROCESS

   Private Const SERVICE_ACCEPT_STOP = &H1
   Private Const SERVICE_ACCEPT_PAUSE_CONTINUE = &H2
   Private Const SERVICE_ACCEPT_SHUTDOWN = &H4

   Private Const SC_MANAGER_CONNECT = &H1
   Private Const SC_MANAGER_CREATE_SERVICE = &H2
   Private Const SC_MANAGER_ENUMERATE_SERVICE = &H4
   Private Const SC_MANAGER_LOCK = &H8
   Private Const SC_MANAGER_QUERY_LOCK_STATUS = &H10
   Private Const SC_MANAGER_MODIFY_BOOT_CONFIG = &H20

   Public Const STANDARD_RIGHTS_REQUIRED = &HF0000
   Private Const SERVICE_QUERY_CONFIG = &H1
   Private Const SERVICE_CHANGE_CONFIG = &H2
   Private Const SERVICE_QUERY_STATUS = &H4
   Private Const SERVICE_ENUMERATE_DEPENDENTS = &H8
   Private Const SERVICE_START = &H10
   Private Const SERVICE_STOP = &H20
   Private Const SERVICE_PAUSE_CONTINUE = &H40
   Private Const SERVICE_INTERROGATE = &H80
   Private Const SERVICE_USER_DEFINED_CONTROL = &H100
   Private Const SERVICE_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED Or _
                                       SERVICE_QUERY_CONFIG Or _
                                       SERVICE_CHANGE_CONFIG Or _
                                       SERVICE_QUERY_STATUS Or _
                                       SERVICE_ENUMERATE_DEPENDENTS Or _
                                       SERVICE_START Or _
                                       SERVICE_STOP Or _
                                       SERVICE_PAUSE_CONTINUE Or _
                                       SERVICE_INTERROGATE Or _
                                       SERVICE_USER_DEFINED_CONTROL)

   Private Const SERVICE_DEMAND_START As Long = &H3

   Private Const SERVICE_ERROR_NORMAL As Long = &H1

   Private Enum SERVICE_CONTROL
      SERVICE_CONTROL_STOP = &H1
      SERVICE_CONTROL_PAUSE = &H2
      SERVICE_CONTROL_CONTINUE = &H3
      SERVICE_CONTROL_INTERROGATE = &H4
      SERVICE_CONTROL_SHUTDOWN = &H5
   End Enum

   Private Enum SERVICE_STATE
      SERVICE_STOPPED = &H1
      SERVICE_START_PENDING = &H2
      SERVICE_STOP_PENDING = &H3
      SERVICE_RUNNING = &H4
      SERVICE_CONTINUE_PENDING = &H5
      SERVICE_PAUSE_PENDING = &H6
      SERVICE_PAUSED = &H7
   End Enum

   Private Type SERVICE_TABLE_ENTRY
      lpServiceName As String
      lpServiceProc As Long
      lpServiceNameNull As Long
      lpServiceProcNull As Long
   End Type

   Private Type SERVICE_STATUS
      dwServiceType As Long
      dwCurrentState As Long
      dwControlsAccepted As Long
      dwWin32ExitCode As Long
      dwServiceSpecificExitCode As Long
      dwCheckPoint As Long
      dwWaitHint As Long
   End Type

   Private Declare Function StartServiceCtrlDispatcher _
      Lib "advapi32.dll" Alias "StartServiceCtrlDispatcherA" _
      (lpServiceStartTable As SERVICE_TABLE_ENTRY) As Long
   Private Declare Function RegisterServiceCtrlHandler _
      Lib "advapi32.dll" Alias "RegisterServiceCtrlHandlerA" _
      (ByVal lpServiceName As String, ByVal lpHandlerProc As Long) _
      As Long
   Private Declare Function SetServiceStatus _
      Lib "advapi32.dll" (ByVal hServiceStatus As Long, _
      lpServiceStatus As SERVICE_STATUS) As Long
   Private Declare Function OpenSCManager _
      Lib "advapi32.dll" Alias "OpenSCManagerA" _
      (ByVal lpMachineName As String, ByVal lpDatabaseName As String, _
      ByVal dwDesiredAccess As Long) As Long
   Private Declare Function CreateService _
      Lib "advapi32.dll" Alias "CreateServiceA" _
      (ByVal hSCManager As Long, ByVal lpServiceName As String, _
      ByVal lpDisplayName As String, ByVal dwDesiredAccess As Long, _
      ByVal dwServiceType As Long, ByVal dwStartType As Long, _
      ByVal dwErrorControl As Long, ByVal lpBinaryPathName As String, _
      ByVal lpLoadOrderGroup As String, ByVal lpdwTagId As String, _
      ByVal lpDependencies As String, ByVal lp As String, _
      ByVal lpPassword As String) As Long
   Private Declare Function DeleteService _
      Lib "advapi32.dll" (ByVal hService As Long) As Long
   Declare Function CloseServiceHandle _
      Lib "advapi32.dll" (ByVal hSCObject As Long) As Long
   Declare Function OpenService _
      Lib "advapi32.dll" Alias "OpenServiceA" _
      (ByVal hSCManager As Long, ByVal lpServiceName As String, _
      ByVal dwDesiredAccess As Long) As Long

   '** Change SERVICE_NAME as needed
   Private Const SERVICE_NAME As String = "MyService"

   Private hServiceStatus As Long
   Private ServiceStatus As SERVICE_STATUS

   Sub Main()
      Dim hSCManager As Long
      Dim hService As Long
      Dim ServiceTableEntry As SERVICE_TABLE_ENTRY
      Dim b As Boolean
      Dim cmd As String

      cmd = Trim(LCase(Command()))
      Select Case cmd
         Case "install"                      'Install service on machine
            hSCManager = OpenSCManager(vbNullString, vbNullString, _
                         SC_MANAGER_CREATE_SERVICE)
            hService = CreateService(hSCManager, SERVICE_NAME, _
                       SERVICE_NAME, SERVICE_ALL_ACCESS, _
                       SERVICE_WIN32_OWN_PROCESS, _
                       SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, _
                       App.Path & "\" & App.EXEName, vbNullString, _
                       vbNullString, vbNullString, vbNullString, _
                       vbNullString)
            CloseServiceHandle hService
            CloseServiceHandle hSCManager
         Case "uninstall"                   'Remove service from machine
            hSCManager = OpenSCManager(vbNullString, vbNullString, _
                         SC_MANAGER_CREATE_SERVICE)
            hService = OpenService(hSCManager, SERVICE_NAME, _
                       SERVICE_ALL_ACCESS)
            DeleteService hService
            CloseServiceHandle hService
            CloseServiceHandle hSCManager
         Case Else                                    'Start the service
            ServiceTableEntry.lpServiceName = SERVICE_NAME
            ServiceTableEntry.lpServiceProc = _
                                           FncPtr(AddressOf ServiceMain)
            b = StartServiceCtrlDispatcher(ServiceTableEntry)
      End Select
   End Sub

   Sub ServiceMain(ByVal dwArgc As Long, ByVal lpszArgv As Long)
      Dim b As Boolean

      'Set initial state
      ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS
      ServiceStatus.dwCurrentState = SERVICE_START_PENDING
      ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP _
                                      Or SERVICE_ACCEPT_PAUSE_CONTINUE _
                                      Or SERVICE_ACCEPT_SHUTDOWN
      ServiceStatus.dwWin32ExitCode = 0
      ServiceStatus.dwServiceSpecificExitCode = 0
      ServiceStatus.dwCheckPoint = 0
      ServiceStatus.dwWaitHint = 0

      hServiceStatus = RegisterServiceCtrlHandler(SERVICE_NAME, _
                       AddressOf Handler)
      ServiceStatus.dwCurrentState = SERVICE_START_PENDING
      b = SetServiceStatus(hServiceStatus, ServiceStatus)

      '** Do Initialization Here

      ServiceStatus.dwCurrentState = SERVICE_RUNNING
      b = SetServiceStatus(hServiceStatus, ServiceStatus)

      '** Perform tasks -- if none exit

      ''** If an error occurs the following should be used for shutting
      ''** down:
      ''   SetServerStatus SERVICE_STOP_PENDING
      ''   Clean up
      ''   SetServerStatus SERVICE_STOPPED
   End Sub

   Sub Handler(ByVal fdwControl As Long)
      Dim b As Boolean

      Select Case fdwControl
         Case SERVICE_CONTROL_PAUSE
            '** Do whatever it takes to pause here.
            ServiceStatus.dwCurrentState = SERVICE_PAUSED
         Case SERVICE_CONTROL_CONTINUE
            '** Do whatever it takes to continue here.
            ServiceStatus.dwCurrentState = SERVICE_RUNNING
         Case SERVICE_CONTROL_STOP
            ServiceStatus.dwWin32ExitCode = 0
            ServiceStatus.dwCurrentState = SERVICE_STOP_PENDING
            ServiceStatus.dwCheckPoint = 0
            ServiceStatus.dwWaitHint = 0     'Might want a time estimate
            b = SetServiceStatus(hServiceStatus, ServiceStatus)
            '** Do whatever it takes to stop here.
            ServiceStatus.dwCurrentState = SERVICE_STOPPED
         Case SERVICE_CONTROL_INTERROGATE
            'Fall through to send current status.
         Case Else
      End Select
      'Send current status.
      b = SetServiceStatus(hServiceStatus, ServiceStatus)
   End Sub

   Function FncPtr(ByVal fnp As Long) As Long
      FncPtr = fnp
   End Function






REFERENCES

For more information, see the following directory on MSDN: 



   \Platform SDK\Windows Base Services\Executables\Services




For more detailed information on "Using MAPI from a Windows NT Service", refer to: 



   [URL unfurl="true"]http://www.microsoft.com/win32dev/mapi/mapiserv.htm[/URL]




For additional information, please see the following article in the Microsoft Knowledge Base: 



   ARTICLE-ID: Q137890
   TITLE     : HOWTO: Create A User-Defined Service



Keywords          : VB4ALL VB4WIN vb5all VBKBEnv VBKBProgramming
Version           : WINDOWS:4.0,5.0
Platform          : WINDOWS
Issue type        : kbinfo
 
Wasn't that the old version for VB5 only though? I thought that technique was broken when VB6 changed the way threading works.
 
Yeah, I no sooner hit <Enter> than I got notice that you'd posted!

I found the source yesterday for a program named vb6svc, (which uses that code as its base), and ran it in debug just to see how it works. Basically, it just allows you to install the service, start it, stop it, and uninstall it.

It uses a timer to check status, so it got me to wondering whether I could use a single program to install/start the service, monitor/process LPD traffic, and uninstall/stop if a Control_Stop or Control_ShutDown is received.
 
Compile VB6 as p-code and it is about as thread-safe as VB5 was. AT least in my experience.
 
If I hang around you guys long enough, I just might learn somethin'.
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top