贝贝花花包包店,精品555皮具,钱夹,皮夹

字体: | 推荐给好友 上一篇 | 下一篇

Windows2000 服务器端应用程序开发设计指南-事件记录

发布: 2008-5-06 19:59 | 作者: Jeffrey Richter Jaso | 来源: 本站原创 | 查看: 121次

6. 事件记录

当一个典型的软件应用程序必须在某些特殊情况下让使用者知道时,通常会使用视觉或听觉返回的方式。软件大多数会给予这种类型事件报告的享受,因为它可以建立一个重要的假定:当它正在执行,而一个人类坐在机器的前面。然而,大部份的伺服软件并不能在上述之假设情形中执行。

因此,服务器开发者使用文件或者一些其他类似持久稳固的储存器来保存经由软件报告的事件记录。然后系统管理者便可以经常地察看记录文件并且持续的监看重要事件与错误的情形。如此解决了没有人负责坐在机器前操作的问题,但是它引出了一个新的问题:管理能力。什么样的系统管理者喜欢使用许多会在系统的许多地方以不同文件格式储存许多事件报告的伺服应用程序?而在这个情形下甚至不会考虑由系统本身报告的事件。

Microsoft Windows在介绍一个标准的事件报告机制时,提出了这个管理能力的议题:Event Log服务。Event Log服务加强了标准的记录格式和透过使用所提供之单一事件检视器应用程序来使系统管理者可以容易察看记录并且可以一次就将所有的操作完成。Windows使用Event Log服务报告像硬盘空间不足和尝试登录失败这种的系统事件。当然,您的伺服软件不须要使用事件记录的功能,然而您的使用者将会欣赏一个与实际系统同样好用的另一个伺服软件。

在本章中,我们将学习有关事件记录与如何编写回报事件软件的方法。包含了学习如何去编译和利用讯息文件与事件关联的部份。事件的回报是大部份应用程序开发者所关心的,通常任何管理者都会需要在事件检视器嵌入式管理单元中读取事件,而我们也将会涵盖如何设计一个应用程序来读取事件记录的内容。

那么让我们花点时间先从一个管理者的角度来探索事件记录,然后再从系统的观点来讨论。

事件记录是什么?
 

从系统管理者的角度来看,事件记录是一个由系统或应用程序软件所发布的讯息清单。这个讯息清单被组织至一个称为log files(或logs)逻辑的群组。记录的收集通常被称作 事件记录 。系统管理者对事件记录的窗口即是被安装在Windows 2000中的Microsoft Management Console(MMC)之事件检视器嵌入式管理单元。您可以按下 开启 并指向 程序集  系统管理工具 ,然后选择 事件检视器选项 来开启事件检视器。您也可以在 系统管理工具 中选择 电脑管理 存取事件检视器。图6-1显示了在电脑管理中的事件检视器嵌入式管理单元。


 

 图6-1 在电脑管理中的事件检视器嵌入式管理单元

在MMC中的事件检视器节点里,您可以看见一组记录。当您选择了一个记录时,右边的窗格会显示关于该记录的事件资讯。在一个事件项目上双按滑鼠会得到关于该事件的详细资讯。接下来将讨论在每一个事件中找到的资讯,但首先必须先讨论不同记录的用途。

在预设值下,您的系统之事件记录会包含叁个记录:应用程序记录档、系统记录档以及安全性记录档。应用程序记录档在系统中添加自己的记录;然而,这并非一般或常见的需求。如果您决定回报至您所拥有的记录档中,那么您要在左边的窗格中选择事件检视器节点并从 执行 功能表中选择 开启记录档 选项,以告知事件检视器嵌入式管理单元。如此会产生一个允许您开启一个记录档的开启对话方块。在事件检视器嵌入式管理单元中察看记录时,您必须至少回报一个事件至自订的记录中。

表6-1定义了叁个标准的事件记录。因为这本书的主题是编写伺服应用程序,所以应用程序记录档将是我们最有兴趣的部份。

 表6-1 在事件检视器中的标准事件记录档
记录名称说明
应用程序记录档包含经由应用程序软件与服务产生的事件。
系统记录档包含经由设备驱动程序与其他的作业系统元件产生的事件。
安全性记录档包含经由安全稽核产生的事件。

现在让我们花点时间来剖析一个被记录的事件项目。一个事件即是在事件记录中的单一项目,而它由以下的资讯栏位组成:事件类型、被产生的日期与时间、被写入的日期与时间、事件来源、事件类别、事件识别码以及系统。除了这个资讯外,每一个事件可以包含一个详细的文字说明并拥有与它关联的二进位资料。事件检视器嵌入式管理单元能显示最多的资讯。表6-2提供了每个栏位的简单说明。

