• ReportEvent的正确使用方式


    向操作系统的事件管理器报告重大信息是一种非常有用的方式,特别是对于没有界面的后台服务而言。如果你对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.
    ");
        }
        
        DeregisterEventSource(h); 
        return;
    }

    这个程序运行正常,到事件管理器中查看,确实多了一条记录。

     

    实际上我们报告的内容只有红框中的一个字符串布局,但是在查看日志时,日志管理器会告诉我们一堆东西,说事件描述未找到之类的,反正就是不太正常。

    那正常的应该是什么样的呢?看下图:

     

    干干净净,清清爽爽,这才是正确的结果。

    那如何实现这个效果呢?需要分下面几步来实现。

    一、编写mc文件

    mc文件是一个消息描述文件,它定义了消息的ID和消息的格式。不知道怎么写没关系,参考MSDN中的例子,直接拷贝以下模板就行了。

     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编码保存该文件。

    二、编译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文件只是不添加到工程中,包含使用是没有问题的。

    三、注册事件源

    这个按MSDN中的操作来就可以了。

    具体来说就是:

    在KEY_LOCAL_MACHINESYSTEMCurrentControlSetServicesEventLog下面添加相应的键值。这里有几个大类,主要的是"Application","Security"和"System",分别对应于系统的三个日志频道。通常,我们自己的应用程序报告的事件写在Application频道下,当然也可以定义自己的频道。

    比如,测试程序名为TestEventLog,那么就注册到ApplicationTestEventLog下面,具体来说就是添加几个键值,如下:

    具体代码可以直接抄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.
    "); 
                __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.
    "); 
                __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.
    "); 
                __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.
    "); 
                __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.
    "); 
                __leave;
            }
    
            bResult = TRUE;
        }
        __finally
        {
            if (hk != NULL)
            {
                RegCloseKey(hk);
            }
        }
        
        return bResult;
    }

    然后调用:

    AddEventSource(_T("Application"),_T("EventLogDemo"),szModulePath);

    szModulePath就是包含了消息定义资源的dll或者exe的路径。

    这样,事件源就注册好了。

    四、如何报告日志

    到这一步,才算做好了所有的准备工作。

    报告事件仍然使用以下三个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就是变量字符串数组,在显示日志内容时,事件查看器会使用数组中的内容依次替换掉事件字符串模板中的%1、%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 .
    ",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.
    ");
    }
    

     最后来看一下效果:

    共产生了4个事件。

    再看一下其中含有变量的两个事件:

    达到了预期效果,这才是完整的、正确的ReportEvent的使用方式。

  • 相关阅读:
    2021hdu多校第二场补题
    ORACLE数据库之SQL语言基础
    EXCEL应用
    element-ui闭坑
    题解 CF1559E 【Mocha and Stars】
    题解 CF1530D 【Secret Santa】
    题解 CF1209E2 【Rotate Columns (hard version)】
    题解 CF761E 【Dasha and Puzzle】
    题解 UVA437 【巴比伦塔 The Tower of Babylon】
    题解 P6100 【[USACO19FEB]Painting the Barn G】
  • 原文地址:https://www.cnblogs.com/achillis/p/10460465.html
Copyright © 2020-2023  润新知