﻿# 如何對 [Windows 服務] 與 [安裝專案的自訂動作] 進行除錯

雖然 MSDN 的 [HOW TO：偵錯 Windows 服務應用程式](http://msdn.microsoft.com/zh-tw/library/7a50syb3\(VS.80\).aspx) 文件有教如何除錯，但是這樣的除錯有個大缺點，就是有時後程式碼是在 OnStart 事件一開始的時候就發生問題的，有時後就會因為來不及 **附加** (**Attach**) 程序導致無法除錯。另外，對於「安裝專案」內的「自訂動作」由於是在專案的 Installer 類別中執行的，所以要對「安裝專案」進行除錯也不太容易。我今天就來分享幾個很簡單又有效的除錯密技。

**方法一：透過** [**System.Diagnostics.Debugger.Launch()**](http://msdn.microsoft.com/zh-tw/library/system.diagnostics.debugger.launch.aspx) **方法**

這個方式我個人是比較喜歡，因為在開發時期要加 Code 上去很容易，你只要在想要除錯的程式碼之前加上以下程式片段，就可以要求程式暫停，並讓你選用 Visual Studio 進行除錯！

```
System.Diagnostics.Debugger.Launch();
```

當執行程式到這行時，就會出現以下視窗：

![Visual Studio Just-In-Time Debugger](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/2716e63423f75fb3ddbb-image_3.png "Visual Studio Just-In-Time Debugger")

相對的要對「安裝專案」中的自訂動作除錯，也可以使用這個技巧，方便又有效！

在啟動 Visual Studio 的 Debugger 之後，你也可以利用 [Debugger.Break()](http://msdn.microsoft.com/zh-tw/library/system.diagnostics.debugger.break.aspx) 方法自訂中斷點，你可以在希望監看的程式碼片段之前加上這個方法，或是透過一些條件判斷讓 Visual Studio Debugger 執行到這行時自動暫停讓你可以看看當下的物件狀況。

**方法二：在 OnStart 事件開始前先睡個 20 秒**

來不及附加程序的問題要解決也很簡單，「睡」一下就好了，讓程式在睡覺的時候趕緊利用 Visual Studio 附加該服務的程序。

```
System.Threading.Thread.Sleep(1000 * 20);
```

**方法三：把 Windows Service 當成 Console Application 來執行**

大家應該知道 Windows Service 是沒辦法直接按下 F5 啟動程式的，所以一般的處理方式都是 Windows Service 專案搭配一個 Windows Setup 專案進行安裝，安裝後再啟動，然後再想辦法進行除錯。

但事實上，透過一點點小修改也可以讓 Windows Service 變成 Console Application 來執行，這樣一來你就可以用 F5 啟動程式並直接進入偵錯模式了！

要達成這個目標，有 2 件事必須瞭解，瞭解後就知道我在寫什麼了。

1\. 由於 Windows Service 與 Console Appliation 的程式進入點都一樣是 static void Main()，預設的 Main() 內容如下：

```csharp
static void Main()
{
	ServiceBase[] ServicesToRun;
	ServicesToRun = new ServiceBase[] 
	{ 
		new Service1() 
	};
	ServiceBase.Run(ServicesToRun);
}
```

2\. 在 Windows Service 專案中的啟動點是在 OnStart 方法，但這個方法是屬於 protected 存取層級，無法對外公開使用。

\---

接下來，就要正式修改程式了，共有 3 個步驟：

**1\. 先修改 Service1.cs 程式 ( Windows Service 專案中的主要服務類別 )**

    由於 OnStart 與 OnStop 方法被 protected 宣告保護著，無法讓**使用此類別的程式**直接呼叫，此時必須多寫兩個方法(method)讓**使用此類別的程式**可以呼叫 [OnStart()](http://msdn.microsoft.com/en-us/library/system.serviceprocess.servicebase.onstart.aspx) 方法。

```csharp
public void Start(string[] args)
{
    this.OnStart(args);
}

public void Stop()
{
    this.OnStop();
}
```

**2\. 接著修改 Program.cs 中的 Main() 方法 ( 也就是整個 Windows Service 專案的程式啟動點 )**

    透過 [Environment.UserInteractive](http://msdn.microsoft.com/zh-tw/library/system.environment.userinteractive.aspx) 屬性即可得知該組件是執行於使用者互動模式中，若該組件是以 Windows Service 執行時，則此屬性將會回傳 false，我將原本的 Main() 程式改成以下程式碼：

```csharp
static void Main()
{
	if (Environment.UserInteractive)
	{
		Service1 s = new Service1();

		s.Start(null);

		Console.WriteLine("服務已啟動，請按下 Enter 鍵關閉服務...");
		// 必須要透過 Console.ReadLine(); 先停止程式執行
		// 因為 Windows Service 大多是利用多 Thread 或 Timer 執行長時間的工作
		// 所以雖然主執行緒停止執行了，但服務中的執行緒已經在運行了!
		Console.ReadLine();

		s.Stop();

		Console.WriteLine("服務已關閉");
	}
	else
	{
		ServiceBase[] ServicesToRun;
		ServicesToRun = new ServiceBase[] 
		{ 
			new Service1() 
		};
		ServiceBase.Run(ServicesToRun);
	}
}
```

**3\. 變更 Windows Service 專案的 輸出類型 ( Output Type )**

    開啟 Windows Service 專案屬性(Properties)視窗，將原本的 Windows Application 切換到 Console Application即可。

![Project Properties - Output Type: Console Application](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/0ae62f715a8939b33110-image_6.png "Project Properties - Output Type: Console Application")

\---

如此一來，你就可以讓同一個組件同時可以用來當 Windows Service 的主程式，以及可以直接當成 Console 程式來執行，非常的方便。

**相關連結**

-   [HOW TO：偵錯 Windows 服務應用程式](http://msdn.microsoft.com/zh-tw/library/7a50syb3\(VS.80\).aspx)
-   [Five ways debugging .NET Windows service](http://blogs.msdn.com/yaleeyangmsblog/archive/2007/05/02/three-ways-debugging-net-windows-service.aspx)
-   [How To Debug Windows Service OnStart](http://blogs.msdn.com/field_notes/archive/2008/07/21/how-to-debug-windows-service-onstart.aspx)
-   [Debugger.Launch 方法](http://msdn.microsoft.com/zh-tw/library/system.diagnostics.debugger.launch.aspx)
-   [Debugger.Break 方法](http://msdn.microsoft.com/zh-tw/library/system.diagnostics.debugger.break.aspx)
-   [ServiceBase.OnStart 方法](http://msdn.microsoft.com/en-us/library/system.serviceprocess.servicebase.onstart.aspx)