大部份栏位的意义已经非常清楚,但是事件来源、事件识别码、事件类别与事件类型则需要更多的解释。

事件来源描述了应用程序、服务或系统元件所报告的事件。回报的原因和事件来源间存在着典型的一对一关系。然而,回报的事件代码决定了所回报的来源,所以单一的应用程序能够回报若干来源。同样地,多个程序可以回报一个单一的来源。Windows不透过任何方法来限制这个回报的灵活性。

事件识别码是一个被来源定义的值,指示了一个某些事件的类型。任何的事件皆可以经由一个事件来源与识别码的合成而被确认。例如,Browser服务定义事件识别码8021为「浏览器无法从浏览主控制器中取回一个网路上的服务器清单……」,还有事件识别码8033为「浏览器被强迫当选……」。

事件类别是一个事件可任意选择的来源定义类别。就它对一个大量的不同类型事件的回报,得进一步中断到逻辑类别里的应用程序和系统元件而言是有益的。

 表6-2 在一个事件记录中的项目栏位
栏位说明
事件类型确认事件的类型。系统定义了五个不同的事件类型,列示在表6-3中。
被产生的日期与时间确认被加入一个记录的事件之所需来源时间。
被写人的日期与时间确认系统在一个记录档中被记录的项目。
事件来源确认元件是加入事件至一个记录的来源。通常来源是一个应用程序或是服务。
事件类别确认一个为了事件而被来源定义的类别。
事件识别码确认一个被来源定义的号码,该号码为指示事件种类的唯一值,且会导致一个项目被加至记录中。
使用者确认产生事件项目之使用者帐户内容。这个值是一个使用者的安全识别项(Security Identifier,SID)。请参阅 第九章 以取得SID的更多讨论内容。
系统确认产生事件的机器。

您必须决定哪一个事件种类是必须的或是对您的软件有帮助。如果一个事件来源选择去忽略类别,那么事件检视器嵌入式管理单元将会从来源回报事件没有类别的情形。

事件类型可以是被列在表6-3之五个被系统定义的事件类型之一。回报事件的软件会选择事件类型。

 表6-3 事件类型
事件类型说明
EVENTLOG_INFORMATION_TYPE资讯事件表明对应用程序或系统没有发生疑问的情况或者操作—例如,服务应用程序的启动或者停止。
EVENTLOG_WARNING_TYPE等待可能的重要性事件或未来的问题情况—例如,相当低的内存或磁盘空间,如果资源被继续使用可能会产生问题。
EVENTLOG_ERROR_TYPE当一个应用程序或系统元件的一部份功能真的失败时,该错误的事件会被记录—例如,不能写入资料到磁盘可能会导致资料遗失。
EVENTLOG_AUDIT_SUCCESS当成功的完成一个稽核时会被Windows安全性记录档记录一个成功稽查的事件。
EVENTLOG_AUDIT_FAILURE当一个稽核的动作被执行且失败时,Windows会记录一个失败的稽查事件。

回报事件
 

在进入自己的事件回报前,先讨论一些关于您的软件应该回报什么事件到事件记录中的内容。然后我们会尝试学习如何去编写回报事件的软件。

什么事件应该被回报?
 

如果您正在开发一个服务应用程序,那么您很可能正想去做一些事件回报的动作。在您这么做之前,无论如何您都必须真正地了解事件记录的内容。记得事件记录是系统管理员从您的服务返回的来源。一个回报无意义事件或太多事件的服务就如同一个显示了太多讯息方块给使用者的应用程序一样,会惹恼系统管理员。

大部份开发者发现决定在哪一种情况下可以授权一个错误事件的记录是件容易的事;而同样的,当您的软件需要发布一个警告事件时,也能容易且适度地被决定。然而,您进入了一个资讯事件的灰色地带。什么软件的活动重要得足以记录一个资讯事件?如果您从系统管理者察看的角度来考虑,那么您通常可以回答这个问题。

没有管理者想在一天结束后还要费力地进行察看几百个或甚至更多资讯事件的动作,只为了找到与她的工作有关的一或两个项目。所以您应该考虑一个「重要性」方面的问题即是在特定情况下的共用问题。一个Web服务器很可能不会为了每个它所接收的连结而记录一个事件。另一方面,它可能会想要为每个因为它太繁忙,无法处理要求而被拒绝存取的连结回报一个事件。然而记录这个事件可能并非必要。如果连结被拒绝时,有一个折衷方案可能是每小时即记录一个事件。该记录可能会包含在一些被拒绝存取连结总数的详细说明中。一个可能性是建立此种回报选项,以使管理者能在被回报的说明中挑挑拣拣。

