As I mentioned in my last article on Developing Windows Services, debugging Windows services is not as trivial as debugging Windows applications because they do not run directly within Visual Studio .NET. Hence, more elaborate techniques must be deployed to effectively develop and debug Windows services. In this article, I will discuss two techniques that you can use to debug and test your Windows services.
The first method to debug a Windows service is to attach a debugger to a running service. You can then set breakpoints to cause the Windows service to suspend, giving you the ability to step through the code. Let's see how we can do this using the sample TimeService Windows service illustrated in my last article.
First, you need to install your Windows service. (You can refer to my last article for the steps to install a Windows service.) After installation, start the service either using the Services manager (located in Control Panel, Administrative Tools), or the command line.
In Visual Studio .NET 2003, set a breakpoint at the statement that you wish to step through (see Figure 1).
|
| Figure 1: Setting a breakpoint in Visual Studio .NET 2003 |
Now, select the menu item Debug->Processes...(see Figure 2) to invoke the Processes window (see Figure 3).
|
| Figure 2: Invoking the Processes window |
In the Available Processes group (see Figure 3), locate the service that you want to debug (TimeService.exe in my case); select it and click the Attach... button.
|
| Figure 3: Attaching the debugger to a process in the Processes window |
The Attach to Process window will appear. Click OK and then the Close button on the Processes window. You are now in debugging mode.
Because the listener.AcceptTcpClient() method (which I used in my Windows service)
is a blocking call, my Windows service will only break for debugging
when a client sends an incoming message. So, execute your client (use
the sample console application from my last article), and send a message
to the service so that we can proceed with the debugging.
Assuming that you've received an incoming message, the Windows service should now be pending at the breakpoint (see Figure 4).
|
| Figure 4: Visual Studio .NET displaying the paused breakpoint |
You can now perform the usual debugging procedures, such as running QuickWatch, Step Into, Step Out, and Step Over operations.
You can also set breakpoints at the OnStop(), OnPause(), and OnContinue()
methods and observe what happens when the service stops, pauses,
and continues, respectively.
|
Related Reading
Programming .NET Web Services |
The main problem with using the debugger in Visual Studio .NET 2003
is that you can't debug the OnStart() method; neither can you debug
the Main method. This is because you can only attach a debugger to a
service after it has started running, which by then, the OnStart() method
has already completed its execution and returned the control to the
operating system. There are workarounds to debugging the OnStart() method
(as documented in MSDN), but even if you could debug the OnStart() method,
you have only 30 seconds to step through the code in the method as the
Windows Service Manager imposes a 30-second limit on all attempts to
start a service.
My suggestion would be to minimize the number of statements in the OnStart()
method and move the bulk of the work to a subroutine or a function. By
doing so, you can then set your breakpoints in the subroutine and not
in the OnStart() method.
It's a tedious task to debug a Windows service after it has started running, so I recommend the second method, as described in the next section.
In this second method, a better way to debug Windows services is to test the logic of the Windows service independently, preferably as a console or Windows application. The idea is to contain the service logic as an independent unit, ideally in a DLL.
In this section, I will create a class (project name TimeServiceClass) to store the application logic of the TimeService Windows service.
Here is the full source for the class:
Imports System.Net.Sockets
Imports System.Net
Imports System.Text
Public Class TimeService
Private canStopListening As Boolean = False
Private pause As Boolean = False
Public Sub startService()
Listen()
End Sub
Public Sub pauseService()
pause = True
End Sub
Public Sub continueService()
pause = False
End Sub
Public Sub stopService()
canStopListening = True
End Sub
Private Sub Listen()
Const portNo As Integer = 500
Dim localAdd As System.Net.IPAddress = _
IPAddress.Parse("127.0.0.1")
Dim listener As New TcpListener(localAdd, portNo)
listener.Start()
Do
If Not pause Then
Dim tcpClient As TcpClient = listener.AcceptTcpClient()
Dim NWStream As NetworkStream = tcpClient.GetStream
Dim bytesToRead(tcpClient.ReceiveBufferSize) As Byte
'---read incoming stream
Dim numBytesRead As Integer = _
NWStream.Read(bytesToRead, 0, _
CInt(tcpClient.ReceiveBufferSize))
'---write to console
Console.WriteLine("Received :" & _
Encoding.ASCII.GetString(bytesToRead, _
0, numBytesRead) & " @ " & Now)
'---write time back to client
Dim time() As Byte = _
Encoding.ASCII.GetBytes(Now.ToString)
NWStream.Write(time, 0, time.Length)
tcpClient.Close()
End If
Loop Until canStopListening = True
listener.Stop()
End Sub
End Class
Basically, I have encapsulated the logic of the TimeService within a class and exposed the various methods to start, stop, pause, and continue the time service. The class is then compiled into a DLL, which I can test by creating a Windows or console application.
|
Now, create a new Windows application to test the DLL. In my Windows application, I need to add a reference to the DLL created above (see Figure 5).
|
| Figure 5: Creating a reference to the TimeService class |
Once it is done, add three buttons to the Windows form to Stop, Pause, and Continue the service.
I have the following global variables:
Dim timeservice As New TimeServiceClass.TimeService
Public Delegate Sub ServiceDelegate()
Note that I have created a delegate called ServiceDelegate() so that
I can invoke the timeservice object asynchronously in my form load event:
Private Sub Form1_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles MyBase.Load
Dim async As New ServiceDelegate(AddressOf StartService)
async.BeginInvoke(Nothing, Nothing)
End Sub
The StartService() method simply starts the service by invoking the
StartService() method of the timeservice object:
Public Sub StartService()
timeservice.startService()
End Sub
Likewise, I do the same for the Stop, Pause, and Continue buttons:
Private Sub Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles Button1.Click
' Stop the service
timeservice.stopService()
End Sub
Private Sub Button2_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles Button2.Click
' Pause the service
timeservice.pauseService()
End Sub
Private Sub Button3_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles Button3.Click
' Continue the service
timeservice.continueService()
End Sub
You can now test your time service by running the Windows application, checking to see if you can stop, pause, and continue, your service.
Once you are sure that the service logic is working correctly, you can
use the service class in your actual Windows service. Here is the
code for the Windows service that uses the TimeService class (remember
to add a reference to the class):
Imports System.ServiceProcess
Public Class Service1
Inherits System.ServiceProcess.ServiceBase
Dim timeservice As New TimeServiceClass.TimeService
Public Delegate Sub ServiceDelegate()
Dim log As New System.Diagnostics.EventLog
'---------------------------------------------------------------
Protected Overrides Sub OnStart(ByVal args() As String)
Dim async As New ServiceDelegate(AddressOf StartService)
async.BeginInvoke(Nothing, Nothing)
Me.AutoLog = False
If Not EventLog.SourceExists("Time Service Log") Then
EventLog.CreateEventSource("Time Service Log", "MyLogFile")
End If
log.Source = "Time Service Log"
log.WriteEntry("Service Started")
End Sub
'---------------------------------------------------------------
Protected Sub StartService()
timeservice.startService()
End Sub
'---------------------------------------------------------------
Protected Overrides Sub OnStop()
timeservice.stopService()
log.WriteEntry("Service Stopped")
End Sub
'---------------------------------------------------------------
Protected Overrides Sub OnPause()
timeservice.pauseService()
log.WriteEntry("Service Paused")
End Sub
'---------------------------------------------------------------
Protected Overrides Sub OnContinue()
timeservice.continueService()
log.WriteEntry("Service Continued")
End Sub
End Class
You can now build the Windows service, install it, start it, and it should work correctly!
Encapsulating Windows services logic within a class allows you to debug the service easily, without the hassles of attaching a debugger to a running service. It is the recommended way to build Windows services. Using a combination of the techniques--attaching a debugger, encapsulating the service logic, and using the event log--will make life as a Windows service developer much easier and more productive.
Wei-Meng Lee (Microsoft MVP) http://weimenglee.blogspot.com is a technologist and founder of Developer Learning Solutions http://www.developerlearningsolutions.com, a technology company specializing in hands-on training on the latest Microsoft technologies.
Return to ONDotnet.com
Copyright © 2009 O'Reilly Media, Inc.