INFO: Running Visual Basic Applications as Windows NT ServicesLast reviewed: October 30, 1997Article ID: Q175948 |
The information in this article applies to:
SUMMARYMicrosoft 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
Why This Unstable Behavior OccursA 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 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.
WorkaroundThe 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 TechnologiesDevelopers 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.0While 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 uninstallAfter 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 REFERENCESFor more information, see the following directory on MSDN:
\Platform SDK\Windows Base Services\Executables\ServicesFor more detailed information on "Using MAPI from a Windows NT Service", refer to:
http://www.microsoft.com/win32dev/mapi/mapiserv.htmFor 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 |
================================================================================
© 1998 Microsoft Corporation. All rights reserved. Terms of Use. |