另一个事件资讯的使用将会回报在您的软件状态中罕见的改变。例如,您可能会想在每次您的服务被暂停或继续执行时便产生一个事件。或者假定您的软件进入了待命模式,释放某些资源就某种时间而言,关系着连结的负载何时是处于较低的情形—这种情形可以允许一个资讯事件的发生。状态改变事件资讯可以是有用的,因为它们给予管理者一个在一个警告或错误事件发生前您的软件完成了什么活动的检测功能。

然而,您要避免将您的应用程序当做一个追踪资讯的储存处并成为考虑事件记录的习惯。这个侦错资讯的型态将会克服管理者与大部份可能会使它忽略每个被您的应用程序所建立之事件。每个事件同样会占用您系统之磁盘空间,所以有效的使用储存媒体也是一个重要的事。如果您的服务器将会回报许多的事件,或者会从许多事件来源回报事件,那些您可能会想要考虑将您的事件记录至一个自订的记录档中。最后在这里必须将一般的常识做个统一;然而您可以依照可能的使用者配置而建立您的应用程序回报机制,以提供使用者最好的记录。

我们已经在关于什么事件应该被回报的内容讨论已经足够。现在让我们进入如何将事件回报的部份。

如何回报事件
 

回报软件事件是一个简单的处理程序,但是在您能够确实利用事件记录前,需要了解如何将它们组合起来。一起拼凑它们之最容易的方法要先看最简单的部份,即是那些回报处理程序。

如果您的处理程序想要开始回报事件至事件记录,它需要使用以下的函数去登录一个事件来源:

HANDLE RegisterEventSource(
PCTSTR pszMachineName,
PCTSTR pszSourceName);

pszMachineName参数为确认您想要将事件项目加入之包含记录档的系统。传递NULL给此参数会在本机中开启记录档。很少有事件会被回报至一个远端机器上的记录文件中。

pszSourceName参数即是事件来源的名称。此名称是被显示在事件检视器嵌入式管理单元中的来源栏位;它不一定是您的可执行程序名称(但是它通常会是)。


说明

我必须提供一个关于安全性的词语:安全性与系统记录档皆是安全的。为了要使您的应用程序加入事件至这些记录文件之一,当它呼叫RegisterEventSource时,您的处理程序必须在拥有安全性内容的权限下执行。它的规则是:安全性记录档只能在使用本机帐户时才可以被写入,而系统记录档则可以在使用本机帐户与管理者帐户时被写入。


如果RegisterEventSource的执行是成功的,它会回传一个有效的handle。您的应用程序即可使用此handle来回报事件。记得所有的handle都一样,当您已完成它时应该让系统知道。您会使用一个呼叫DeregisterEventSource的函数来做这件事:

BOOL DeregisterEventSource(HANDLE hEventLog);

至目前为止一切皆很顺利,对不对?回报一个事件也相当地繁琐,您可以使用一个呼叫至以下的函数:

BOOL ReportEvent(
HANDLE hEventLog,
WORD wType,
WORD wCategory,
DWORD dwEventID,
PSID psidUser,
WORD wNumStrings,
DWORD dwDataSize,
PCTSTR* ppszStrings,
PVOID pvRawData);

hEventLog参数是被RegisterEventSource回传的handle。wType参数则是表6-3所列之五个事先定义类型之一。wCategory和dwEventID参数是您随意选择的值。dwEventID参数将会唯一地识别您的事件来源。事件识别码可以是任意值,并且以任何您想要的顺序来安排。然而,一个指示事件之被定义来源群组的事件类别必须拥有以1启动的值并从那里开始进行。该0值是被保留来指示没有类别时使用的。psidUser参数确认一个使用者,它没有在事件上隐含安全性的限制。NULL时常被传递以说明与任何使用者无关。

ppszStrings参数指向一个与事件关联的文字字串阵列,而wNumStrings是一些阵列中的字串。(我将会在本章之〈建立讯息DLL与Exe〉一节中讨论ppszStrings参数。)最后,pvRawData参数指向一个与事件关联的二进位资料区块。dwDataSize参数指出以位元组为单位之资料区块尺寸。该资料被回报它的应用程序定义,而且它可以是任何您觉得会对使用者或管理者有用的资料。例如,网路驱动程序把原始材料放在被驱动程序所报告的某些事件里。

根据文件所提,刚刚所描述的函数是关于记录事件至事件记录档之所有您需要知道的事情。技术上来说,这是事实;不过,有一些要点被删掉了。如果您把我们使用ReportEvent传递给Event Log服务的资讯与事件检视器嵌入式管理单元所显示的一个典型事件做比较,您将会发现缺少了一些资料。具体而言,我们传递一个类别号码,而事件检视器嵌入式管理单元显示一个文字字串。同样地,并非依照RegisterEventSource或ReportEvent来说明我们想要报告的记录(即应用程序、系统或安全性)。最后,您必须注意ReportEvent缺少了一个对事件详细说明的参数。

