Docelowy OS: Windows XP.

Jestem w trakcie wykańczania skromnego programu powiadamiajacego "dymkiem" m.in. o nowych rekordach w systemowym eventlogu. Natrafiłem na mały problem w funkcji formatującej wiadomość (FormatMessageForEvent) z jakimiś resztkami znaków - u mnie jest to pojedyńczy znak procenta:

DCOM got error "%The dependency service or group failed to start. " attempting to start the service blah with arguments "" in order to run the server:{blah}

Już na prawdę nie wiem co jest nie tak, dodałem ręcznego usuwacza tego znaku procenta - jest w #if 0 pod koniec funkcji FormatMessageForEvent.

Program demonstracyjny wyświetla wszystkie rekordy ze źródła DCOM. Jeżeli nie wyświetli nic a byłbyś tak uprzejmy by wyłączyć i zablokować usługę stisvc, a tuż po tym uruchomić mspaint, wtedy taki błąd się pojawi w logu, no i w programie po ponownym jego uruchomieniu.

#include "stdafx.h"
#include <windows.h>
#include <wchar.h>
// wszystko poniżej w unicode

const int BUFFER_SIZE = 0x7ffff;
const int FORMAT_MESSAGE_FLAGS_COMMON = FORMAT_MESSAGE_MAX_WIDTH_MASK | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_ALLOCATE_BUFFER;
const int FORMAT_MESSAGE_FLAGS  = FORMAT_MESSAGE_FLAGS_COMMON | FORMAT_MESSAGE_ARGUMENT_ARRAY;
const int FORMAT_MESSAGE_FLAGS2 = FORMAT_MESSAGE_FLAGS_COMMON;


static void* operator new(size_t len)
{
	return HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, len);
}
static void operator delete (void *m)
{
	HeapFree(GetProcessHeap(), 0, m);
}


struct MODULENODE
{
	MODULENODE *next;
	WCHAR wszPath[1];
};

struct EVENTDATA
{
	MODULENODE *pMessageFiles;
	MODULENODE *pParameterMessageFiles;
};

MODULENODE* LoadEventStrings(HKEY hkSource, LPWSTR pwszValueName, DWORD dwBytesNeeded)
{
	// read pwszValueName value from hkSource key, split strings separated by ';' into MODULENODE list
	WCHAR wszExpanded[MAX_PATH];
	MODULENODE *modules = NULL;

	LPWSTR pwszModule = (LPWSTR)new char[dwBytesNeeded];

	if (pwszModule)
	{
		if (!RegQueryValueEx(hkSource, pwszValueName, 0, NULL, (LPBYTE)pwszModule, &dwBytesNeeded))
		{
			WCHAR *next = wcschr(pwszModule, ';');
			while (1)
			{
				if (next) *next = NULL;
				// append module path
				ExpandEnvironmentStrings(pwszModule, wszExpanded, MAX_PATH);
				// sizeof(MODULENODE) includes space for string terminating NULL.
				MODULENODE *node = (MODULENODE*)new char[sizeof(MODULENODE) + (wcslen(wszExpanded)*2)];
				if (node)
				{
					node->next = modules;
					modules = node;
					wcscpy(node->wszPath, wszExpanded);
				}
				// done, seek to next module
				if (!next) break;
				pwszModule = next+1;
				next = wcschr(pwszModule, ';');
			}
		}
	}
	return modules;
}
 
