Goals
In order to develop the service we need to use VC++, hence I will work with VC6 and take advantage of a base class developed some time ago by "Nigel Thompson", since it is very well written and is worthless to waste time in this because he did a very good job and explained it even better.
We will discuss the following points:
Creation of a service in VC++
To create the service we should first to create an application of the type console in C++, but keeping in mind that it will be able to use the MFC libaries in order to make the coding easier in some ways, such as these two images show.
At this point we already created a work environment fit to develop any C++ program set as console type, now, since we intend our application to be a service we will add the classes developed by "Nigel Thompson" to the project. These happen to be the files NTService.cpp, NTService.h y NTServmsg.h as shown bellow..
Once the classes are included in the project we need to create our class to Start working, but this class has to inherit from the class "CNTService" and to achieve this we will define a new class called "CVFPService", as shown.
At this point we have the base to start with, now we need to overwrite the methods of the class CNTService to include or logic. The methods we will use are:
Method OnInit
This method will run only when the operative system try to start the service. Here we will try to read the required info for the Fox application that we have to execute as a service and will record an the Event Log that we passed this point.
Method OnShutdown
This method will run only when the operative system try to stop the service, either automatically or by the user action.
This method will run when the service up to be shutdown of the process list of the O.S.
Method OnStop
This method will run only when the operative system stops the service, either automatically or by the user action.
Method Run
This method is perhaps the most important, for we will write here the logic that will run as long the service is started. For our case, it will execute the Fox program and will control that it keeps running.
Now, to add the first method just right click on the class CVFPService and chose the option new method from the context menu, as the figure shows.
Now we add the code to run when starting the service, which is:
BOOL CVFPService::OnInit() { BOOL bReturn = TRUE; DWORD dwType = EVENTLOG_INFORMATION_TYPE; CString sMsg; LogEvent( EVENTLOG_INFORMATION_TYPE, 101, "The service 'VFP - Service 1.0' is starting" ); // Read configuration. LoadConfig(); // Erases the flan file, if exists. DeleteFile( m_sAppVFPEnd ); // Records the name of the application to run. if( !m_sAppVFP.IsEmpty() ) sMsg.Format( "The application to run is: %s", m_sAppVFP ); else { sMsg.Format( "No application to run, the service will be "+; stopped. [Config file was taken from:%s]", GetPathINI() ); bReturn = FALSE; dwType = EVENTLOG_ERROR_TYPE; } LogEvent( dwType, 101, sMsg ); return bReturn; }
As we can see at the example, the code is not complex. We first record at the Event Log that the service is about to be started, then we read the settings from the ini file and erase a file, which we will use as a flag so that the service can indicate to the VFP application that it sould stop.
void CVFPService::OnShutdown() { LogEvent( EVENTLOG_INFORMATION_TYPE, 101, "Shutdown - 'VFP - Service 1.0'" ); CNTService::OnShutdown(); } void CVFPService::OnStop() { LogEvent( EVENTLOG_INFORMATION_TYPE, 101, " 'VFP - Service 1.0' service is stopping" ); CNTService::OnStop(); }
The methods OnShutdown and OnStop are quite simple, they just record they were executed so that the events can be controlled from the console.
void CVFPService::Run() { CString sMsg; sMsg.Format( "About to run the application '%s'", m_sAppVFP ); LogEvent( EVENTLOG_INFORMATION_TYPE, 101, sMsg ); // Starts the application in a separated process. if( !IniciarVFPApp() ) { sMsg.Format( "Couldn't start the application de VFP\r\n%s\r\n%s",; m_sAppVFP, m_sError ); LogEvent( EVENTLOG_ERROR_TYPE, 101, sMsg ); SetStatus( SERVICE_STOPPED ); return; } while( m_bIsRunning ) { // Nothing to do, I keep waiting.... Sleep( 5000 ); // 5 seconds. // Verify that the VFP application is running. if( !ConsultarVFPApp() ) { sMsg.Format( " VFP application closed while the service "+; "was executing: ['%s']", m_sAppVFP ); LogEvent( EVENTLOG_ERROR_TYPE, 101, sMsg ); SetStatus( SERVICE_STOPPED ); } } // Stops the application because the service is about to stop. DetenerVFPApp(); LogEvent( EVENTLOG_INFORMATION_TYPE, 101, "is finishing to run the application" ); }
Let us see now thel method Run(), the most important of the class. What it does is to Stara the application through the function IniciarVFPApp() and then goes into a loop as long the variable m_bIsRunning is true, d, this variable will be true until the service be stopped and each loop the service keeps waiting 5 seconds and then checks that the VFP application is still running. Once it exits the bucle, the method calls the function DetenerVFPApp() to inform to the Fox application that it must to shutdown, or shut it down by itself otherwise.
The method IniciarVFPApp, simple creates anew process of the type OutProcess and keeps it running.
BOOL CVFPService::IniciarVFPApp() { ... if( CreateProcess( m_sAppVFP, NULL, NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS,NULL, NULL, &siData, &piData) ) m_hProcess = piData.hProcess; else { DWORD dwErr; LPVOID lpMsgBuf; bReturn = FALSE; dwErr = GetLastError(); FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dwErr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language (LPTSTR) &lpMsgBuf, 0, NULL ); // Error Message. m_sError.Format( "Error:0x%X, Msg:%s", dwErr, lpMsgBuf ); LocalFree( lpMsgBuf ); } ... }
As we see, the API function CreateProcess does the job and should it fails it gets the error to be recorded at the Event Log. The variable m_sAppVFP is the one that carries the name and full path of the application to run, and it was previously set when the configuration file was read.
All we have to do now is how to stop the VFP application and to check that is running. To stop the application we use the method DetenerVFPApp creating a file and wait that the application normally ends its execution, otherwise, we suddenly kill the process as is shown bellow.
BOOL CVFPService::DetenerVFPApp() { int nCount = 0; FILE *fEnd; // Generates the end of process mark. fEnd = fopen( m_sAppVFPEnd, "wt" ); fclose( fEnd ); // Once the flag file is created, wait up to // 5 seconds, otherwise kills the app. while( ConsultarVFPApp() && nCount < 5 ) { nCount++; Sleep( 1000 ); } // Erases the flan file. DeleteFile( m_sAppVFPEnd ); if( ConsultarVFPApp() ) return TerminateProcess( m_hProcess, VFP_EXIT_CODE_OK ); else return TRUE; }
At last, we have juste to see the method that checks that the application is running, the method ConsultarVFPApp. It reads from the O.S. the time corresponding to the process (the application) and check that the "exit" equals zero, otherwise, it would be a sign that the process ended for any reason.
BOOL CVFPService::ConsultarVFPApp() { ... if( GetProcessTimes( m_hProcess, &lpCreationTime, &lpExitTime, &lpKernelTime, &lpUserTime ) ) { // ExitTime has to be zero, otherwise // the process is over. if( lpExitTime.dwHighDateTime != 0 || lpExitTime.dwLowDateTime != 0 ) bReturn = FALSE; } else bReturn = FALSE; // The process couldn't be checked. ... }
We use for this the API function GetProcessTimes, which provides quite info, but now we are specially interested in the variable lpExitTime, which should be zero.
So, we already Developed our VC++ service to run a VFP application, so that we just compile it and -the most important- we ended my boring lectura on C++, very few guyslike it, but it wouldn't me if I don't mention it somewhere at my articles.
Creation of a VFP application for the test.
To create the application just do a Fox project with a main program that will loop until it finds the exit file.
LOCAL lnHand, lnPaso DECLARE Sleep IN "kernel32" Long dwMilliseconds lnHand = FCREATE( "C:\FileProcess.txt" ) lnPaso = 0 DO WHILE !FILE("C:\EndProcess.txt") lnPaso = lnPaso + 1 FPUTS( lnHand, "Paso número " + ALLTRIM( STR( lnPaso ) ) ) Sleep( 1000 ) ENDDO FCLOSE( lnHand )
An important point is to define a config.fpw file for the project that indicates that it won't have resources files, otherwise, the first time it runs it will ask for the creation of the files and -as a service- it is not interactive and it will "hang" waiting for an input that the user will never provide.
Config.fpw
RESOURCE = OFF
So we just compile the application with the business logic, in this case, to write a file once per second.
Test of the service
To run the test, we must first to define the configuration file, its name must be "Services.ini" and has to be at the same place where the.exe of the service "Services.exe" lies. Two entries must be filled in it, the first one indicates which is the Fox application to be ran and the second indicates the name of the file to be created in order the application learns that it must stop in a controlled way.
[GENERAL] VFPApp=C:\SvrFOX\SvrFox.exe EndVFPApp=C:\EndProcess.txt
Once configured, we need to install the service on the machina, in order to do this just execute the service created with VC++ with the parameter "-i" to install it and "-u" to uninstall it.
C:\Services\Services.exe -i Service VFP installed
C:\Services\Services.exe -u Service VFP removed.
If we look now at the services list of the operative system, or service will appear as installed but shut down.
If we want to Start it, we get this.
And when trying to stop it, will get this.
As we can see, this is no other thing that a service of the operative system, and we are running our VFP application totally without effort.
Ok, I hope you liked it and I also wanted to say that if anybody wants me to talk about any particular issue, just let me know… I'm running out of ideas...