依照我曾解释过在应用程序记录档中放置您的事件之预设的方法。记录档包含了详细说明您的事件之一般文字,然后把您的字串资料附加至文字的末端。我们可以在这里停止,但是如果您另外又实作了您自己的详细说明与类别部份,使用者将会非常感谢您,做这件事有助于理解事件记录之设计目标。

讯息文件
 

Windows事件记录的设计者想要建立一个有效的机制以增加语言的独立性。基于这个目标,一个人类可读的事件部分—那就是,类别与详细说明—是从您的应用程序取出到分开的讯息文件中。这些讯息文件被实作成DLL或EXE文件,而它们包含了一个自订之内含您的讯息文字的二进位码资源。本章后面将会解释关于讯息DLL的详细内容,然而现在有几件事是您必须要知道的。

讯息档可以是一个被给定的事件资讯:事件讯息文件、类别讯息文件以及参数讯息文件。一个单一讯息的DLL(或EXE)可以展示它们之中的任何结合,所以您的应用程序可以在一个讯息DLL中储存您的事件讯息文件、类别讯息文件或参数讯息文件;相反的动作也可以成立,例如,可以使用超过一个的讯息DLL为单一事件资源表示事件讯息文件。


说明

通常DLL与EXE文件分别被称为讯息DLL和讯息EXE。为了容易阅读,本章的其馀部份将使用「讯息DLL」来说明,然而,所有资讯皆适用于讯息EXE的部份。


您透过用与您传递给RegisterEventSource的pszSourceName参数相同的名字在子机码下建立少量的登录值使讯息DLL与您的事件来源联系。事件记录档也用它来指定这个新登录机码到您那一些将被记录的来源事件中。一般安装应用程序的软件会将这些项目加到登录中,但是没有说明您的服务不能够加入他们的规则。当该项目已被加入登录时,登录的布局会跟随着以下的阶层而结束:

HKEY_LOCAL_MACHINE
SYSTEM
CurrentControlSet
Services
EventLog
Application
Event Source
...
Security
Event Source
...
System
Event Source
...
CustomLog
...

请注意CustomLog机码位于EventLog之下。在预设情形下,在EventLog机码下只有Application、Security与System机码,但是您的程序代码也可以在标准的记录机码中添加另一个机码以采用一个自订的记录至事件记录中。当您呼叫RegisterEventSource时,系统会为了与pszSourceName参数相符的机码而搜寻位于HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EventLog\Log下的机码。每一个记录皆被依照字母排序而搜寻,直到发现一个符合的来源子机码为止。

所以您可以看到,事件来源的命名空间被所有标准或自订的记录所分享。如果系统下的事件来源在应用程序中与事件来源相匹配,则它将会被忽略。

为了使您的讯息文件与您的事件来源联系,要在您的事件来源机码下建立适当的登录值。支援的登录值定义在表6-4中。

 表6-4 支援事件来源子机码的登录值
登录值类型说明
TypesSupportedREG_DWORD确认一组显示在表6-3中的标记,指出被讯息DLL支援的事件类型。
EventMessageFileREG_EXPAND_SZ确认到事件讯息文件的路径名称(被分号分开的)。试着将一个事件识别码转换至人类可阅读的字串时,事件检视器会搜寻这个文件设备。
CategoryMessageFileREG_EXPAND_SZ确认至类别讯息文件的路径名称(被分号分开的)。试着将一个类别识别码转换至人类可阅读的字串时,事件检视器会搜寻此文件设备。
ParameterMessageFileREG_EXPAND_SZ确认至参数讯息文件的路径名称(被分号分开的)。试着将一个可置换的字串参数识别码转换至人类可阅读的字串时,事件检视器会搜寻此文件设备。
CategoryCountREG_DWORD确认被事件来源支援的类别数量。

注意在表6-4中所有讯息文件的值皆是REG_EXPAND_SZ资料型别。它意味着路径名称能够包括将在执行时期被扩张的系统环境变数。下面范例中的路径名称设备是任何讯息文件登录值的一个有效值:

"%SystemRoot%\System32 \msg.dll;c:\messages \msg2.dll"

说明

