Monthly Archives: January 2006

How to convert a UNICODE string to an ANSI C null-terminated string ?

In some cases, you may need to convert a unicode string into an ANSI C null-terminated string. The WideCharToMultiByte function allows you to do this.

In the following example, I have used the ReadDirectoryChangesW function to detect a file modification in a directory. The name of the modified file is stored in UNICODE format. I want to convert it to  a standard C string.

PFILE_NOTIFY_INFORMATION pstFileNotif;

// Initialization of pstFileNotif

char szNotifFilename[ MAX_PATH ] = { 0 };
int iNbChar = WideCharToMultiByte(
CP_OEMCP,
NULL,
pstFileNotif->FileName,
pstFileNotif->FileNameLength / sizeof( WCHAR ),
szNotifFilename,
sizeof( szNotifFilename ) / sizeof( char ),
NULL, NULL ) ;

if ( iNbCar == 0 )
{
// Error
}

Be careful : the size of the 2 strings is not their length in bytes, but their length in characters (a UNICODE character takes 2 bytes)

How to automatically detect a file modification ?

You may have wondered how these antivirus programs detect a modification on any file as soon a it is done. Instead of spending their time checking all the files, they use an event approach provided by microsoft.

The ReadDirectoryChangesW method allows you to monitor a given directory, and to wait (without consumming processos resource) until a change occurs.
The sample code below illustrates how to detect a change (modification of the last time written attribute) of a given file.

char szFilename[ MAX_PATH ] = “monitoredfile.txt”; // The file monitoredchar szFiledir[ MAX_PATH ] = “.”;  // Monitoring the previous file in the current directorym_hMonitoredDir = CreateFile( szFiledir, FILE_LIST_DIRECTORY,
FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS, NULL );

if ( m_hMonitoredDir == INVALID_HANDLE_VALUE )
{
DWORD dwErr = GetLastError();
return;
}

char szBuf[ MAX_PATH ];
DWORD dwBytesRead = 0;

// Waiting file changes
while ( ReadDirectoryChangesW( m_hMonitoredDir, szBuf, MAX_PATH,
FALSE, FILE_NOTIFY_CHANGE_LAST_WRITE,
&dwBytesRead, NULL, NULL )  )
{
PFILE_NOTIFY_INFORMATION pstFileNotif = (PFILE_NOTIFY_INFORMATION)( szBuf );
if ( pstFileNotif->Action == FILE_ACTION_MODIFIED )
{
char szNotifFilename[ MAX_PATH ] = { 0 };
if ( int iNotifFilenameLen = WideCharToMultiByte( CP_OEMCP, NULL,
pstFileNotif->FileName,
pstFileNotif->FileNameLength / sizeof( WCHAR ),
szNotifFilename, sizeof( szNotifFilename ) / sizeof( char ),
NULL, NULL ) )
{
if ( strcmp( szFilename, szNotifFilename ) == 0 )
{
DoYourStuff();
}
}
}
}

This code works fine, but imagine you want to execute it in a thread. You’ll get into trouble when you’ll want to terminate your thread. Why ? Because ReadDirectoryChangesW() as it is used here works synchronously. It hangs until a file change occurs. If you don’t want to change your code, the only way to lunock it is to make a change to the monitored file so that we get out of ReadDirectoryChangesW() and that we can check if we must break our loop.

That’s not a very nice way to manage a clean exit…

There is a better solution : using ReadDirectoryChangesW() asynchronously.
To do so :

  1. Get the handle to your directory with an additionnal flag : FILE_FLAG_OVERLAPPED.
    m_hMonitoredDir = CreateFile( szFiledir, FILE_LIST_DIRECTORY, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_OVERLAPPED, NULL );
  2. give a non-signaled event handle to the ReadDirectoryChangesW function through the OVERLAPPED structure optionnal parameter. The ReadDirectoryChangesW () will return immediately, and will indicate any file change by firing the event
  3. Using the wait events mechanisms allows you to wait for the event to be triggered. But you will still be stucked waiting for a file change if you use WaitForSingleObject(). To wait untile a file change or end of thread, use a second event that your main thread will fire to notify terminate to the thread. And the use WaitForMultipleObjects on the two events. You will get out of the wait state as soon as one of the two events is triggered.