void InitEventData(LPWSTR pwszSource, EVENTDATA *data)
{
	data->pMessageFiles = NULL;
	data->pParameterMessageFiles = NULL;

	WCHAR wszPath[MAX_PATH];
	HKEY hkSource;
	swprintf(wszPath, L"SYSTEM\\CurrentControlSet\\Services\\Eventlog\\System\\%s", pwszSource);

	if (!RegOpenKeyEx(HKEY_LOCAL_MACHINE, wszPath, 0, KEY_READ, &hkSource))
	{
		DWORD cchMessageFiles = 0;
		DWORD cchParameterMessageFiles = 0;
		RegQueryValueEx(hkSource, L"EventMessageFile", 0, NULL, (LPBYTE)wszPath, &cchMessageFiles);
		RegQueryValueEx(hkSource, L"ParameterMessageFile", 0, NULL, (LPBYTE)wszPath, &cchParameterMessageFiles);

		if (cchMessageFiles)
		{
			data->pMessageFiles = LoadEventStrings(hkSource, L"EventMessageFile", cchMessageFiles);
		}
		if (cchParameterMessageFiles)
		{
			data->pParameterMessageFiles = LoadEventStrings(hkSource, L"ParameterMessageFile", cchParameterMessageFiles);
		}

		RegCloseKey(hkSource);
	}
}

void FreeModuleList(MODULENODE *module)
{
	while (module)
	{
		MODULENODE *next = module->next;
		delete module;
		module = next;
	}
}


void FreeEventData(EVENTDATA *data)
{
	FreeModuleList(data->pMessageFiles);
	FreeModuleList(data->pParameterMessageFiles);
}


LPWSTR* CreateArgumentsArray(EVENTLOGRECORD *pevlr, HMODULE hParameterMessageFileModule)
{
	LPWSTR *pArguments = NULL;
	if (pevlr->NumStrings > 0)
	{
		pArguments = new LPWSTR[pevlr->NumStrings];
		if (pArguments)
		{
			WCHAR *pString = (WCHAR*)((char*)pevlr + pevlr->StringOffset);

			for (int z = 0; z < pevlr->NumStrings; z++)
			{
				size_t cch = wcslen(pString)+1;
				pArguments[z] = pString;

				// check if this is a number
				if (hParameterMessageFileModule && iswdigit(*pString))
				{
					LPWSTR pwszMessage;
					if (FormatMessage(FORMAT_MESSAGE_FLAGS2, hParameterMessageFileModule, _wtoi(pString), 0, (LPWSTR)&pwszMessage, 0, 0))
					{
						pArguments[z] = pwszMessage;
					}
				}
				// next string
				pString = &pString[cch];
			}
		}
	}
	return pArguments;
}

void FreeArgumentsArray(LPWSTR *pArguments, EVENTLOGRECORD *pevlr)
{
	if (pArguments)
	{
		char *pevlrMax = (char*)pevlr + pevlr->Length;
		for (int a=0; a<pevlr->NumStrings; a++)
		{
			LPWSTR pwszArgument = pArguments[a];
			// delete pwszArgument if it points outside EVENTLOGRECORD
			if (pwszArgument && (((char*)pwszArgument < (char*)pevlr) || ((char*)pwszArgument >= pevlrMax)))
			{
				LocalFree(pwszArgument);
			}
		}
		delete pArguments;
	}
}