因为您的使用者可能会从一个远端机器上察看您的记录事件,所以将路径以一个通用命名惯例(Universal Naming Convention,UNC)的格式列示至讯息文件通常会比使用一个驱动程序字母与路径还好。当一个事件检视工具(例如事件检视器嵌入式管理单元)查询了一个DLL讯息文件的路径,并在网路分享上发现它时,事件检视器即可以载入DLL并且阅读讯息。如果事件检视器嵌入式管理单元无法确定该被指定之DLL的位置,那么它便无法将识别码转换至人类可读的字串。这里有一个当识别码无法被转换至字串时,事件检视器嵌入式管理单元所显示的内容:


 

注意事件识别码与类型栏位被显示为数字而非字串。同样请注意说明栏位在这种情况下所能显示之最好的资讯。


顺便一提,在一个单一的EXE或DLL文件中包含所有的事件、类别与参数讯息是很常见的。当产生这个情况时,会同时放置叁个讯息文件登录值。

每一个个别的记录档(如应用程序记录档或一个自订的记录档)会被存放在它所拥有的记录档中,其延伸档名为 .evt。正常情况下,事件检视器会抓取登录子机码,附加 .evt延伸档名,并且尝试使用此文件名称去开启一个记录档。然而,您可以撤消此行为并指定一个自订的路径名称给被加至Logname机码下之文件登录值的记录档。这个改变路径名称的方法需要重新启动系统以使改变生效。注意到之前已改变的记录资讯不会显示在新的记录中。

有二个影响一个记录的登录值应该被提出来说明。第一个为MaxSize,它的型别为REG_DWORD。这个值是系统允许记录档之最大值,以位元组表示。第二个值是Retention,它的型别也是REG_DWORD。这个值指定在它自动地从记录档中删除前,一个事件在短时间内应该多大。您的程序代码可以自由的去修改这些值。如果MaxSize与Retention值不存在一个被给定的登录中,系统会预设最大的记录文件大小为512 KB和7天的保留时间。

您不太可能会在登录中手动地改变这些值。更确切地说,您大概会使用事件检视器嵌入式管理单元选择一个记录并显示它的内容对话方块来改变它们。图6-2显示了一个记录的内容对话方块。


 

 图6-2 应用程序记录档的内容对话方块

既然我们已经讨论了用您的事件来源记录事件和与其联系的讯息DLL,那么现在是将讯息文件的细节讨论与上述两个主题结合的时候。

建立讯息DLL与EXE
 

我们还必须回答的问题如下:

  • 讯息文件中的文字如何与一个事件结合?
     
  • ReportEvent的ppszStrings参数如何获得利用?
     
  • 我们如何建立一个讯息DLL?
     

本节中将会回答上述的所有问题。图6-3显示了所有的事件记录结构,包含建立一个讯息DLL所需的步骤。您将会在后续的讨论中参考到此图。

您的第一个建立讯息DLL的步骤是为您的讯息来源建立一个文件。此讯息来源一般被称为「MC」文件,而且,可以使用任何您所选择的名称,只要它的延伸档为 .mc。例如,MyMsgs.mc。

您的 .mc文件被架构为一系列的讯息项目,并以任意的顺序安排。请参阅《Platform SDK》文件以取得完整的讯息文件之语法。以下为一个讯息文件的范例:

;/**************************************************************
;Module name:MyMsgs.mc
;**************************************************************/
;//*********************MESSAGE SECTION ***********************
MessageIdTypedef=DWORD
MessageId=0x1
SymbolicName=MSG_DATE
Language=English
Today’s date is %1. %%536871912
.
MessageId=0x2
SymbolicName=MSG_TIME
Language=English
The current time is %1. %%536871912
.
MessageId=
SymbolicName=MSG_SEC
Language=English
The seconds are %1. %%536871912
.
;//****************STRING PARAMETERS SECTION ******************
MessageIdTypedef=DWORD
MessageId=0x100
Language=English
I hope you enjoy today.
.
;//************************END OF FILE ************************


 

 图6-3 事件记录的架构,包含建立一个讯息DLL的步骤

MessageId是与文字关联的识别码。注意到您可以在讯息文件中为这个MessageId栏位省略一个明确的值。这将会导致编译器产生比之前的讯息识别码多的识别码。SymbolicName栏位参考到产生之标头文件中将被使用的已定义巨集。您可以引入被产生的标头档与您的事件回报控制码,并且使用被SymbolicName栏位定义的巨集去确认一个讯息。Language行定义了该讯息使用的语言。您可以使用此方法建立一个为单一讯息识别码鉴定拥有若干语言支援的单一的讯息资源。因为讯息可能是几行长,所以您会在一个单一时期使用一行来指示讯息的结尾。注意此处需要一个延长的期间,而且在您的 .mc文件中的最后一个项目应该在延长期后回传Return键(Carriage Return)。

