是否可以在Visual Studio中调试Windows服务?
我用的代码就像
System.Diagnostics.Debugger.Break();
但它给出了一些代码错误,如:
我收到两个事件错误:eventID 4096 VsJITDebugger和“服务没有及时响应启动或控制请求。”
在服务OnStart
方法中使用以下代码:
System.Diagnostics.Debugger.Launch();
从弹出消息中选择Visual Studio选项。
注意:要仅在调试模式下使用它,可以使用#if DEBUG
编译器指令,如下所示。这将防止生产服务器上的发布模式中的意外或调试。
#if DEBUG
System.Diagnostics.Debugger.Launch();
#endif
我在Visual Studio项目中使用/Console
参数Debug→Start Options→命令行参数:
public static class Program
{
[STAThread]
public static void Main(string[] args)
{
var runMode = args.Contains(@"/Console")
? WindowsService.RunMode.Console
: WindowsService.RunMode.WindowsService;
new WinodwsService().Run(runMode);
}
}
public class WindowsService : ServiceBase
{
public enum RunMode
{
Console,
WindowsService
}
public void Run(RunMode runMode)
{
if (runMode.Equals(RunMode.Console))
{
this.StartService();
Console.WriteLine("Press <ENTER> to stop service...");
Console.ReadLine();
this.StopService();
Console.WriteLine("Press <ENTER> to exit.");
Console.ReadLine();
}
else if (runMode.Equals(RunMode.WindowsService))
{
ServiceBase.Run(new[] { this });
}
}
protected override void OnStart(string[] args)
{
StartService(args);
}
protected override void OnStop()
{
StopService();
}
/// <summary>
/// Logic to Start Service
/// Public accessibility for running as a console application in Visual Studio debugging experience
/// </summary>
public virtual void StartService(params string[] args){ ... }
/// <summary>
/// Logic to Stop Service
/// Public accessibility for running as a console application in Visual Studio debugging experience
/// </summary>
public virtual void StopService() {....}
}
不幸的是,如果您在Windows服务操作的最初阶段尝试调试某些内容,则“附加”到正在运行的进程将无法正常工作。我尝试在OnStart程序中使用Debugger.Break(),但是使用64位Visual Studio 2010编译的应用程序,break命令只会抛出这样的错误:
System error 1067 has occurred.
此时,您需要在注册表中为可执行文件设置“映像文件执行”选项。设置需要五分钟,而且效果非常好。这是一篇微软文章,详细信息如下:
尝试使用Visual Studio自己的构建后事件命令行。
尝试在post-build中添加:
@echo off
sc query "ServiceName" > nul
if errorlevel 1060 goto install
goto stop
:delete
echo delete
sc delete "ServiceName" > nul
echo %errorlevel%
goto install
:install
echo install
sc create "ServiceName" displayname= "Service Display Name" binpath= "$(TargetPath)" start= auto > nul
echo %errorlevel%
goto start
:start
echo start
sc start "ServiceName" > nul
echo %errorlevel%
goto end
:stop
echo stop
sc stop "ServiceName" > nul
echo %errorlevel%
goto delete
:end
如果构建错误的消息如Error 1 The command "@echo off sc query "ServiceName" > nul
等,则按Ctrl + C然后按Ctrl + V将错误消息放入记事本并查看消息的最后一句。
可能会说exited with code x
。在这里查找一些常见错误的代码,看看如何解决它。
1072 -- Marked for deletion → Close all applications that maybe using the service including services.msc and Windows event log.
1058 -- Can't be started because disabled or has no enabled associated devices → just delete it.
1060 -- Doesn't exist → just delete it.
1062 -- Has not been started → just delete it.
1053 -- Didn't respond to start or control → see event log (if logged to event log). It may be the service itself throwing an exception.
1056 -- Service is already running → stop the service, and then delete.
有关错误代码here的更多信息。
如果构建错误与这样的消息,
Error 11 Could not copy "obj\x86\Debug\ServiceName.exe" to "bin\Debug\ServiceName.exe". Exceeded retry count of 10. Failed. ServiceName
Error 12 Unable to copy file "obj\x86\Debug\ServiceName.exe" to "bin\Debug\ServiceName.exe". The process cannot access the file 'bin\Debug\ServiceName.exe' because it is being used by another process. ServiceName
打开cmd,然后尝试先用taskkill /fi "services eq ServiceName" /f
杀死它
如果一切顺利,F5应该足以调试它。
我使用了一个名为ServiceProcess.Helpers的优秀Nuget包。
我引用......
它通过在连接调试器的情况下创建播放/停止/暂停UI来帮助调试Windows服务,但也允许Windows服务器环境安装和运行服务。
所有这一切都有一行代码。
http://windowsservicehelper.codeplex.com/
安装完成后,您需要做的就是将Windows服务项目设置为启动项目,然后单击调试器上的start。
在OnStart
方法中,执行以下操作。
protected override void OnStart(string[] args)
{
try
{
RequestAdditionalTime(600000);
System.Diagnostics.Debugger.Launch(); // Put breakpoint here.
.... Your code
}
catch (Exception ex)
{
.... Your exception code
}
}
然后以管理员身份运行命令提示符并输入以下内容:
c:\> sc create test-xyzService binPath= <ProjectPath>\bin\debug\service.exe type= own start= demand
上面的代码行将在服务列表中创建test-xyzService。
要启动该服务,这将提示您附加到Visual Studio中的首次亮相。
c:\> sc start text-xyzService
要停止服务:
c:\> sc stop test-xyzService
要删除或卸载:
c:\> sc delete text-xyzService
我发现了这个问题,但我认为缺少一个简单明了的答案。
我不想将我的调试器附加到进程,但我仍然希望能够调用服务OnStart
和OnStop
方法。我还希望它作为控制台应用程序运行,以便我可以将信息从NLog记录到控制台。
我发现这些精彩的指南可以做到这一点:
首先将项目Output type
更改为Console Application
。
将您的Program.cs
更改为如下所示:
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
static void Main()
{
// Startup as service.
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new Service1()
};
if (Environment.UserInteractive)
{
RunInteractive(ServicesToRun);
}
else
{
ServiceBase.Run(ServicesToRun);
}
}
}
然后添加以下方法以允许以交互模式运行的服务。
static void RunInteractive(ServiceBase[] servicesToRun)
{
Console.WriteLine("Services running in interactive mode.");
Console.WriteLine();
MethodInfo onStartMethod = typeof(ServiceBase).GetMethod("OnStart",
BindingFlags.Instance | BindingFlags.NonPublic);
foreach (ServiceBase service in servicesToRun)
{
Console.Write("Starting {0}...", service.ServiceName);
onStartMethod.Invoke(service, new object[] { new string[] { } });
Console.Write("Started");
}
Console.WriteLine();
Console.WriteLine();
Console.WriteLine(
"Press any key to stop the services and end the process...");
Console.ReadKey();
Console.WriteLine();
MethodInfo onStopMethod = typeof(ServiceBase).GetMethod("OnStop",
BindingFlags.Instance | BindingFlags.NonPublic);
foreach (ServiceBase service in servicesToRun)
{
Console.Write("Stopping {0}...", service.ServiceName);
onStopMethod.Invoke(service, null);
Console.WriteLine("Stopped");
}
Console.WriteLine("All services stopped.");
// Keep the console alive for a second to allow the user to see the message.
Thread.Sleep(1000);
}
首先,您必须在VS解决方案中创建一个控制台项目(添加 - >新建项目 - >控制台应用程序)。
在新项目中,使用该代码创建一个“ConsoleHost”类:
class ConsoleHost : IDisposable
{
public static Uri BaseAddress = new Uri(http://localhost:8161/MyService/mex);
private ServiceHost host;
public void Start(Uri baseAddress)
{
if (host != null) return;
host = new ServiceHost(typeof(MyService), baseAddress ?? BaseAddress);
//binding
var binding = new BasicHttpBinding()
{
Name = "MyService",
MessageEncoding = WSMessageEncoding.Text,
TextEncoding = Encoding.UTF8,
MaxBufferPoolSize = 2147483647,
MaxBufferSize = 2147483647,
MaxReceivedMessageSize = 2147483647
};
host.Description.Endpoints.Clear();
host.AddServiceEndpoint(typeof(IMyService), binding, baseAddress ?? BaseAddress);
// Enable metadata publishing.
var smb = new ServiceMetadataBehavior
{
HttpGetEnabled = true,
MetadataExporter = { PolicyVersion = PolicyVersion.Policy15 },
};
host.Description.Behaviors.Add(smb);
var defaultBehaviour = host.Description.Behaviors.OfType<ServiceDebugBehavior>().FirstOrDefault();
if (defaultBehaviour != null)
{
defaultBehaviour.IncludeExceptionDetailInFaults = true;
}
host.Open();
}
public void Stop()
{
if (host == null)
return;
host.Close();
host = null;
}
public void Dispose()
{
this.Stop();
}
}
这是Program.cs类的代码:
public static class Program
{
[STAThread]
public static void Main(string[] args)
{
var baseAddress = new Uri(http://localhost:8161/MyService);
var host = new ConsoleHost();
host.Start(null);
Console.WriteLine("The service is ready at {0}", baseAddress);
Console.WriteLine("Press <Enter> to stop the service.");
Console.ReadLine();
host.Stop();
}
}
应将连接字符串等配置复制到Console项目的App.config文件中。
要启动控制台,请右键单击Console项目,然后单击Debug - > Start new instance。
你也可以试试这个。
(经过大量的谷歌搜索后,我在“如何在Visual Studio中调试Windows服务”中找到了这个。)
您应该将所有将从服务项目中执行操作的代码分离到单独的项目中,然后创建一个可以正常运行和调试的测试应用程序。
服务项目只是实现服务部分所需的shell。
要么就像Lasse V. Karlsen所建议的那样,要么在服务中设置一个等待调试器附加的循环。最简单的是
while (!Debugger.IsAttached)
{
Thread.Sleep(1000);
}
... continue with code
这样你可以启动服务,在Visual Studio中你选择“附加到进程......”并附加到你的服务,然后恢复正常的服务。
鉴于ServiceBase.OnStart
具有protected
可见性,我沿着反射路线进行调试。
private static void Main(string[] args)
{
var serviceBases = new ServiceBase[] {new Service() /* ... */ };
#if DEBUG
if (Environment.UserInteractive)
{
const BindingFlags bindingFlags =
BindingFlags.Instance | BindingFlags.NonPublic;
foreach (var serviceBase in serviceBases)
{
var serviceType = serviceBase.GetType();
var methodInfo = serviceType.GetMethod("OnStart", bindingFlags);
new Thread(service => methodInfo.Invoke(service, new object[] {args})).Start(serviceBase);
}
return;
}
#endif
ServiceBase.Run(serviceBases);
}
请注意,默认情况下,Thread
是前台线程。来自return
的Main
ing,而虚假服务线程正在运行时不会终止该过程。
Microsoft文章解释了如何调试Windows服务here以及如果他们通过附加到进程来调试它,那么任何人都可能错过的部分。
以下是我的工作代码。我遵循了微软建议的方法。
将此代码添加到program.cs
:
static void Main(string[] args)
{
// 'If' block will execute when launched through Visual Studio
if (Environment.UserInteractive)
{
ServiceMonitor serviceRequest = new ServiceMonitor();
serviceRequest.TestOnStartAndOnStop(args);
}
else // This block will execute when code is compiled as a Windows application
{
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new ServiceMonitor()
};
ServiceBase.Run(ServicesToRun);
}
}
将此代码添加到ServiceMonitor类。
internal void TestOnStartAndOnStop(string[] args)
{
this.OnStart(args);
Console.ReadLine();
this.OnStop();
}
现在转到Project Properties,选择选项卡“Application”并在调试时选择Output Type作为“Console Application”,或者在调试完成后选择“Windows Application”,重新编译并安装服务。
您可以创建一个控制台应用程序我用这个main
函数:
static void Main(string[] args)
{
ImportFileService ws = new ImportFileService();
ws.OnStart(args);
while (true)
{
ConsoleKeyInfo key = System.Console.ReadKey();
if (key.Key == ConsoleKey.Escape)
break;
}
ws.OnStop();
}
我的ImportFileService
类与我的Windows服务应用程序完全相同,除了继承者(ServiceBase
)。
您还可以尝试System.Diagnostics.Debugger.Launch()方法。它有助于将调试器指针指向指定的位置,然后您可以调试代码。
在此步骤之前,请使用Visual Studio命令提示符 - installutil projectservice.exe的命令行安装service.exe
然后从控制面板 - >管理工具 - >计算机管理 - >服务和应用程序 - >服务 - >您的服务名称启动您的服务
我刚刚将此代码添加到我的服务类中,因此我可以间接调用OnStart,类似于OnStop。
public void MyOnStart(string[] args)
{
OnStart(args);
}