向操做系统的事件管理器报告重大信息是一种很是有用的方式,特别是对于没有界面的后台服务而言。若是你对Windows编程有必定了解,应该很快就能想到使用ReportEvent这个API,而后快速写出下面的程序: 编程
VOID TestReportEvent1() { // Get a handle to the event log. HANDLE h = RegisterEventSource(NULL, // Use local computer. _T(“EventLogDemo”)); // Event source name. if (h == NULL) { printf("Cannot register the event source."); return; } TCHAR szEventMsg[256] = _T("文件备份服务已启动."); LPCTSTR lpInput[] = {szEventMsg} ; if (!ReportEvent(h, // Event log handle. EVENTLOG_INFORMATION_TYPE, // Event type. NULL, // Event category. 1001, // Event identifier. NULL, // No user security identifier. 1, // Number of substitution strings. 0, // No data. lpInput, // Pointer to strings. NULL)) // No data. { printf("Cannot report the event."); } else { printf("Report Event Successfully.\n"); } DeregisterEventSource(h); return; }
这个程序运行正常,到事件管理器中查看,确实多了一条记录。 小程序
实际上咱们报告的内容只有红框中的一个字符串布局,可是在查看日志时,日志管理器会告诉咱们一堆东西,说事件描述未找到之类的,反正就是不太正常。数组
那正常的应该是什么样的呢?看下图:ide
干干净净,清清爽爽,这才是正确的结果。 布局
那如何实现这个效果呢?须要分下面几步来实现。 测试
1、编写mc文件 编码
mc文件是一个消息描述文件,它定义了消息的ID和消息的格式。不知道怎么写不要紧,参考MSDN中的例子,直接拷贝如下模板就好了。 spa
1 ; // ***** TestEventLog.mc ***** 2 3 ; // This is the header. 4 5 MessageIdTypedef=DWORD 6 7 SeverityNames=(Success=0x0:STATUS_SEVERITY_SUCCESS 8 Informational=0x1:STATUS_SEVERITY_INFORMATIONAL 9 Warning=0x2:STATUS_SEVERITY_WARNING 10 Error=0x3:STATUS_SEVERITY_ERROR 11 ) 12 13 14 FacilityNames=(System=0x0:FACILITY_SYSTEM 15 Runtime=0x2:FACILITY_RUNTIME 16 Stubs=0x3:FACILITY_STUBS 17 Io=0x4:FACILITY_IO_ERROR_CODE 18 ) 19 20 LanguageNames=(Chinese=0x804:MSG00804) 21 22 ; // The following are message definitions. 23 24 MessageId=1001 25 Severity=Informational 26 Facility=Runtime 27 SymbolicName=MSG_SERVICE_START 28 Language=Chinese 29 文件备份服务已启动. 30 . 31 32 MessageId=1002 33 Severity=Informational 34 Facility=Runtime 35 SymbolicName=MSG_SERVICE_STOP 36 Language=Chinese 37 文件备份服务已中止. 38 .
上面的部分不须要改动,LanguageNames往下才是本身要定义的消息内容。注意消息的内容并非以换行结束的,必须在后面起一个新行写上一个"."做为结束符(第30、38行),不然会提示 EventLog.mc(linenum) : error : Unterminated message definition,根据错误提示的行号进行修改就能够了。操作系统
一般来讲,程序报告给事件管理器的事件都是预先定义好的事件,好比服务启动了、中止了,或者出现了某错误等等。上面的mc文件中定义了两个消息,MessageId就是未来在事件管理器中看到的事件ID,Serverity表示事件的严重程度,能够从上面的SeverityNames中选一个,经常使用的是Informational表示成功或普通讯息,Warning为警告信息,Error为错误信息。SymbolicName即符号名称,它由用户根据本身的须要进行命名,好比服务启动事件能够命名为MSG_SERVICE_START,在编写代码时会用到这个名字,具体怎么用的,后面再讲。 命令行
这时读者可能会有一个疑问,就是这些消息内容都是固定的,可是实际运行过程当中会有一些变化的信息,好比本例做为一个文件备份服务,想在备份成功时报告成功的是哪一个文件,备份失败的时候报告操做失败的文件名称和错误代码,这些变化的信息怎么加入到消息内容中去呢?
事实上,事件消息的正文能够是固定的一句话,也能够是模板,其中须要替换的内容按顺序用%n替换,n的取值范围是1到99。
好比:将目标文件备份到 %1 时出错,错误代码为 %2.
在使用这个模板时,只须要提供两个变量就能够了,事件系统会自动分别替换模板中的%1和%2。具体如何操做,后面会讲到。
保存mc文件时可使用与操做系统相同的语言编码,也可使用Unicode编码。若是mc文件中使用了多种语言,那么最好使用Unicode编码保存该文件。
2、编译mc文件
Visual Studio提供了一个程序mc.exe专门用于编译mc文件。若是VS的可执行程序路径已经添加到了PATH环境变量中,那么直接在存放mc文件的目录中打开cmd窗口,执行以下命令(本例中文件名为EventLog.mc):
若是mc文件编写没有问题,那么编译经过时没有其它的额外提示,不然会提示你错误信息及错误所在的行号,根据具体提示进行修改就能够了。
若是mc文件的保存格式是Unicode的,这时的编译参数要变成mc –u –U EventLog.mc
第一个-u表示输入文件是Unicode编码,第二个-U表示输出文件要使用Unicode编码,若是软件支持多国语言,那最好是使用Unicode编码。
编译成功后,会生成至少三个文件,分别是一个h文件,一个rc文件,一个MSG开头的bin文件,具体文件名与mc文件中定义的LanguageNames有关,好比中文就是MSG00804.bin。
接下来要作的,就是把这个rc文件编译到程序中去,它将成为资源的一部分。一般有两种作法,一种是编译为纯资源dll,一种是编译到exe自己,选择哪一种纯属我的爱好。若是你的程序原本就是由多个模块组成的,那么就编译成dll好一些,若是你只有一个exe,不想额外携带dll,那就编译到exe自己。
编译为纯资源dll的方法仍然是使用命令行:
先编译rc文件,最后使用Link命令编译为纯资源dll。
若是要编译到exe,那么在exe自己的rc文件中添加一行:
#include "EventLog.rc"
就能够了。
EventLog.rc的内容其实很简单,以下:
LANGUAGE 0x4,0x2 1 11 MSG00804.bin
实际是一个类型为11,名称为1的资源自定义资源文件,文件指向MSG00804.bin。
因此,若是使用包含方式有问题的话,能够直接把上面两行内容添加到exe自己的rc文件中。
若是把mc文件加入到工程中的话,须要设置自定义编译操做(注意选择"全部设置",就不须要每一个编译配置都从新设置一次了,除非你须要不一样的配置选项)。
VC6设置以下(原谅我还在用VC6写一些小程序):
VS2010的设置以下:
小提示:
因为每次mc文件编译后会从新生成这几个文件,若是把这几个文件添加到了工程里就会致使IDE重复提示"文件已修改,是否从新加载"的状况。因此,只添加mc文件到工程中,但不要添加生成的h文件和rc文件。h文件只是不添加到工程中,包含使用是没有问题的。
3、注册事件源
这个按MSDN中的操做来就能够了。
具体来讲就是:
在KEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EventLog下面添加相应的键值。这里有几个大类,主要的是"Application","Security"和"System",分别对应于系统的三个日志频道。一般,咱们本身的应用程序报告的事件写在Application频道下,固然也能够定义本身的频道。
好比,测试程序名为TestEventLog,那么就注册到Application\TestEventLog下面,具体来讲就是添加几个键值,以下:
具体代码能够直接抄MSDN:
BOOL AddEventSource(LPCTSTR lpszChannelName, LPCTSTR lpszSourceName , LPCTSTR lpModulePath) { BOOL bResult = FALSE ; DWORD dwCategoryNum = 1; HKEY hk; DWORD dwData, dwDisp; TCHAR szBuf[MAX_PATH] = {0}; size_t cchSize = MAX_PATH; __try { // Create the event source as a subkey of the log. _stprintf(szBuf, _T("SYSTEM\\CurrentControlSet\\Services\\EventLog\\%s\\%s"), lpszChannelName, lpszSourceName); if (RegCreateKeyEx(HKEY_LOCAL_MACHINE, szBuf, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hk, &dwDisp)) { printf("Could not create the registry key.\n"); __leave; } // Set the name of the message file. if (RegSetValueEx(hk, // subkey handle _T("EventMessageFile"), // value name 0, // must be zero REG_EXPAND_SZ, // value type (LPBYTE) lpModulePath, // pointer to value data (DWORD) (lstrlen(lpModulePath)+1)*sizeof(TCHAR))) // data size { printf("Could not set the event message file.\n"); __leave; } // Set the supported event types. dwData = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_INFORMATION_TYPE; if (RegSetValueEx(hk, // subkey handle _T("TypesSupported"), // value name 0, // must be zero REG_DWORD, // value type (LPBYTE) &dwData, // pointer to value data sizeof(DWORD))) // length of value data { printf("Could not set the supported types.\n"); __leave; } // Set the category message file and number of categories. if (RegSetValueEx(hk, // subkey handle _T("CategoryMessageFile"), // value name 0, // must be zero REG_EXPAND_SZ, // value type (LPBYTE) lpModulePath, // pointer to value data (DWORD) (lstrlen(lpModulePath)+1)*sizeof(TCHAR))) // data size { printf("Could not set the category message file.\n"); __leave; } if (RegSetValueEx(hk, // subkey handle _T("CategoryCount"), // value name 0, // must be zero REG_DWORD, // value type (LPBYTE) &dwCategoryNum, // pointer to value data sizeof(DWORD))) // length of value data { printf("Could not set the category count.\n"); __leave; } bResult = TRUE; } __finally { if (hk != NULL) { RegCloseKey(hk); } } return bResult; }
而后调用:
AddEventSource(_T("Application"),_T("EventLogDemo"),szModulePath);
szModulePath就是包含了消息定义资源的dll或者exe的路径。
这样,事件源就注册好了。
4、如何报告日志
到这一步,才算作好了全部的准备工做。
报告事件仍然使用如下三个API:
RegisterEventSource获得事件管理器的句柄
ReportEvent进行报告
DeregisterEventSource关闭事件源句柄
注意再来看一下ReportEvent的参数:
BOOL ReportEvent( __in HANDLE hEventLog, //事件源句柄 __in WORD wType, //消息类型:信息、警告、错误 __in WORD wCategory, __in DWORD dwEventID,//事件ID __in PSID lpUserSid, __in WORD wNumStrings, //要插入到模板中的字符串数量 __in DWORD dwDataSize, __in LPCTSTR* lpStrings, //变量字符串组 __in LPVOID lpRawData );
其中wNumStrings就是模板中要插入的变量字符串数量,lpStrings就是变量字符串数组,在显示日志内容时,事件查看器会使用数组中的内容依次替换掉事件字符串模板中的%一、%2这些变量;而dwEventID就是前面在mc文件中定义的MessageId,要使用它,须要包含mc文件编译生成的那个头文件。它的部份内容以下:
除了模板以外,还能够有附加数据,以二进制形式提供,须要使用到dwDataSize和lpRawData两个参数,相信对于这两个参数如何使用应该不会陌生。
因为每次报告事件都须要获得事件源的句柄,因此能够把这一过程简单包装下:
BOOL ReportEventDebugMsg(WORD EventType,DWORD dwEventId,WORD cInserts,LPCTSTR *lpStrings) { BOOL bResult = FALSE ; HANDLE h = RegisterEventSource(NULL, // Use local computer. g_szEventSourceName); // Event source name. if (h == NULL) { printf("Cannot register the event source."); return FALSE; } SetLastError(ERROR_SUCCESS); bResult = ReportEvent(h,EventType,NULL,dwEventId,NULL,cInserts,0,lpStrings,NULL); printf("Report Event %s , Errcode = %d .\n",bResult?"Success":"Failed",GetLastError()); DeregisterEventSource(h); return bResult; }
接下来能够这么调用(根据前面的定义,共报告服务启动、操做成功、操做失败、服务中止四个事件):
//正确的报告方式 VOID TestReportEvent3() { //Report Service Start ReportEventDebugMsg(EVENTLOG_INFORMATION_TYPE,MSG_SERVICE_START,0,NULL); //使用模板报告特定事件 LPCTSTR lpInputStrings[2] = {0}; TCHAR szFilePath[MAX_PATH] = _T("D:\\test.dat"); TCHAR szErrCode[32] = _T("32"); //Report something Success lpInputStrings[0] = szFilePath; ReportEventDebugMsg(EVENTLOG_INFORMATION_TYPE,MSG_BACKUP_DONE,1,lpInputStrings); //Report something Error lpInputStrings[0] = szFilePath; lpInputStrings[1] = szErrCode; ReportEventDebugMsg(EVENTLOG_ERROR_TYPE,MSG_BACKUP_FAIL,2,lpInputStrings); //Report Service Stop ReportEventDebugMsg(EVENTLOG_INFORMATION_TYPE,MSG_SERVICE_STOP,0,NULL); printf("Report Finished.\n"); }
最后来看一下效果:
共产生了4个事件。
再看一下其中含有变量的两个事件:
达到了预期效果,这才是完整的、正确的ReportEvent的使用方式。