当您使用ReportEvent记录一个事件时,dwEventID参数指定了事件的识别码。后来事件检视器嵌入式管理单元会使用事件识别码的值搜寻人类可阅读字串之事件讯息文件。同样地,ReportEvent的wCategory参数会与一个在类别讯息文件中识别码相同的讯息关联。

如您所见,讯息文件的语法相当的简单,但是,有些方面需要某些附加的讨论。如前所述,事件记录的主要目的之一即是语言的独立性。讯息资源允许您容易地为了多种语言所包含的讯息。为了达成目的,您只需在被要求的语言内的讯息后面加入一个额外的Language行至每一个讯息的项目即可。它也常被用来将不同的语言编译至相同的讯息DLL中。例如,您的专案可能包含了一个MsgEnglish.dll和一个MsgFrench.dll。只要为您的讯息文件将这二个DLL含入登录项目,事件检视器嵌入式管理单元就可以找到适当的语言所代表讯息。

到目前为止,您可能会认为对于任何给定的事件识别码来说,事件检视器会报告一个不变的细节讯息。如果您认为由编译时期定义的静态字串所组成的一个事件记录机制不是非常有用,那么您是正确的。我们需要对相同的识别码但是为不同文字之回报多个事件的能力。例如,如果您的应用程序回报它无法开启一个文件,您想要使它在事件的说明文字中动态地引入被指定的文件名称。您也许会猜想事件记录的解决方法即是位于ReportEvent内的ppszString参数。

当您建立一个讯息时,该讯息可能包含了指示特定事件字串应被放置在何处的特别字元序列。例如,以下的事件字串指示了二个可置换的字串:

"The file %1 was replaced with the file %2"

如果您使用ReportEvent的ppszStrings传递一个包含二个字串的阵列,事件检视器嵌入式管理单元会使用「%1」取代第一个字串,「%2」则取代第二个字串。

通常,您应该只传递与语言无关的字串值,例如数字、文件名称以及其他资源的名称。尽管您可以传递任何文字而事件检视器嵌入式管理单元会取代它,但是例如传递一个英文片语时,便会破坏事件记录的语言独立性。

可以与扩充的字串一起使用参数代替来从参数讯息文件包含附加的细节字串。当扩充事件字串时,一个「%%」字元序列会被附加在一个指示参数代替的数字后。例如,以下的事件字串指示一个可取代的参数字串:

"This is an example %%237"

当扩充此字串时,事件检视器嵌入式管理单元会搜寻一个识别码为237的字串,且以参数字串取代事件字串的「%%237」之参数讯息文件的讯息表格资源参数。

此外,参数扩充在字串扩充后才被执行,它考虑到复杂的扩充情形。假设讯息字串「Replace with a dynamic parameter %%%1」与一个值为「237」的字串会被传递至ReportEvent。事件检视器嵌入式管理单元会先取代「%1」,然后继续使用在参数讯息文件中有237的识别码取代「%%237」。

如果您将您的事件来源之ParameterMessageFile登录值设定至Kernel32.dll,那么您可以使用从GetLastError回传的值而动态地插入错误讯息文字。例如,假设Kernel32.dll是您的参数讯息文件,而您的讯息字串是「GetLastError() returned the following error: %%%1」。您可以用一个单一字串值「5」呼叫ReportEvent以指示为拒绝存取。事件讯息的结果将会是:

"GetLastError()returned the following error:Access is denied".

能在您的事件说明中插入最后的错误讯息文字,确实是一个有用的特性。

编译您的讯息
 

在您建立 .mc文件后,您需要使用附加在Microsoft Visual Studio中的Message Compiler(Mc.exe)来编译它。以下的文字显示编译器的使用方法:

C:\>mc.exe
Microsoft (R) Message Compiler Version 1.00.5239
Copyright (c) Microsoft Corp 1992-1995.All rights reserved.
用法:MC [-?vcdwso] [-m maxmsglen] [-h dirspec] [-e extension]
[-r dirspec] [-x dbgFileSpec] [-u] [-U] filename.mc
-? - 显示此讯息。
-v - 产生冗长的输出。
-c - 在所有的讯息识别码中设定Customer位元。
-d - 标头档中的FACILTY与SEVERITY值,以十进制值表示。
设定标头中的讯息值为以十进制初始。
-w - 如果讯息文字中包含非OS/2相容之插入物时,发出警告。
-s - 插入符号连结名称以视为每一个讯息的第一行。
-o - 产生OLE2标头档(使用HRESULT定义取代状态控制码定义)。
-m maxmsglen - 如果任何讯息的大小超过maxmsglen所设定之字元数时,产生一个警告。
-h pathspec - 设定建立C含入文件之路径,预设值是 .\。
-e extension - 指定档头文件之延伸名称。
1到3个字元。
-r pathspec - 设定建立RC含入文件与含入之二进位码讯息资源文件的路径。
预设值是 .\。
-x pathspec - 设定建立 .dbg之讯息识别码与符号名称对应的C含入文件路径。
-u - 输入文件为Unicode格式。
-U - 在 .BIN文件中的讯息应为Unicode。
filename.mc - 设定一个用于编译的讯息文字文件名称。
产生的文件使得保存(Archive)位元被清除。

