65.9K
CodeProject 正在变化。 阅读更多。
Home

在 C# Windows 服务中捕获设备事件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.82/5 (21投票s)

2008年12月25日

CPOL

5分钟阅读

viewsIcon

132669

downloadIcon

7500

在托管的 Windows 服务中处理 DBT_DEVICEQUERYREMOVE

引言

我正在编写一个 C# Windows 服务,并且需要捕获一些设备事件。虽然这看起来是一项简单的任务,但奇怪的是,事实并非如此。以下是我需要做的以及我所做的事情。

必备组件

Windows 服务本质上是一个由服务控制管理器 (SCM) 启动的 Windows 程序。服务可以手动启动,也可以安排 SCM 在用户登录后立即启动。如果您不熟悉它们,可以在 MSDN 上获取更多信息。

我在 Visual Studio 2008 Professional SP1、.NET 3.5 SP1、Windows XP SP3 中测试了代码。它应该可以在 2.0 以上的所有框架中工作,甚至可能在 1.1 中也可用。我建议您在阅读文本的同时浏览代码,这样会更清楚。跳转查看其运行情况。

问题描述

Windows 中的通信是通过消息完成的。当设备(如 USB 驱动器)插入计算机时,Windows 会向所有监听此类事件的程序发送消息。例如,Windows Forms 应用程序可以通过重写 `Form` 基类的虚拟 `WndProc` 方法并使用其 `Message` 参数来轻松拦截 Windows 消息。然而,Windows 服务通常不用于与桌面或 I/O 设备交互,因此没有简单的方法来知道何时发生了有趣的事情。而且,如果您想处理 `DBT_DEVICEQUERYREMOVE` 事件,事情会变得复杂。

服务控制处理程序 (SCH)

服务有一个控制处理程序,可以接收来自 Windows 的所有消息。这些消息可能包括停止或暂停服务的代码,或者像我们一样,设备事件。托管服务将抽象出这个控制处理程序,只提供 `OnStart`、`OnStop` 等方法,您可以在其中实现所需的功能。然而,我们需要注册我们自己的服务处理程序,以便能够捕获设备事件。请注意,这样做会禁用除 `OnStart` 之外的所有回调(如 `OnStop`),`OnStart` 是在我们告诉 Windows 使用我们的处理程序之前调用的。

对应的 Windows API 函数是 `RegisterServiceCtrlHandlerEx`,它接受一个服务名称和一个回调函数,当收到消息时调用该函数。我们将在服务中的 `OnStart` 函数中调用它。托管版本返回一个 `IntPtr`,这是一个服务句柄,但我们已经有一个名为 `ServiceHandle` 的类属性,所以我们不需要保存它。

服务控制处理程序的签名如下:

public delegate int ServiceControlHandlerEx(int control, 
                int eventType, IntPtr eventData, IntPtr context);

现在,我们可以实现我们的“`OnStop`”回调,捕获在处理程序的“`control`”参数中接收到的 `SERVICE_CONTROL_STOP` 事件。但是,为了处理 `SERVICE_CONTROL_DEVICEEVENT`,我们需要做一些其他事情。

注册设备通知

在 `OnStart` 方法中,除了注册控制处理程序外,我们还将使用 Win32 API 函数 `RegisterDeviceNotification` 来注册设备通知。我们向它提供服务的句柄、指向 `DEV_BROADCAST_DEVICEINTERFACE` 结构的指针(告知函数注册一类设备)以及一些标志,其中包括 `DEVICE_NOTIFY_SERVICE_HANDLE`,它指定调用者是一个服务而不是一个窗口。它会返回一个句柄,我们必须保留它才能在不再需要设备消息时注销(例如,我们可以在 `SERVICE_CONTROL_STOP` 事件中这样做)。

使用此函数允许我们捕获 `DBT_DEVICEARRIVAL` 和 `DBT_DEVICEREMOVECOMPLETE` 事件类型。我们通过 SCH 的 `eventType` 参数获取它们。在那里,我们可以处理 `SERVICE_CONTROL_DEVICEEVENT` 并做任何我们想做的事情。

DBT_DEVICEQUERYREMOVE

到目前为止一切顺利。但我需要更多。我需要使用 Microsoft 在 .NET Framework 中随附的 `FileSystemWatcher` 控件来监视新插入的 USB 设备。我可以在收到设备插入事件时启动监视器,但当我尝试“安全删除硬件”时,它总是提示无法删除。显然是因为监视器。我搜索了一下,发现有一个事件 `DBT_DEVICEQUERYREMOVE`,它在设备即将被移除之前触发。唯一的问题是我无法通过到目前为止所做的一切来捕获它。有趣的是,Windows Forms 应用程序的工作方式相同,因此下面所做的一切也适用于它们。

解决方案是创建设备的句柄,在(这一次)`DEV_BROADCAST_HANDLE` 结构中使用它,并将其注册到我们的 SCH。所有这些都可以在 `DEVICEARRIVAL` 事件中完成。为了实现这一点,需要做几件事——使用 `GetVolumeNameForVolumeMountPoint`(它从 `DEVICEINTERFACE` 结构中的 name 字段获取参数)和 `GetVolumePathNamesForVolumeName` 来查找设备分配的驱动器号,然后使用 `CreateFile` 函数获取设备句柄。只有这样,我们才能捕获 `QUERYREMOVE` 事件,禁用 `FileSystemWatcher`,并允许 USB 被自由移除。

结论

还有很多其他小细节,如果写得不像我写的那样,是无法工作的。如果您需要这种功能,我强烈建议您浏览我提供的代码,即使您不想理解它,至少也要复制粘贴。 :) 这将为您省去很多麻烦。

历史

  • 2008 年 12 月 25 日:初始发布
  • 2009 年 3 月 14 日:更新源代码
© . All rights reserved.