BOOL FormatMessageForEvent(EVENTLOGRECORD *pevlr, LPWSTR *ppv)
{
	BOOL      fMessageCreated = FALSE;
	LPWSTR   *pArguments = NULL;
	EVENTDATA evdata;
	HMODULE   hMessageFileModule;
	HMODULE   hParameterMessageFileModule;

	WCHAR *pszSource = (WCHAR*)((char*)pevlr + sizeof(EVENTLOGRECORD));
	InitEventData(pszSource, &evdata);

	// iterate MessageFile modules
	MODULENODE *pMessageFile = evdata.pMessageFiles;

	while (pMessageFile && !fMessageCreated)
	{
		// load first/next message module
		hMessageFileModule = LoadLibraryEx(pMessageFile->wszPath, 0, DONT_RESOLVE_DLL_REFERENCES);
		if (hMessageFileModule)
		{
			// check if current event specifies a module with arguments
			// MSDN: When the "ParameterMessageFile" value is present, the client SHOULD attempt to load the resource file
			MODULENODE *pParameterMessageFiles = evdata.pParameterMessageFiles;
			if (pParameterMessageFiles)
			{
				// iterate ParameterMessage modules
				while (pParameterMessageFiles && !fMessageCreated)
				{
					hParameterMessageFileModule = LoadLibraryEx(pParameterMessageFiles->wszPath, 0, DONT_RESOLVE_DLL_REFERENCES);
					if (hParameterMessageFileModule)
					{
						pArguments = CreateArgumentsArray(pevlr, hParameterMessageFileModule);
						// attempt to format the message
						fMessageCreated = FormatMessage(FORMAT_MESSAGE_FLAGS, hMessageFileModule, pevlr->EventID, 0, (LPWSTR)ppv, 256, (va_list*)pArguments);
						FreeArgumentsArray(pArguments, pevlr);
						pArguments = NULL;
						FreeLibrary(hParameterMessageFileModule);
					}
					pParameterMessageFiles = pParameterMessageFiles->next;
				}
			}
			else
			{
				if (!pArguments)
				{
					pArguments = CreateArgumentsArray(pevlr, 0);
				}
				fMessageCreated = FormatMessage(FORMAT_MESSAGE_FLAGS, hMessageFileModule, pevlr->EventID, 0, (LPWSTR)ppv, 256, (va_list*)pArguments);
			}
			FreeLibrary(hMessageFileModule);
		}
		pMessageFile = pMessageFile->next;
	}

	if (!fMessageCreated && evdata.pParameterMessageFiles)
	{
		// ignore evdata.pParameterMessageFiles
		pMessageFile = evdata.pMessageFiles;
		while (pMessageFile && !fMessageCreated)
		{
			hMessageFileModule = LoadLibraryEx(pMessageFile->wszPath, 0, DONT_RESOLVE_DLL_REFERENCES);
			if (hMessageFileModule)
			{
				if (!pArguments)
				{
					pArguments = CreateArgumentsArray(pevlr, 0);
				}
				fMessageCreated = FormatMessage(FORMAT_MESSAGE_FLAGS, hMessageFileModule, pevlr->EventID, 0, (LPWSTR)ppv, 256, (va_list*)pArguments);
				FreeLibrary(hMessageFileModule);
			}
			pMessageFile = pMessageFile->next;
		}
	}

#if 0
	if (fMessageCreated)
	{
		// remove garbages
		WCHAR *pwszGarbage = wcsstr(*ppv, L"\"%");
		if (pwszGarbage)
		{
			wcscpy(pwszGarbage+1, pwszGarbage+2);
		}
	}
#endif

	FreeEventData(&evdata);
	FreeArgumentsArray(pArguments, pevlr);

	return fMessageCreated;
}


void ReadEventLogEntry(EVENTLOGRECORD *pevlr)
{
	WCHAR *pszSource  = (WCHAR*)((char*)pevlr + sizeof(EVENTLOGRECORD));
	WCHAR *pszMessage;

	if (!_wcsicmp(pszSource, L"DCOM"))
	{
		if (FormatMessageForEvent(pevlr, &pszMessage))
		{
			_putws(pszMessage);
			LocalFree(pszMessage);
		}
	}
}

void ReadEventLogEntries(HANDLE hEventLog)
{
	DWORD dwRead, dwNeeded;
	void *buffer = new char[BUFFER_SIZE];
	if (buffer)
	{
		while (ReadEventLog(hEventLog, EVENTLOG_SEQUENTIAL_READ|EVENTLOG_FORWARDS_READ, 0, buffer, BUFFER_SIZE, &dwRead, &dwNeeded))
		{
			EVENTLOGRECORD *pevlr = (EVENTLOGRECORD*)buffer;
			while (dwRead > 0)
			{
				ReadEventLogEntry(pevlr);

				dwRead -= pevlr->Length;
				pevlr = (EVENTLOGRECORD*)((char*)pevlr + pevlr->Length);
			}
		}
		delete buffer;
	}
}

int _tmain(int argc, _TCHAR* argv[])
{
	HANDLE hEventLog = OpenEventLog(NULL, L"System");
	ReadEventLogEntries(hEventLog);
	CloseEventLog(hEventLog);
	_wsystem(L"pause");
	return 0;
}