拼吾爱程序人生

首页 » .Net编程 » Visual Studio.NET » 在控制台应用程序中处理信息
cobra - 2007-8-7 1:33:00
对于许多使用 Microsoft .NET Framework 的开发人员来说,他们印象中认为这类应用程序与可以在该应用程序中使用的库相关联,比如,控制台应用程序不能使用 Windows Forms 类。

对于许多使用 Microsoftreg; .NET Framework 的开发人员来说,他们印象中认为这类应用程序与可以在该应用程序中使用的库相关联,比如,控制台应用程序不能使用 Windowsreg; Forms 类。其实,这些概念是独立的。当您在 Visual Studioreg; 中创建新的控制台应用程序时,它将应用程序输出类型设置为“控制台应用程序”(等同于给 csc.exe 的 /t:exe 命令行参数)。当您在 Visual Studio 中创建新的 Windows 窗体应用程序时,它将应用程序输出类型设置为“Windows 应用程序”(等同于 /t:winexe),同时添加引用至 System.Windows.Forms.dll。但是很多人没有意识到的是,在控制台应用程序中可以添加对 System.Windows.Forms.dll 的引用,而且与之相反,您可以更改 Windows 窗体项目并编译为“控制台应用程序”。应用程序类型实际只规定是否将为进程启动默认的控制台窗口。

  所有这些的意思是,在控制台应用程序中,您可以使用来自 System.Windows.Forms 的类帮助您达成目标。我将创建一个依赖 Windows 窗体类的类使之变得很容易,将用由 winmm.dll 导出的 mciSendString 函数作为此类的测试案例,因为它提供的功能与您在使用时存在困难的 API 相似。同样的解决方案,但是会让您的控制台应用程序能处理各种 Windows 消息。例如,为了利用 Windows Vista™ 中提供的一些新的电源管理功能,您可能希望处理 WM_POWERBROADCAST 消息。本专栏中提供的类可以帮您解决这个问题。

  我将从我正在进行的设计开始。从 System.dll 导出的 Microsoft.Win32.SystemEvents 类提供了大量静态事件,用于监视各种系统状况。其中一些由广播消息引发。例如,DisplaySettingsChanging 和 DisplaySettingsChanged 事件是由 WM_DISPLAYCHANGE 消息引发。

  当 SystemEvents 在用户交互式应用程序中初始化时,如果引发 SystemEvents 初始化的应用程序线程被标记为 STA(单线程单元,即 Windows 窗体应用程序的默认设置),SystemEvents 将预期此线程有一个消息循环,以便 SystemEvents 附带发生。(因此,如果您错误地将某控制台应用程序标记为 STA,而且没有人工弹出消息,就无法引发 SystemEvents 上任何基于消息的事件。)但是,如果初始化线程是 MTA(多线程单元,即控制台应用程序中的默认设置),SystemEvents 将生成自己的线程。该线程将执行一个消息循环。在任何一种情况下,SystemEvents 都会创建一个隐藏窗口,与运行消息循环的线程相关联。此窗口的窗口过程负责处理相关消息和引发相关事件。

  由于我的实现不会像 SystemEvents 那么复杂,因此一个与 SystemEvents 类似的基本设计将使一切顺利开始工作。我的 MessageEvents 类提供了三个公共和静态成员:

  public static class MessageEvents

  {

   public static void WatchMessage(int message);

   public static event EventHandler

   MessageReceived;

   public static IntPtr WindowHandle;

   ...

  }

  WatchMessage 方法用来通知 MessageEvents 类有关(我作为使用者实际关心的)消息。(相比较而言,SystemEvents 已将这些硬编码到它的实现中。)当收到其中一条消息时,就会引发 MessageReceived 事件;此事件携带一个 MessageReceivedEventArgs 实例,并进而关联到代表所接收消息的相关 System.Windows.Forms.Message 结构。最后,如果 API 的目标是将向其发送消息的特定窗口,MesssageEvents 类将为 MessageEvents 用于接收和处理事件的底层窗口公开句柄。(通常那些使用窗口消息进行通知的 API 也能与 HWND_BROADCAST 或 0xFFFF 配合使用,如果使用后者的话,可以导致通知消息到达我们的底层窗口。)

  该底层窗口由 MessageEvents 创建。SystemEvents 能够智能化地决定是否创建一个附加窗口,如果不必这么做就节省资源。与之不同的是,我采用的是总是创建新线程和窗口的简单方法。如果您通过测量数据发现这损害了应用程序的性能,请放心使用必要的附加逻辑适当地扩展类型。

  MessageWindow 是 MessageEvents 中的一种私有嵌套类型。它由窗体派生而来,因此也继承了所有控件实例可用的底层 NativeWindow 功能(我可以将 MessageWindow 重新编写得精简一些,并直接操作 NativeWindow,但我还是把它留给您作为练习,如果您觉得合适的话)。MessageWindow 提供了一个公共成员 RegisterEventForMessage,它所做的仅仅将提供的 messageID 存入一个由 Dictionary 表示的消息集。该类也会覆盖 WndProc 方法,后者是对接收消息的窗口做出响应时执行的方法。这种覆盖会检查接收的消息是否在消息集内,并因此判断它是否已是为要引发的事件所注册的消息。如果是,WndProc 引发 MessageReceived 事件。不管如何,所有消息将传递到基本类的 WndProc 实现。

  关于如何引发事件,有几个有趣的地方值得注意。首先,MessageReceived 是 MessageEvents 上的一个事件,而非 MessageWindow 的。通常,一个类不能通过直接访问引发另一个类上的事件。这是 C# 编译器玩的一个小技巧。当我声明 MessageReceived 事件时,实际上我在做两件事:声明一个公共事件和声明一个私有委托。公共事件提供 add 和 remove 访问器,这就是为什么任何类都能用事件注册和注销处理程序。私有委托是在我“引发事件”时实际所调用的内容,即使在 C# 源代码中看上去像是我直接调用了事件(有关详细信息,请参见 msdn.microsoft.com/msdnmag/issues/06/11/NETMatters)。由于委托是私有的,因此其他类应该无法对其进行访问。但是,由于 MessageWindow 是 MessageEvents 中的嵌套类,因此 MessageWindow 确实可以访问 MessageEvents 私有实现的详细信息。在这种情况下,MessageWindow 可以访问私有的 MessageReceived 委托,允许它被 MessageWindow 直接调用。

  需要注意的另外一点是,事件不会在执行 WndProc 的同一线程上引发。相反,WndProc 在初始化后会使用 MessageEvents 获取的 SynchronizationContext 实例(稍后将详细讨论此问题)。SynchronizationContext 用来在应用程序的同步环境中(不管是什么),通过异步 Post(而不是同步 Send)引发事件。在控制台应用程序中,SynchronizationContext.Post 所做的仅仅是执行 ThreadPool 线程上的委托。在 Windows 窗体应用程序中,WindowsFormsSynchronizationContext.Post 通过将调用封送处理回 UI 线程来执行委托。

 您可能对 [Visual Studio.NET] 的这些文章也感兴趣:

ListView和CSS Friendly
从.Net类库代码来看Asp.net运行时
如何通过需要验证的邮件服务器发送邮件?
让.NET应用成为灰色盒子
Promesh.NET:一个.NET的MVC Web框架
WebBrowser控件的简单应用
使用Vs2005打造简单分页浏览器
C#和ASP.Net面试题目集锦
.Net 保护中的native compile方式
Tracing memory leaks in .NET applications with ANTS Profiler
发现并防止托管代码中出现内存泄漏
深入理解.NET内存回收机制
cobra - 2007-8-7 1:33:00
MessageEvents 类仅包括静态成员,事实上类本身就声明为静态类(如果尝试添加非静态成员,编译器将报错)。在内部,它将一个静态引用存储到 MessageEvents 初始化时创建的单独的 MessageWindow。

  private static MessageWindow _window;

  private static IntPtr _windowHandle;

  MessageEvents 的两个公共方法的实现十分简单,因为大多数工作都分解成独立的初始化方法。WatchMessage 方法调用 EnsureInitialized,然后将消息 ID 传递给 MessageWindow 的 RegisterEventForMessage 方法。WindowHandle 属性也调用 EnsureInitialized,但只返回初始化时创建的窗口句柄。

  public static void WatchMessage(int message) {

   EnsureInitialized();

   _window.RegisterEventForMessage(message);

  }

  public static IntPtr WindowHandle {

   get {

   EnsureInitialized();

   return _windowHandle;

   }

  }

  现在我们需要考虑如何实现 EnsureInitialized。EnsureInitialized 负责使 MessageEvents 类开始运行,使之监视和响应所有相关消息。因此,它需要生成新的线程,创建将接收消息的广播窗口以及启动消息循环运行。

  EnsureInitialized 仅运行初始化逻辑一次;如果它发现 MessageWindow 已经创建,就放弃运行。如果需要执行初始化,它会从获取与当前线程关联的 SynchronizationContext 开始。它可以使用 SynchronizationContext.Current 达成此目的;但是,如果没有预先配置好环境,SynchronizationContext.Current 将返回空值。实际上,我使用 AsyncOperationManager.SynchronizationContext,它是对 SynchronizationContext.Current 的一个简单包装,如果 SynchronizationContext 不存在,它首先会设置一个默认的;这样,当继续返回 SynchronizationContext.Current 值时,将永远不会返回空值。

  配置好环境之后,EnsureInitialized 就会创建一个新的线程。此线程会创建 MessageWindow 并存储它,同时将窗口的句柄存入一个单独的静态成员,如之前所示,该成员用作从 WindowHandle 属性返回的值。此时,窗口打开并运行,我需要启动消息循环运行。为达此目的,我可以编写自己的消息循环,但是利用 .NET Framework 中现有的则更为方便。Application.Run 方法开始在当前线程中运行标准应用程序消息循环,所以我只需要调用该方法即可开始此进程。

  以上是我的 MessageEvents 类。要观察它的运行,我将使用前面提到的 winmm.dll 中的 mciSendString 函数。mciSendString 函数是 Windows 中媒体控制接口 (Media Control Interface, MCI) 的一部分,可以用来与各种多媒体设备交互:

  [DllImport(“winmm.dll”, EntryPoint = “mciSendStringA”,

   CharSet = CharSet.Ansi)]

  private static extern int MciSendString(

   string lpszCommand, StringBuilder lpszReturnString,

   int cchReturn, IntPtr hwndCallback);

  要用 mciSendString 播放 WAV 文件,可使用如下所示的典型命令序列:

  open “C:\Windows\Media\chimes.wav” type waveaudio alias 12345

  play 12345 wait

  close 12345

  第一个命令会打开指定设备,为其分配一个别名(在本例中是 12345),以便稍后在调用其他命令时引用它。第二个命令告知系统启动设备,在命令完成执行之前不会返回。最后一个命令告知系统关闭设备。但是请注意,我为 MciSendString 创建的 P/Invoke 签名有一个 hwndCallback IntPtr 参数。它与通知标志配合使用,该标志可添加至包括 play(播放)在内的多个命令中。

  play 12345 notify

  由于还未指定等待标志,此命令将开始播放并立即返回。但是,由于指定了通知标志,因此在命令异步完成的时候,一个 MM_MCINOTIFY 消息会发送至通过该 hwndCallback 过程指定的 HWND。

  MciSendString(“play 12345 notify”, null,

   0, MessageEvents.WindowHandle);

  请注意我如何提供 MessageEvents.WindowHandle 作为目标 HWND。执行该命令之前,我可以配置 MessageEvents 类以接收那些回调消息:

  MessageEvents.WatchMessage(MM_MCINOTIFY);

  MessageEvents.MessageReceived += delegate(

   object sender, MessageReceivedEventArgs e)

  {

   Console.WriteLine(“Message received: “ + e.Message.Msg);

  };

  有了这些代码之后,当通知命令完成时,关于结果消息的信息将写入控制台。当然,虽然我并不一定需要 MessageEvents 类在 Windows 窗体应用程序中完成此任务,但它在此类应用程序中也应该同样正常工作,就像在没有自己的消息循环的控制台应用程序中一样。
1
查看完整版本: 在控制台应用程序中处理信息
Modify by pin5i DZNT_ExpandPackage 2.1.3295 2007-2009 pin5i.com
 Total Unique Visitors: