Thread: Win32 Service for FileChangeNotification

  1. #1
    Registered User
    Join Date
    May 2003
    Posts
    9

    Question Win32 Service for FileChangeNotification

    hi there,

    I currently try to write a windows service that acts on filechanges in a specified directory (yes a filemonitoring tool)

    I wrote an application (the out commented part at the bottom of main.c) thzat worked quiet well. I also wrote a service doing beep()s and it also did great.

    now I need to combine the two.

    all compiles just fine,
    with 'ca.exe install' the service gets registered and with 'ca.exe uninstall' the service gets removed.

    I pass the following arguments as needed to the service before starting it manually: '-fs c:\\Temp'. these are needed by my application, meaning that the application should act on changes to the filesize or changedate within c:\Temp (you have to escape the backslash with another one so \\ is ok).

    as stated before the service starts and runs fine only it does not react on changes. nothing happenes except that every bunch of minutes lots of entries are made to c:\temp\test.txt (my log file) stating 'WAIT_FAILED'..

    I think the mistake I made lies in some timing. I call the WaitForSingleObject() within the services while loop, but I can not say what causes the problem. maybe you can?


    please be gentle with me, since I am pretty n00b to windows programming. in fact this is the first win32 code I ever did.. :-/


    thanks in advance!

    regards
    /christian

    platform is windows nt4
    ide is devcpp
    compoiler is mingw

  2. #2
    Yes, my avatar is stolen anonytmouse's Avatar
    Join Date
    Dec 2002
    Posts
    2,544
    We know that WaitForSingleObject is returning WAIT_FAILED here:
    Code:
    		do
    		{
    			FindNextChangeNotification(cnh);
    			wr = WaitForSingleObject(cnh, dwMilSecs);
    		} while(wr == WAIT_OBJECT_0);
    Generally, the only reason WaitForSingleObject should fail is if you pass it an invalid handle.

    Which leads us here:
    Code:
    	// request a handle to monitor given directory for changes
    	cnh = FindFirstChangeNotification(NotifyPath, NotifyRecursive, NotifyParameters);
    	
    //	if(cnh = INVALID_HANDLE_VALUE)
    //		ExitProcess(GetLastError());
    //	wr = WaitForSingleObject(cnh, INFINITE);
    As you can see, the error checking is commented out so your program continues even if the FindFirstChangeNotification call fails.

    Generally, the only reason that this function will fail is if it is passed invalid input. You can add a MessageBox call to see the contents of NotifyPath before the call.

    Which leads us here:
    Code:
    void EscapeString(char *string)
    {
    	char *buffer = (char*) calloc(1000, sizeof(char));
    	int i,j = 0;
    	for(i = 0; i < strlen(string); ++i)
    	{
    		buffer[j++] = string[i];
    		if(string[i] == '\\') buffer[j++] = string[i];
    	}
    	char *NotifyPath = (char*) calloc(j+1, sizeof(char));
    	strcpy(NotifyPath, buffer);
    	free(buffer);
    }
    You seem to be doubling the back slashes. With the suggested input "C:\\temp" this gives you no less than four back slashes "C:\\\\temp".

    >>(you have to escape the backslash with another one so \\ is ok).<<

    I'm not sure about this. Generally, you only have to escape a back slash in a string literal.

    EDIT: That is some pretty advanced stuff for your first win32 program. Diving in the deep end!
    Last edited by anonytmouse; 01-04-2004 at 09:47 AM.

  3. #3
    Registered User
    Join Date
    May 2003
    Posts
    9
    hi anonytmouse,

    great analysis you did there, thanks a lot.

    I escaped the backslash in the parameterbox because I read somewhere, that given pathnames for parameter to a service should be escaped that way.
    so I thought they would reach my app with the escape character removed, which I never checked..

    so you're explanation certainly has a point and I will check it as soon as I'm at work again.


    regards
    /christian

  4. #4
    Registered User
    Join Date
    May 2003
    Posts
    9
    EDIT: That is some pretty advanced stuff for your first win32 program. Diving in the deep end!
    yes, maybe. I use to program for unix systems, mainly linux and did some deamon stuff as well.

    when I first thought about my problem (reacting on a plain textfile change) I immediatly thought about pipes, which is not at all hard to do in unix world, but try that in windows (replacing a file with a pipe) and it is headache again..
    in windows world everything feels so weird, cause you have to use all those big things to do so little..


    /christian

  5. #5
    Registered User
    Join Date
    May 2003
    Posts
    9
    I now changed the code a bit. still everything runs smooth only the changes have no effect..

    it looks like the WaitForSingleObject() timing and the overall service timing..



    <I get this log>
    Code:
     
    --- START ---
    Monitoring:	c:\\Temp
    
    0000: WAIT_TIMEOUT
    0001: WAIT_TIMEOUT
    0002: WAIT_TIMEOUT
    0003: WAIT_TIMEOUT
    0004: WAIT_TIMEOUT
    0005: WAIT_TIMEOUT
    0006: WAIT_TIMEOUT
    0007: WAIT_TIMEOUT
    0008: WAIT_TIMEOUT
    0009: WAIT_TIMEOUT
    ---- END ----
    </I get this log>

    although I am doing a lot in c:\Temp..
    like
    Code:
    cmd:> echo "barfoo" >> C:\Temp\test.txt
    <code main.c>
    Code:
     
    #include <windows.h>
    #include <winbase.h>
    #include <tchar.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    void ca_init(int argc, TCHAR* argv[]);
    void ca_action();
    void ca_cleanup();
    
    FILE *fp;
    HANDLE cnh;
    DWORD wr;
    DWORD dwMilSecs; 		// arbitrary; enlarge for floppies
    int test;
    
    LPCTSTR	NotifyPath;					// Directory to be monitored
    BOOL	NotifyRecursive;				// Flag if monitoring shall be recursive
    
    TCHAR* serviceName = TEXT("Beeper Service");
    SERVICE_STATUS serviceStatus;
    SERVICE_STATUS_HANDLE serviceStatusHandle = 0;
    HANDLE stopServiceEvent = 0;
    
    void WINAPI ServiceControlHandler( DWORD controlCode )
    {
    	switch ( controlCode )
    	{
    		case SERVICE_CONTROL_INTERROGATE:
    			break;
    
    		case SERVICE_CONTROL_SHUTDOWN:
    		case SERVICE_CONTROL_STOP:
    			serviceStatus.dwCurrentState = SERVICE_STOP_PENDING;
    			SetServiceStatus( serviceStatusHandle, &serviceStatus );
    
    			SetEvent( stopServiceEvent );
    			return;
    
    		case SERVICE_CONTROL_PAUSE:
    			break;
    
    		case SERVICE_CONTROL_CONTINUE:
    			break;
    
    		default:
    			if ( controlCode >= 128 && controlCode <= 255 )
    				// user defined control code
    				break;
    			else
    				// unrecognised control code
    				break;
    	}
    
    	SetServiceStatus( serviceStatusHandle, &serviceStatus );
    }
    
    void WINAPI ServiceMain( DWORD argc, TCHAR* argv[] )
    {
    	// initialise service status
    	serviceStatus.dwServiceType = SERVICE_WIN32;
    	serviceStatus.dwCurrentState = SERVICE_STOPPED;
    	serviceStatus.dwControlsAccepted = 0;
    	serviceStatus.dwWin32ExitCode = NO_ERROR;
    	serviceStatus.dwServiceSpecificExitCode = NO_ERROR;
    	serviceStatus.dwCheckPoint = 0;
    	serviceStatus.dwWaitHint = 0;
    
    	serviceStatusHandle = RegisterServiceCtrlHandler( serviceName, ServiceControlHandler );
    
    	if ( serviceStatusHandle )
    	{
    		// service is starting
    		serviceStatus.dwCurrentState = SERVICE_START_PENDING;
    		SetServiceStatus( serviceStatusHandle, &serviceStatus );
    
    		// do initialisation here
    		stopServiceEvent = CreateEvent( 0, FALSE, FALSE, 0 );
    
    		// running
    		serviceStatus.dwControlsAccepted |= (SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN);
    		serviceStatus.dwCurrentState = SERVICE_RUNNING;
    		SetServiceStatus( serviceStatusHandle, &serviceStatus );
    		
    		// custom initialization
    		ca_init(argc, argv);
    
    		do
    		{
    			// custom action
    			ca_action();
    		}
    		while ( WaitForSingleObject( stopServiceEvent, 5000 ) == WAIT_TIMEOUT );
    
    		// custom cleanup
    		ca_cleanup();
    
    		// service was stopped
    		serviceStatus.dwCurrentState = SERVICE_STOP_PENDING;
    		SetServiceStatus( serviceStatusHandle, &serviceStatus );
    
    		// do cleanup here
    		CloseHandle( stopServiceEvent );
    		stopServiceEvent = 0;
    
    		// service is now stopped
    		serviceStatus.dwControlsAccepted &= ~(SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN);
    		serviceStatus.dwCurrentState = SERVICE_STOPPED;
    		SetServiceStatus( serviceStatusHandle, &serviceStatus );
    	}
    }
    
    void RunService()
    {
    	SERVICE_TABLE_ENTRY serviceTable[] =
    	{
    		{ serviceName, ServiceMain },
    		{ 0, 0 }
    	};
    
    	StartServiceCtrlDispatcher( serviceTable );
    }
    
    void InstallService()
    {
    	SC_HANDLE serviceControlManager = OpenSCManager( 0, 0, SC_MANAGER_CREATE_SERVICE );
    
    	if ( serviceControlManager )
    	{
    		TCHAR path[ _MAX_PATH + 1 ];
    		if ( GetModuleFileName( 0, path, sizeof(path)/sizeof(path[0]) ) > 0 )
    		{
    			SC_HANDLE service = CreateService( serviceControlManager,
    							serviceName, serviceName,
    							SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
    							SERVICE_AUTO_START, SERVICE_ERROR_IGNORE, path,
    							0, 0, 0, 0, 0 );
    			if ( service )
    				CloseServiceHandle( service );
    		}
    
    		CloseServiceHandle( serviceControlManager );
    	}
    }
    
    void UninstallService()
    {
    	SC_HANDLE serviceControlManager = OpenSCManager( 0, 0, SC_MANAGER_CONNECT );
    
    	if ( serviceControlManager )
    	{
    		SC_HANDLE service = OpenService( serviceControlManager,
    			serviceName, SERVICE_QUERY_STATUS | DELETE );
    		if ( service )
    		{
    			SERVICE_STATUS serviceStatus;
    			if ( QueryServiceStatus( service, &serviceStatus ) )
    			{
    				if ( serviceStatus.dwCurrentState == SERVICE_STOPPED )
    					DeleteService( service );
    			}
    
    			CloseServiceHandle( service );
    		}
    
    		CloseServiceHandle( serviceControlManager );
    	}
    }
    
    int _tmain( int argc, TCHAR* argv[] )
    {
    	if ( argc > 1 && lstrcmpi( argv[1], TEXT("install") ) == 0 )
    	{
    		InstallService();
    	}
    	else if ( argc > 1 && lstrcmpi( argv[1], TEXT("uninstall") ) == 0 )
    	{
    		UninstallService();
    	}
    	else
    	{
    		RunService();
    	}
    
    	return 0;
    }
    
    
    void ca_init(int argc, TCHAR* argv[])
    {
    	Beep( 1000, 1000 );
    
        fp = fopen("C:\\Temp\\ca.out", "w");
        fprintf(fp, "--- START ---\n");
    
        dwMilSecs = 200;
    
        EscapeString("C:\Temp");
        fprintf(fp, "Monitoring:\t%s\n\n", NotifyPath);
        NotifyRecursive = FALSE;
    
        test = 0;
    
    	// request a handle to monitor given directory for changes
    	cnh = FindFirstChangeNotification(NotifyPath, NotifyRecursive, FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_WRITE);
    	
    	if(cnh == INVALID_HANDLE_VALUE)
    		ExitProcess(GetLastError());
    }
    
    void ca_action()
    {
    
    	do
    	{
    		if(FindNextChangeNotification(cnh) == FALSE)
    		{
    		    fprintf(fp, "ERROR: FindNextChangeNotification(cnh) == FALSE\n");
    		    ExitProcess(GetLastError());
    		}
    		wr = WaitForSingleObject(cnh, dwMilSecs);
    	} while(wr == WAIT_OBJECT_0);
    
    	switch(wr)
    	{
     		case WAIT_OBJECT_0:
    			fprintf(fp, "%04d: WAIT_OBJECT_0\n", test++);
     			break;
     		case WAIT_ABANDONED:
    			fprintf(fp, "%04d: WAIT_ABANDONED\n", test++);
     			break;
     		case WAIT_IO_COMPLETION:
    			fprintf(fp, "%04d: WAIT_IO_COMPLETION\n", test++);
     			break;
     		case WAIT_TIMEOUT:
    			fprintf(fp, "%04d: WAIT_TIMEOUT\n", test++);
     			break;
     		case WAIT_FAILED:
    			fprintf(fp, "%04d: WAIT_FAILED\n", test++);
     			break;
     		default:
    			fprintf(fp, "%04d: Can't say what that was for..?\n", test++);
    	}
    
    	Beep( 1000, 100 );
    }
    
    void ca_cleanup()
    {
    	// release notification handle
    	FindCloseChangeNotification(cnh);
    
        fprintf(fp, "---- END ----\n");
        fclose(fp);
    
    	Beep( 1000, 1000 );
    }
    </code main.c>

  6. #6
    Yes, my avatar is stolen anonytmouse's Avatar
    Join Date
    Dec 2002
    Posts
    2,544
    --- START ---
    Monitoring: c:\\Temp
    c:\\temp is not a valid path, c:\temp is.

    However, I don't understand why your program is not exiting here:
    Code:
    	if(cnh == INVALID_HANDLE_VALUE)
    		ExitProcess(GetLastError());
    or here, for that matter:
    Code:
    		if(FindNextChangeNotification(cnh) == FALSE)
    		{
    		    fprintf(fp, "ERROR: FindNextChangeNotification(cnh) == FALSE\n");
    		    ExitProcess(GetLastError());
    		}
    Stop Press: There is no Wait call between the call to FindFirstChangeNotification and the first call to FindNextChangeNotification.

    FindNextChangeNotification should only be called after a successful wait so maybe you should relocate it to under the WAIT_OBJECT_0 case.

    I escaped the backslash in the parameterbox because I read somewhere, that given pathnames for parameter to a service should be escaped that way.

    This is often a requirement in wmi(which also deals with services) but I don't think I have seen it needed in the Win32 api.

  7. #7
    Registered User
    Join Date
    May 2003
    Posts
    9

    Exclamation SOLVED!

    Hi,

    thanks for your help!

    I found the mistake in this line:

    Code:
    		while ( WaitForSingleObject( stopServiceEvent, 5000 ) == WAIT_TIMEOUT );
    if you set the timeout to 5 secs like the above did this will result in 5 secs idle time where no change can be notified.
    if set to 0 like this

    Code:
    		while ( WaitForSingleObject( stopServiceEvent, 0 ) == WAIT_TIMEOUT );
    the loop immidiatly continues and so all events are triggered.




    if you want full sources, please pm me at:
    caefer_at_krachstoff_net

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. running my program as service
    By daher in forum Windows Programming
    Replies: 5
    Last Post: 09-05-2008, 12:30 PM
  2. Basic Win32 Service Control Code
    By The Dog in forum Windows Programming
    Replies: 4
    Last Post: 09-08-2006, 01:00 AM
  3. Win32 API or Win32 SDK?
    By jverkoey in forum A Brief History of Cprogramming.com
    Replies: 2
    Last Post: 07-20-2005, 03:26 PM
  4. Replies: 11
    Last Post: 10-24-2004, 10:28 AM
  5. Another windows service question... starting/stopping
    By BrianK in forum Windows Programming
    Replies: 1
    Last Post: 03-20-2003, 12:22 AM