讯息编译器会剖析您的 .mc文件并产生叁个文件:

  •  MSG00001.bin 此文件包含了所有二进位码格式的讯息字串。它也包含了对应一个讯息识别号码至字串的资讯。
     
  •  MyMsgs.rc 此资源指令码文件中只有一个参考至包含在MSG00001.bin文件的二进位码讯息。
     
  •  MyMsgs.h 此文件是一个C/C++ 标头文件,包含了出现在 .mc文件之任何符号名称的#define。您应该在您的原始码模组中呼叫ReportEvent,以含入此文件。
     

资源文件
 

在经由讯息编译器执行MyMsgs.mc后,我用一个非常简单的资源指令档(MyMsgs.rc)来结束它,该文件看起来像图6-4所示的内容一样:


 

 图6-4 一个被讯息编译器产生的资源指令档

您可能已经很熟悉图示、点阵图以及对话方块范本的资源,一个讯息表格不过是资源的另一种型别而已。就像是图示与点阵图,其讯息表格资源是在资源指令中参照的二进位文件,而对话方块范本与功能表范本则嵌入在指令档中。

如果您在Platform SDK中开启WinUser.h标头文件,您将会发现以下被定义之设备符号:

#define RT_CURSOR MAKEINTRESOURCE(1)
#define RT_BITMAP MAKEINTRESOURCE(2)
#define RT_ICON MAKEINTRESOURCE(3)
#define RT_MENU MAKEINTRESOURCE(4)
#define RT_DIALOG MAKEINTRESOURCE(5)
#define RT_STRING MAKEINTRESOURCE(6)
#define RT_FONTDIR MAKEINTRESOURCE(7)
#define RT_FONT MAKEINTRESOURCE(8)
#define RT_ACCELERATOR MAKEINTRESOURCE(9)
#define RT_RCDATA MAKEINTRESOURCE(10)
#define RT_MESSAGETABLE MAKEINTRESOURCE(11) // 看这里!
#define RT_GROUP_CURSOR MAKEINTRESOURCE(12)
#define RT_GROUP_ICON MAKEINTRESOURCE(14)
#define RT_VERSION MAKEINTRESOURCE(16)
#define RT_DLGINCLUDE MAKEINTRESOURCE(17)
#define RT_PLUGPLAY MAKEINTRESOURCE(19)
#define RT_VXD MAKEINTRESOURCE(20)
#define RT_ANICURSOR MAKEINTRESOURCE(21)
#define RT_ANIICON MAKEINTRESOURCE(22)
#define RT_HTML MAKEINTRESOURCE(23)

讯息表格资源已经被指定为号码11。当您加入资源至一个资源指令码(.rc)档时,您必须为每一个资源的类型指定一个唯一的数字。例如,我可以加入一个识别码为53的图示至我的资源指令码中,然后加入另一个识别码为172的图示至我的资源指令码中。这些数字实际上并不重要,因为它们是唯一的。

讯息表格资源与其他资源的工作有些不同。一个资源指令码文件可以只拥有一个讯息表格资源,而且该资源必须被指定识别码为1。如果您指定一个不同的识别码给讯息表格资源,在使用像事件检视器嵌入式管理单元时将无法将事件与类别识别码转换成人类可阅读的字串。


说明

如果您的讯息DLL或EXE包含了附加的资源,只要将被讯息编译器产生的 .rc文件内容复制至您拥有的 .rc文件中即可。然后您可以丢弃被讯息编译器产生的 .rc档。


使用Visual Studio建立一个讯息文件之专案
 

虽然您可以在每一次改变您的讯息时,手动地执行讯息编译器工具,然而那么做会变得麻烦且乏味。我强烈地建议为您的DLL或EXE加入讯息编译的步骤至您的Visual Studio专案中。由于一些未知的原因,Visual Studio环境无法察觉到这个讯息编译器与 .mc文件,所以您需要在专案中加入自订的建立步骤。步骤如下:

  1. 将您的 .mc文件加入EXE或DLL专案中。
  2. 显示Project Settings对话方块。
  3. 在Custom Build页签中选择 .mc文件。
  4. 在说明文字方块中设定您想要的说明文字。我通常会使用「Message Compiler」。
  5. 在Commands文字方块中设定「mc -s -U -h $(ProjDir) -r $(ProjDir) $(InputName)」与「del $(ProjDir)\$(InputName).rc」。
  6. 在Outputs部份,加入二个项目:「$(InputName).h」与「Msg00001.bin」。该对话方块看起来应该像图6-5所示的内容。按下OK按钮。
     

     图6-5 为 .mc文件加入讯息编译器命令后的Project Settings对话方块
  7. 在呼叫ReportEvent函数的原始码文件中含入被产生的标头档。
  8. 在您专案的 .rc档中加入一行包含资源数字为1、资源类型为11,以及文件名称为MSG00001.bin的内容。例如:
    1 11 MSG00001.bin

