C# 泛型动态 Windows 服务使用 .NET 反射
一种使用 .NET 反射创建可配置和动态的 Windows 服务应用程序的技术。
引言
本文展示了一种设计 Windows 服务应用程序的技术,其目标如下:
- 可以像“控制台”应用程序一样进行调试,而无需调用“安装程序”。
- 主要的“Windows 服务”代码应该是可重用的,并且可以快速应用于任何新的“Windows 服务”应用程序开发。
- 分离“服务器逻辑”并允许在需要时进行动态切换。
- 安装期间“Windows 服务”的详细信息可以通过 XML 文件进行配置。
- 服务器逻辑的加载可以通过 XML 文件进行配置。
我在此处提供的示例代码是基本框架,已使用 Visual Studio 2008 和 .NET 3.5 Framework 进行了测试。 但这个概念也应该适用于其他版本。
背景
如果您从未创建过 Windows 服务应用程序,则 MSDN 库站点上有一篇“演练”文章。 显然,The Code Project 上也有许多有用的文章。
Using the Code
通过使用所提供的技术,创建 Windows 服务应用程序只需要您使用 Visual Studio 创建一个新的“控制台”项目即可。
示例 Visual Studio 解决方案包含 4 个项目。 它们是
GDWS.Common
- 这是 Windows 服务的通用可重用代码所在的位置。GDWS.ExampleService
- 这是一个示例“服务器”逻辑,旨在构建为 .NET 程序集。GDWS.ExampleServiceProgram
- 这是一个示例“控制台”应用程序,它实际调用了服务器逻辑。SetupExampleService
- 这是一个典型的“安装和部署”项目,用于演示此技术也可以与标准安装机制无缝协作。
简而言之,Main()
程序非常简单,如下所示
namespace GDWS.ExampleServiceProgram
{
class Program
{
static void Main(string[] args)
{
ServiceMainProgram.ServiceMain(args);
}
}
[RunInstaller(true)]
public class ExampleServiceInstaller : CustomServiceInstaller
{
}
}
类 - CustomServiceInstaller
此类主要读取 ServiceInstall.xml 文件,以便告诉 Windows Installer “服务名称”、“服务显示名称”和“服务描述”。
类 - ServiceMainProgram
为了动态加载“服务器逻辑”,使用 .NET 反射来加载封装服务器逻辑的 DLL。 这两个构造函数是 private
,并且您可以使用两个 static
方法来调用该过程。
public class ServiceMainProgram
{
...
...
public static void Service(string[] args, Type type)...
public static void Service(string[] args)...
...
}
采用两个参数的 static
方法要求您提交一个 Type
,以便在内部创建对象期间,可以使用反射来找出是否存在两个必需的 static
方法 StartThreadProc
和 StopThreadProc
。
仅采用一个参数的第二个 static
方法从 ServiceConfig.xml 文件中读取 Type
的名称,该文件还会告诉它在哪里可以找到服务器逻辑程序集 DLL。 找到 DLL 的路径后,将使用反射加载该程序集。
private Type LoadAssemby(string configFileName)
{
...
...
Assembly asm = Assembly.LoadFile(Path.GetFullPath(assemblyFullPath));
...
Type type = asm.GetType(typeName);
...
}
一旦找到并检查了 Type
,以下代码就可以利用反射的方法
private void Run()
{
if (debugMode) RunDebug();
else
{
ServiceBase[] servicesToRun = new ServiceBase[]
{ new GenericService(threadProcType) };
ServiceBase.Run(servicesToRun);
}
}
private void RunDebug()
{
...
...
MethodInfo mStart = threadProcType.GetMethod(START_THREAD_PROC);
// assuming static method, hence no need to pass any instantiated object
mStart.Invoke(null, null);
bool stop = false;
while (!stop)
{
...
if (k.KeyChar == 'q' || k.KeyChar == 'Q')
{
...
MethodInfo mStop = threadProcType.GetMethod(STOP_THREAD_PROC);
// assuming static method, hence no need to pass any instantiated object
mStop.Invoke(null, null);
stop = true;
}
...
}
}
调试
出于调试目的,定义一个命令行参数 debug
,或者在命令提示符下,只需键入 yourservice.exe debug
即可将程序调用到调试模式。
安装为适当的 Windows 服务
您可以使用捆绑的 Setup 项目安装示例服务应用程序。
要快速测试应用程序作为 Windows 服务,您可以使用 installutil.exe
并通过调用 installutil.exe /u
来卸载它。
ServiceConfig.xml
<?xml version="1.0" encoding="utf-8" ?>
<ConfigService>
<ServiceName value="ExampleService"/>
<ServiceDisplayName value="An Example Service"/>
<ServiceDescription value="An example service
that demonstrates a generic and dynamic technique."/>
</ConfigService>
ServiceInstall.xml
<?xml version="1.0" encoding="utf-8" ?>
<InstallService>
<ServiceAssemblyFullPath value=
"..\..\..\GDWS.ExampleService\bin\Debug\GDWS.ExampleService.dll"/>
<TypeName value="GDWS.ExampleService.ThreadProcExample"/>
</InstallService>
请注意,尽管 ServiceAssemblyFullPath
被指定为相对路径,但如果代码在第一个实例中找不到它,它将尝试在本地找到它。
历史
- 2008-01-22: 文章创建