The code below illustrates the solution.

/** Index of the file change event in CFileChangeMonitor::dwEvents*/
const int EVENT_INDEX_FILECHANGE = 0;
/** Index of the terminate event in CFileChangeMonitor::dwEvents*/
const int EVENT_INDEX_TERMINATE = 1;

HANDLE m_events[ 2 ] ; // Array containing the events used

unsigned int CFileChangeMonitor::DoWaitFileChange( const char* szFilename ){    m_hMonitoredDir = CreateFile( szFiledir, FILE_LIST_DIRECTORY,                                             FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE,                                             NULL, OPEN_EXISTING,                                             FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_OVERLAPPED,                                             NULL );    OVERLAPPED stOverlapped;
memset( &stOverlapped, NULL, sizeof( stOverlapped ) );
m_events[ EVENT_INDEX_FILECHANGE ]  = CreateEvent( NULL, FALSE, FALSE, NULL );
m_events[ EVENT_INDEX_TERMINATE ]   = CreateEvent( NULL, FALSE, FALSE, NULL );
stOverlapped.hEvent = m_events[ EVENT_INDEX_FILECHANGE ];

char szBuf[ MAX_PATH ];
DWORD dwBytesRead = 0;

while ( ReadDirectoryChangesW( m_hMonitoredDir, szBuf, MAX_PATH,
FALSE, FILE_NOTIFY_CHANGE_LAST_WRITE,
&dwBytesRead, &stOverlapped, NULL ) )
{
// Wait either for a file change or a terminate event
DWORD dwWaitRes = WaitForMultipleObjects( 2, m_events, FALSE, INFINITE );
switch ( dwWaitRes )
{
case EVENT_INDEX_FILECHANGE: // Change on the monitored file
if ( GetOverlappedResult( m_hMonitoredDir, &stOverlapped, &dwBytesRead, TRUE ) )
{
PFILE_NOTIFY_INFORMATION pstFileNotif = ( PFILE_NOTIFY_INFORMATION )( szBuf );
if ( pstFileNotif->Action == FILE_ACTION_MODIFIED )
{
char szNotifFilename[ MAX_PATH ] = { 0 };
if ( int iNotifFilenameLen = WideCharToMultiByte( CP_OEMCP, NULL,
pstFileNotif->FileName,
pstFileNotif->FileNameLength / sizeof( WCHAR ), szNotifFilename,
sizeof( szNotifFilename ) / sizeof( char ),
NULL, NULL ) )
{
if ( strcmp( szFilename, szNotifFilename ) == 0 )
OnFileChanged();
}
}
}
break;
default: // EVENT_INDEX_TERMINATE : must terminate
CloseHandle( m_hMonitoredDir );
CloseHandle( m_events[ EVENT_INDEX_FILECHANGE ] );
CloseHandle( m_events[ EVENT_INDEX_TERMINATE ] );
m_events[ EVENT_INDEX_TERMINATE ]   = INVALID_HANDLE_VALUE;
m_events[ EVENT_INDEX_FILECHANGE ]  = INVALID_HANDLE_VALUE;
m_hMonitoredDir                     = INVALID_HANDLE_VALUE;
return 0;
}
}

return 0;
} // unsigned int CFileChangeMonitor::DoWaitFileChange

 

 

How to register an event in the windows system Log ?

The system log allows programmer to store log from their application in a standard way.
To do so, you need to register an event source before registering the event itself.
The following is a simple example to register one message.

This function  adds a log entry to the system log
Param wEventType is one of the MSDN event type for ReportEvent (EVENTLOG_SUCCESS, EVENTLOG_AUDIT_FAILURE, EVENTLOG_AUDIT_SUCCESS, EVENTLOG_ERROR_TYPE, EVENTLOG_INFORMATION_TYPE, EVENTLOG_WARNING_TYPE)
Param szMessage is the message to log.

void SystemLog( WORD wEventType, const char* szMessage )
{
const char* LOCAL_HOST = NULL;
if ( HANDLE hEventSource = RegisterEventSource( LOCAL_HOST, “Vircom Log system” ) )
{
ReportEvent( hEventSource, wEventType, NULL, NULL, NULL, 1, 0, &szMessage, NULL );
DeregisterEventSource( hEventSource );
}
} // void SystemLog