在执行此步骤后,若要产生最后的EXE或DLL,Visual Studio就会知道如何去编译您的讯息文字档与含入的资源。注意到如果您建立了一个只含有资源而没有程序代码的DLL模组,那么您应该使用 /NOENTRY连结器开关来防止连接一个进入点并且减少产生模组的大小。

您必须去编写的唯一程序代码即是对登录的设定部份,因此事件检视器嵌入式管理单元能找到这个讯息EXE或者DLL的位置。

MsgTableDump范例应用程序
 

MsgTableDump范例应用程序(「06 MsgTableDump.exe」)是一个简单的工具程序,它可以开启一个被包含在EXE或DLL中的讯息表格资源并且倾印在表格中的所有字串。此范例应用程序的原始程序代码与资源档存放在随书光碟中的06-MsgTableDump目录中。

这个程序代码非常简单易懂。它把讯息表格资源置于特定的模组并沿着每个存在唯读之编辑控制项中的字串而走。图6-6显示了当MsgTableDump在Kernel32.dll上执行时,其输出的情形。


 

 图6-6 MsgTableDump范例应用程序显示kernel32.dll的讯息字串

AppLog范例应用程序
 

AppLog范例应用程序(「06 AppLog.exe」)显示于列表6-1中,它说明从一个应用程序回报事件的内容。它的原始码与资源文件存放在附赠光碟中的06-AppLog目录中。AppLog范例应用程序被实作如同一个标准的应用程序一样,但是如果控制码属于一个服务的话,它便会与回报事件的控制码相同。

当AppLog启动时,它会开启一个本机的应用程序记录并且加入一个指示应用程序己被启动执行的项目。AppLog也会在终止前加入一个项目。在正常的情形下,为了增进执行效能并且不浪费事件记录资料库空间,您不会将这些资讯事件类型回报至事件记录中。

一旦AppLog执行起来,您便可以在编辑控制项中键入一个Win32之错误码并按下Simulate Error按钮。此按钮会使AppLog增加一个事件至系统的应用程序记录档中。当AppLog执行时,您可以依您的喜好而模拟许多Win32的错误。为了察看错误的情形,可以按下Open Event Viewer按钮,以使事件检视器嵌入式管理单元在MMC中显示那些产生错误的原因。

如果您使用事件检视器嵌入式管理单元来看那些项目,您将会看到类别与讯息识别数字与可置换的字串值(被您模拟的错误码数字)。事件检视器无法将类别与讯息识别码对应到它们的英文字串中,直到登录被适当地设定为止。若要将讯息文件模组资讯安装至登录中,可以按下Install Event Message File In Registry按钮。然后回去事件检视器嵌入式管理单元中察看被AppLog加入的事件。此时,您应该会看见识别号码已被转换至适当的字串中。AppLog也允许您按下Remove Event Message File From Registry按钮去删除登录资讯。图6-7显示了AppLog模拟一个错误的情形。


 

 图6-7 AppLog范例应用程序模拟 "Access is denied" (5) 之Win32错误码
AppLog.cpp
/********************************************************************
模组:AppLog.cpp
通告:Copyright (c)2000 Jeffrey Richter
********************************************************************/
#include "..\CmnHdr.h" // 请察看附录A
#include
#define EVENTLOG_IMPL
#include "EventLog.h"
#include "Resource.h"
#include "AppLogMsgs.h" // 被MC.exe产生
///////////////////////////////////////////////////////////////////////////////
CEventLog g_EventLog(TEXT("AppLog "));
///////////////////////////////////////////////////////////////////////////////
BOOL Dlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam) {
chSETDLGICONS(hwnd, IDI_APPLOG);
return(TRUE);
}
///////////////////////////////////////////////////////////////////////////////
void Dlg_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify) {
switch (id) {
case IDCANCEL:
EndDialog(hwnd, id);
break;
case IDC_SPAWNEVENTVIEWER:
// 产生事件检视

TAG: Windows2000 事件 应用程序 开发 服务器端 记录 设计指南

 

评分:0

我来说两句

seccode