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.\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的使用方式。

相關文章
相關標籤/搜索