• Kenco Computers

    OpenGL MFC Glut C++ C# Tutorials

    By Kenneth Peterson

  • Skip Navigation Links
    Home
    OpenGLExpand OpenGL
    C# ThreadsExpand C# Threads
    C++Expand C++
    MFCExpand MFC
    iPhoneExpand iPhone
    Lesson 1
    Lesson 2
    Lesson 3
    Lesson 4
    Scroll up
    Scroll down
    Simple Threading
    Scroll up
    Scroll down
    Windows Service
    Scroll up
    Scroll down
    Splash
    Splitter
    Scroll up
    Scroll down
    Block Spam
    Scroll up
    Scroll down

    Windows Service

    • Download demo project

    Introduction

    Have you ever wanted to create an windows service but can get it to work because there's no way to troubleshoot your code?

    Well I have the solution for you. We wont just make one project but three. A class will contain all our important code and it will be statically linked to two other projects, one that's the actual service that will be installed and one that's a command line version for testing.

    Let's get started. First lets create a Win32 console app. This will be used for building the service. The code here will get replaced later.

    Start up Visual Studio and on the menu go to File->New->Project.

    In the new project pop up scroll down to Visual C++. Expand the tree if needed and select Win32. In the Name box type WindowsServiceDemo and in the location box put in where you want your project created at.

    This wizard will start. Hit next. Uncheck Precompiled Header. Precompiled headers cause more problems than they solve. Use them if you want, I won't be. We don't want ALT or MFC so leave those unchecked. Since there is no GUI, we don't want all the overhead that comes with MFC.

    Hit Finish.

    What you will see now is:

    #include "stdafx.h"
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	return 0;
    }
    

    Now lets create the second project for this solution. File->Add->New Project.

    The new project wizard will start. Put WindowsServiceTester in for name and fill in the location where you would like the project to be. I suggest you put it in the same directory as the first project. The wizard will make subdirectories for each project in the parent directory and this will keep all the projects for this solution together.

    Click OK. Hit next. Uncleck Pre-compiled headers. Getting Déjà vu? That's right it has the same settings as the first project. There is a point to that but more on that later.

    Hit finish.

    What you will see now is:

    #include "stdafx.h"
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	return 0;
    }
    

    Time to add the third project. This one is important because this is where all the real code will be for your program. We are doing to create a class and compile it into a static lib.

    File->Add->New Project Name the project WindowsServiceClass and put it with the rest of the projects for this solution. However this time select Win32 Project. We want to make a lib not an exe.

    Click OK. Click Next. Select Static library this time. Uncleck Pre-compiled Headers.

    Hit Finish.

    In the solution explorer you should now see this.

    You will notice that while it created a project there are no code files in it. Lets fix that.

    Right click on the WindowsServiceClass in the solution explorer select Add->Class expand the tree if needed and select C++. Select C++ Class.

    Click Add. In class name put CMyService. The other text boxes will populate automatically.

    Click Finish. A shell for your service class will be created for you.

    MyService.h

    #pragma once
    
    class CMyService
    {
    public:
    	CMyService(void);
    	~CMyService(void);
    };
    
    MyService.cpp
    #include "MyService.h"
    
    CMyService::CMyService(void)
    {
    }
    
    CMyService::~CMyService(void)
    {
    }

    Let's put some code in it so we know it is being created and used

    In MyService.cpp add a cout to the constructor so we will see it being created in the test program.

    CMyService::CMyService(void)
    {
    	cout << "CMyService: Created\n";
    }
    

    If we are going to use cout then we need to include the header and tell the compiler to use STD.

    In MyService.h include iostream and std at the top of the file.

    #pragma once
    #include 
    using namespace std;
    
    class CMyService
    {
    public:
    	CMyService(void);
    	~CMyService(void);
    };

    Now we need to use our class in the test program.

    In the WindowsServiceTester project put #include "MyService.h" at the top of the WindowsServiceTester.cpp file.

    We need to create an instance of our class in order to use it. Add CMyService aService; to the main function.

    #include "stdafx.h"
    #include "MyService.h"
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	CMyService aService;
    	return 0;
    }

    We still can't compile yet. Remember we are putting the class in a static lib so we have to tell the compiler where it's located.

    Right click on WindowsServiceTester and select properties. You will see the Property Pages pop up. Change the All configurations. Expand the trees and select Configuration Properties->C/C++->General and put ../WindowsServiceClass in the Additional Include Directories Field. This will tell the compiler where to find the header.

    Hit Apply.

    Now we need to tell the linker where to find the lib file. It's location will be different for debug and release so we set them separately.

    Change the Configuration to Debug. Expand Linker in the tree control and select Input. Enter ../Debug/WindowsServiceClass.lib in the Additional Dependencies field.

    Hit Apply.

    Change Configuration to Release. If not visible expand the Linker in the tree control and select Input. Enter ../Release/WindowsServiceClass.lib in the Additional Dependencies field.

    Hit Apply.

    Notice that we are using relative pathing. If you didn't put all the projects in the same solution folder you will have to work out your paths as they will be different than those shown here.

    We wont be using unicode so we need to tell the compiler or it will get confused later when we try and use char arrays and call API functions.

    Right click on WindowsServieTester and select Properties. Change the configuration to All Configurations. Expand Configuration Properties and select General. Change Character Set to Not Set.

    Now we are almost ready to compile we just need to tell the solution which projects are dependant and which ones should start.

    At the top of the solution explorer right click on WindowsServiceDemo and select Project Dependencies.

    On the Project Dependencies pop up select WindowsServiceTester from the drop down selector. Check off WindowsServiceClass and WindowServiceDemo in the Dependencies list.

    This causes the build order to change so that the library is always built prior to the exe. It will also build the service when ever we hit the compile button.

    Next change the drop down to WindowsserviceDemo and make sure WindowsServiceClass is checked off.

    Switch to the build order tab and you should see this.

    Now everything is being built in the right order.

    Lets set the start up. Right click on the Solution WindowsServiceDemo  at the top of the Solution Explorer.

    From the right click menu select Set Start up Projects. On the pop up select Single Startup Project and choose WindowsServiceTester from the drop down.

    You can hit the run button now if you want.

    It won't do much but flash on the screen for a second. Lets create a small batch file so we can pause the screen and see it. Go to the solution directory and look for a folder in there called Debug. When you hit the run button it compile the debug exe and placed it there. You should see WindowsServiceTester.exe.

    Right click in the folder and choose New->Text Document. Change the name to test.bat. Right click on test.bet and choose Edit. Put the following code in the batch file.

    WindowsServiceTester.exe
    pause
    

    Save the file.

    Now double click on test.bat and it should run out test program and pause after the execution so you can see the output.

    You can see that the lib file was linked properly and the class was created in our test program.

    Now lets configure the other project to the same settings. This is key. The two projects will be identical so that when something is wrong we know it is in the service class code not in the shell code needed to make it a service. This way we can troubleshoot service problems by running the service tester and checking it's error messages.

    Right click WindowsServiceDemo (the project name part way down not the solution name at the top)

    Select Properties from the right click menu.

    Change configuration to All Configurations. Expand the C/C++ leaf and select General.

    In the Additional Include Directories enter ../WindowsServiceClass and hit Apply.

    Change the configuration to Debug. Expand the Linker leaf and select Input.

    In additional Dependencies enter ../Debug/WindowsServiceClass.lib. Hit Apply.

    Switch the configuration to Release. Select Input on the linker leaf if it is not already selected. Enter ../Release/WindowsServiceClass.lib in the Additional Depenencies field.

     Hit Ok.

    We wont be using unicode so we need to tell the compiler or it will get confused later when we try and use char arrays and call API functions.

    Right click on WindowsServieDemo and select Properties. Change the configuration to All Configurations. Expand Configuration Properties and select General. Change Character Set to Not Set.

    Click Ok.

    We need to tell the class project not to use unicode either.

    Right click on WindowsServieClass and select Properties. Change the configuration to All Configurations. Expand Configuration Properties and select General. Change Character Set to Not Set.

    Now the configurations should be the same. Lets test that.

    Copy the code from WindowsServiceTester.cpp

    // WindowsServiceTester.cpp : Defines the entry point for the console application.
    //
    
    #include "stdafx.h"
    #include "MyService.h"
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	CMyService aService;
    	return 0;
    }
    

    Replace the code in WindowsServiceDemo.cpp with the code you copied.

    // WindowsServiceDemo.cpp : Defines the entry point for the console application.
    //
    
    #include "stdafx.h"
    #include "MyService.h"
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	CMyService aService;
    	return 0;
    }
    

    In order to test the configuration we need to temporarilly change the start up project to the WindowsServiceDemo.

    Right click WindowsServiceDemo Solution at the top of the Solution Explorer.

    From the right click menu select Set Start up Projects. On the pop up select Single Startup Project and choose WindowsServiceDemo from the drop down.

    Click OK.

    You can hit the run button now if you want.

    It won't do much but flash on the screen for a second. Edit the test.bat file we made earlier in the debug directory.

    Add the line WindowsServiceDemo.exe just before the pause.

    WindowsServiceTester.exe
    WindowsServiceDemo.exe
    pause
    

    Save the batch file and then double click on it to run it.

    You will see that they both work exactly the same. So we now have our paths and links correct. We are going to change WindowsServiceDemo.exe into a service so lets remove it from test.bat.

    WindowsServiceTester.exe
    
    pause
    

    When we make a service it normally doesn't run once, it runs continuously once started. This Service loop is where we will do work. since our service will be looping we need a place in our class that does stuff each loop.

    Switch to Class view in the Solution Explorer and expand WindowsServiceClass.

    Right Click on CMyService select Add->Add Function. Enter void as the return type and ServiceLoop as the function name. this function takes no parameters because it is just a placeholder for code you want to execute over and over until the service is asked to shut down.

    Right now we are not going to put any code in there but this is where you would put something like server waiting for incoming connection code, process next packet or perhaps a named pipe waiting for a request. This might be a good place to place timer code for those services that wait to perform an action until a set time has come.

    Click Finish.

    Another thing our class needs is a place to put the start up code. This would be were a network service would initialize sockets or a named pipe might be created.

    Right Click on CMyService select Add->Add Function. Enter bool as the return type and StartMyService as the functions name. You may or may not take parameters here for start up. I am not using any parameters for start up in this example.

    Click Finish.

    In StartMyService change the code to return true. This will simulate a successful start up. later you can add you initialization code and return true or false depending if everything started up ok.

    bool CMyService::StartMyService(void)
    {
    	return true;
    }

    This last thing our class needs is a place to put code that runs when the service is stopped. At this point you may be wondering why we don't use the CMyService and ~CMyService for this. The reason is we want to separate errors that might occur in the instantiation of the class from errors that occur from starting and stopping sockets, pipes and the like.

    Right Click on CMyService select Add->Add Function. Enter void for the return type and StopMyService for the function name.

    Click Finish.

    The service will have it's own loop that waits for a service stop message from the OS, in the Service tester we need to simulate this loop and it will stop when we hit a key in the testers console window.

    In WindowsServiceTester.cpp add the header line #include <conio.h>. Put a while loop in that waits for a keypress to end.

    // WindowsServiceTester.cpp : Defines the entry point for the console application.
    //
    
    #include "stdafx.h"
    #include "MyService.h"
    #include <conio.h>
    int _tmain(int argc, _TCHAR* argv[])
    {
    	CMyService aService;
    	
    	if(aService.StartMyService())
    	{
    		printf("Press any key to stop.\n");
    		while(_kbhit() == false) 
    		{
    			aService.ServiceLoop();				
    		}	
    		aService.StopMyService();
    		printf("Service closed successfully.\n");
    	}
    	return 0;
    }

    Before we can test this we need to switch our start up project back to WindowsServiceTester. Lets do it a different way this time. Right click on WindowsServiceTester in the Solution Explorer and select Set as Startup Project.

    Why didn't we do it this way before you might ask. I wanted to show you the pop up selector because it has more options and lets you set multiple projects to start up. Now that you've learned that we can take a few short cuts when we only need one project set to start up.

    You can hit the run button now if you want.

    You should see.

    We don't need the test.bat file now since it stays on the screen looping until we hit a key.

    Now lets change WindowsServiceDemo into a real service. The code for a service shell is well known and available in many locations. Here it is.

    In WindowsServiceDemo.cpp change the code to:

    // WindowsServiceDemo.cpp : Defines the entry point for the console application.
    //
    
    #include <stdio.h>
    #include <windows.h>
    #include <tchar.h>
    #include "MyService.h"
    
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //Here is where we name our service. don't use spaces in the name. If you use spaces and the name is long the service gets jacked up. Welcome to Microsoft
    TCHAR* gszServiceName = TEXT("MyService");
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    
    SERVICE_STATUS serviceStatus;
    SERVICE_STATUS_HANDLE serviceStatusHandle = 0;
    HANDLE ServiceControlEvent = 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( ServiceControlEvent );
    			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( gszServiceName, ServiceControlHandler );
    
    	if ( serviceStatusHandle )
    	{
    		// service is starting
    		serviceStatus.dwCurrentState = SERVICE_START_PENDING;
    		SetServiceStatus( serviceStatusHandle, &serviceStatus );
    
    		// Create the Controlling Event here
     		ServiceControlEvent = CreateEvent( 0, FALSE, FALSE, 0 );
    
    		// Service running
    		serviceStatus.dwControlsAccepted |= (SERVICE_ACCEPT_STOP |
    		SERVICE_ACCEPT_SHUTDOWN);
    		serviceStatus.dwCurrentState = SERVICE_RUNNING;
    		SetServiceStatus( serviceStatusHandle, &serviceStatus );
    		
    		//////////////////////////////////////////////////////////////////////////////////////////////
    		//This is the only area you need to be concerned with. This is where the service loop is and 
    		//this is where you will put your class and run its loop
    		CMyService aService;		
    		if(aService.StartMyService())
    		{
    			do
    			{
    				aService.ServiceLoop();
    				
    			}
    			while ( WaitForSingleObject( ServiceControlEvent, 30 ) == WAIT_TIMEOUT );
    			aService.StopMyService();
    		}
    		//Everything else is just boilerplate needed for a service.
    		///////////////////////////////////////////////////////////////////////////////////////////////
    	
    		// service was stopped
    		serviceStatus.dwCurrentState = SERVICE_STOP_PENDING;
    		SetServiceStatus( serviceStatusHandle, &serviceStatus );
    
    		// do cleanup here
    		
    		CloseHandle( ServiceControlEvent );
    		ServiceControlEvent = 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[] =
    	{
    		{ gszServiceName, ServiceMain },
    		{ 0, 0 }
    	};
    
    	StartServiceCtrlDispatcher( serviceTable );
    }
    
    void InstallService()
    {
    	SC_HANDLE serviceControlManager = OpenSCManager( 0, 0,
    	SC_MANAGER_CREATE_SERVICE );
    
    	if ( serviceControlManager )
    	{
     		char path[ _MAX_PATH + 1 ];
    		if ( GetModuleFileName( 0, path, sizeof(path)/sizeof(path[0]) ) > 0 )
    		{
    			SC_HANDLE service = CreateService( serviceControlManager,
    			gszServiceName, gszServiceName,
    			SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
    			SERVICE_AUTO_START, SERVICE_ERROR_IGNORE, path,
    			0, 0, 0, 0, 0 );
        		if ( service )
    			{
    				CloseServiceHandle( service );
    				printf("%s Installed Successfully\n", gszServiceName);
    			}
    			else
    			{
    				if(GetLastError() == ERROR_SERVICE_EXISTS)
    					printf("%s Already Exists.\n", gszServiceName);
    				else
    					printf("%s Was not Installed Successfully. Error Code %d\n", gszServiceName, GetLastError());
    			}
    		}
    
    		CloseServiceHandle( serviceControlManager );
    	}
    }
    
    void UninstallService()
    {
    	SC_HANDLE serviceControlManager = OpenSCManager( 0, 0,
    	SC_MANAGER_CONNECT );
    
    	if ( serviceControlManager )
    	{
    		SC_HANDLE service = OpenService( serviceControlManager,
    		gszServiceName, SERVICE_QUERY_STATUS | DELETE );
    		if ( service )
    		{
    			SERVICE_STATUS serviceStatus;
    			if ( QueryServiceStatus( service, &serviceStatus ) )
    			{
    				if ( serviceStatus.dwCurrentState == SERVICE_STOPPED )
    				{
    					if(DeleteService( service ))
    						printf("%s Removed	Successfully\n", gszServiceName);
    					else
    					{
    						DWORD dwError;
    						dwError = GetLastError();
    						if(dwError == ERROR_ACCESS_DENIED)
    							printf("Access Denied While trying to Remove %s \n", gszServiceName);
    						else if(dwError == ERROR_INVALID_HANDLE)
    							printf("Handle invalid while trying to Remove %s \n", gszServiceName);
    						else if(dwError == ERROR_SERVICE_MARKED_FOR_DELETE)
    							printf("%s already marked for deletion\n", gszServiceName);
    					}
    				}
    				else
    				{
    					printf("%s is still Running.\n", gszServiceName);
    				}
    			}
    			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;
    }
    
    
    
    
    

    Hit the run button now. It will compile the service.

    Let's test installing and uninstalling the service. For this navigate to the Debug directory. Remember, it's where we created the test.bat. We could just type our commands into the prompt but then we have to navigate tot eh right directory in command prompt and type the same commands over and over when testing. Making batch files for this makes it a lot easier.

    Not create two batch files. One named install.bat and the other named uninstall.bat. I bet you can guess what these will do.

    Install.bat

    "C:\Public\work\Tutorials\WindowsServiceDemo\Debug\WindowsServiceDemo.exe" install
    pause
    
    Uninstall.bat
    "C:\Public\work\Tutorials\WindowsServiceDemo\Debug\WindowsServiceDemo.exe" uninstall
    pause
    

    You need to specify the complete path because the service registration runs from the windows system32 directory and will need to know the path of the service exe in order to install it.

    Of course, these show the path of WindowsServiceDemo.exe on my pc. You will need to modify them to reflect where you put your project files. Also when you are actually doing final tests you will want to compile and install the Release version of the WindowsServiceDemo.exe

    If you are running Windows 7 or Vista you will need to right click on the install.bat and choose Run as Administrator in order to install a service.

    Now lets check services and see if its there.

    There it is. Congratulations you have made your first windows service. Now lets remove it by right clicking and running as administrator the uninstall.bat.

    Now it's gone.

    You can now add what ever code you want to the CMyService class and run it either in a testing command line or as an installed service. Later you may want to look into posting messages to the windows log from your service. While during development trying to use logs for traces is far to slow for day to day programming it is useful when things go wrong on a clients machine to be able to have them send you a copy of the system log with messages from your service in it.

    I hope you found this useful.