Create a Windows Service Application Using the Boost.Application Library






4.82/5 (22 votes)
This article presents the Boost.Application library used to build a Windows Service.
Important
Note:
This
article presents the old (0.3) version of Boost.Application (Library Proposal
to boost.org)!
A new
version (0.4) with other interface is aready available to download on:
https://github.com/retf/Boost.Application
Note that version 0.3 is no longer maintained! Version 0.4, is now maintained and receives regular updates.
Introduction
In this article I will present a “Boost.Application” library to build a windows service. Note that Boost.Application is not yet an official Boost C++ library.
A “Boost.Application” provides an application environment, or start point to any people that want a basic infrastructure to build a system application on Windows or Unix Variants (e.g., Linux, MacOS).
In this tutorial concentrate on Windows Side development, if you are a UNIX user, refer to library documentation to more detail.
The library is in “beta” status, and the last version can be downloaded on: https://sourceforge.net/projects/boostapp/
Feedback
If you are boost
user, and use Boost Mailing Lists, please provide your feedback about the
library directly on the list. (Specify if you think if the library should be
accepted as part of boost.com).
http://www.boost.org/community/groups.html
If you are a CodeProject user, please provide your feedback about the library directly in this page.
Bugs
If you find any bugs, please send them to me at: re.tf@acm.org
Library dependences
Boost.Application is a header only library, but have some dependences on other boost libraries that need to be built and be available, thus you need to download and build boost on your machine. The build process of boost is not very easy, but a good documentation is provided here: http://www.boost.org/doc/libs/1_54_0/more/getting_started/windows.html.
Or you can check this good article to help with the build: http://www.codeproject.com/Articles/11597/Building-Boost-libraries-for-Visual-Studio.
1. Boost.Application Installation
The first thing to do is download boost, unzip on your drive, e.g.: “c:\”, and build it. After that you need to download Boost.Application and copy it to the boost folder.
Copy both folders to your boost installation directory:
Now you have your environment ready to use.
2. Boost.Application Introduction
The main propose of the Boost.Application library is to abstract the Windows Service API of the user. Boost.Application supports basically two flavors of applications that can be:
- Common Application: This kind of application is a usual Interactive Terminal/Console Application.
- Server Application: This kind of application generates a Service (Windows), or a background process/Daemon (Unix).
This article is focused on “Server Application” flavor, but it will use Common Application too as a client that will call a server.
Boost.Application supports many others useful ready-to-use features, e.g.:
- Plugin extension system;
- Environment variables access;
- Args access;
- Setup (windows service);
- Process (executable) Single instance Instantiation support;
- Path handle;
- And many others.
This article intends to cover items 1 and 4. Check library manual to know more about other features provided in Boost.Application, the manual can be accessed here: boost_installation\libs\application\doc\html\index.html.
The documentation is in alpha status. If you are a native English speaker and would like to contribute to the documentation, or want to contribute with some new functionality for the libraries (see future work on documentation), please contact me.
3. Sample Application Project
This article provides a how-to tutorial that shows how to use the Boost.Application library to build a client-server application; this article covers in less detail level other boost libraries like:
- Boost.Thread;
- Boost.Program_options;
- Boost.UUID;
- Boost.System;
- Boost.Asio.
Thus a
client (console/common application) that connects to the server (service/server
application) using a “TCP/IP” socket will be developed. The client role is to send a
string to the server, and the server role is to return a “GUID/UUID” based on
the received
string from the client.
4. Create Application Skeleton on Visual Studio
Note that the IDE used in this article is Visual Studio 2012, and all samples provided are to this environment. All code and projects are provided; check the download link on the top of the page.
4.1 Create Visual Studio Project skeletons to Client and Server applications.
Open Visual Studio, and create two Win32 projects, one for the client and another for the server.
4.2 On each project, open properties window and add boost installation directories to “C/C++ -> General -> Additional Include Directories”
4.3 On each project, open properties window again, and add boost libs directories to “Linker -> General -> Additional Library Directories”
Note that when you build boost, the common place for libs is: C:\boost_1_54_0\stage\lib.
5. Server Application
5.1 Add a Boost.Application interface to the server implementation.
#include "stdafx.h"
#include <boost\application.hpp>
int _tmain(int argc, _TCHAR* argv[])
{
return 0;
}
5.2 Create a class (functor) that will hold the server implementation.
class my_server
{
public:
int operator()(const std::vector< application_ctrl::string_type >& args,
application_ctrl& ctrl)
{
return 0;
}
};
Here are two important things. The first is args
, the functor
will always receive a vector with the argument passed to the application; the
second parameter is ctrl
, which owns the current state of the application and
several other methods that allow you to control the application.
The complete code, up to this point, looks like:
#include "stdafx.h"
#define BOOST_ALL_DYN_LINK
#include <boost\application.hpp>
using namespace boost::application;
class my_server
{
public:
int operator()(const std::vector< application_ctrl::string_type >& args,
application_ctrl& ctrl)
{
return 0;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
return 0;
}
BOOST_ALL_DYN_LINK
is used to enable a dynamic link to other boost needed
libraries.
At this point you can copy all of the above code to the client application too.
5.3 Instantiate server class to be a Windows Service.
To do that, add the below code to the main function:
int _tmain(int argc, _TCHAR* argv[])
{
return application<
application_type<server_application>,
my_application< my_server > >( args(argc, argv))();
}
One more detail is needed here. The server must be running continuous, and only stop when the user requests (using SCM), so we need to add a line of code in our operator functor that will do this.
class my_server
{
public:
int operator()(const std::vector< application_ctrl::string_type >& args,
application_ctrl& ctrl)
{
ctrl.wait_for_termination_request(); // <--
return 0;
}
};
5.4 Error Handler
Boost.Application
provides two ways to handle errors: one throws an exception of
type boost::system::system_error
and another receives a
boost::system::error_code
variable ec
that would be set to the
result of the operation, and does not throw an exception.
Refer to: http://www.boost.org/doc/libs/1_54_0/libs/system/doc/index.html to know more.
We will use the exception version, see:
int _tmain(int argc, _TCHAR* argv[])
{
try
{
return application<
application_type<server_application>,
my_application< my_server > >( args(argc, argv))();
}
catch(boost::system::system_error& se)
{
std::cerr << se.what() << std::endl;
return 1;
}
}
6. Add setup handler to server application
In order to test our Windows service, we need to install it. Installation makes the SCM aware of the service and causes the SCM to add it to the list of services that appear in the Services Console of the Control Panel.
Boost.Application provides a setup handler that can be added to the “functor” class to enable installation/un-installation of the application.
6.1 Add a “setup” handler to the application (functor) class.
class my_server
{
public:
int setup(application_ctrl& ctrl)
{
return 0;
}
// ...
};
The setup handler will be called automatically, before operator()
, then we can add code that automated installation of our application service; then when
the user
requests setup, we can run code to install or uninstall the service and exit.
6.2 Integrate Boost.Program_options to know what user want, and enable service installation.
The program_options library allows the user to obtain program options, that is pairs from the user, via conventional methods such as command line and config file. Refer to: http://www.boost.org/doc/libs/1_54_0/doc/html/program_options.html to know more.
class my_server
{
public:
int setup(application_ctrl& ctrl)
{
// get our executable path name
boost::filesystem::path executable_path_name = ctrl.executable_path_name();
// define our simple installation schema options
po::options_description install("service options");
install.add_options()
("help", "produce a help message")
(",i", "install service")
(",u", "unistall service")
(",c", "check service")
("name", po::value<std::string>()->default_value(ctrl.executable_name().stem().string()), "service name")
("display", po::value<std::string>()->default_value(""), "service display name (optional, installation only)")
("description", po::value<std::string>()->default_value(""), "service description (optional, installation only)")
;
po::variables_map vm;
po::store(po::parse_command_line(ctrl.argc(), ctrl.argv(), install), vm);
boost::system::error_code ec;
if (vm.count("help"))
{
std::cout << install << std::cout;
return 1;
}
if (vm.count("-i"))
{
install_windows_service(
setup_arg(vm["name"].as<std::string>()),
setup_arg(vm["display"].as<std::string>()),
setup_arg(vm["description"].as<std::string>()),
setup_arg(executable_path_name)).install(ec);
std::cout << ec.message() << std::endl;
return 1;
}
if (vm.count("-u"))
{
uninstall_windows_service(
setup_arg(vm["name"].as<std::string>()),
setup_arg(executable_path_name)).uninstall(ec);
std::cout << ec.message() << std::endl;
return 1;
}
if (vm.count("-c"))
{
if(check_windows_service(setup_arg(vm["name"].as<std::string>())).exist(ec))
std::cout
<< "Windows service '"
<< vm["name"].as<std::string>()
<< "' is aready installed!"
<< std::endl;
else
std::cout
<< "Windows service '"
<< vm["name"].as<std::string>()
<< "' is not installed!"
<< std::endl;
std::cout << ec.message() << std::endl;
return 1;
}
return 0;
}
};
6.3 Test Service Installation (setup)
Now we have a
command line interface on our server to install, uninstall, and check if the service
is installed. When the user requests installation, e.g.: ‘server.exe -i', the
installation requisition is identified by Boost.Program_options
and the code on ‘-i if' is executed,
installing the service. Note that in this case we return 1, from the setup method.
This causes a execution to exit after installation, and the operator (that holds server
logic) is not executed.
The command line options are:
// [installation]
// server.exe -i
// /or/
// server.exe -i --name "My Service"
// server.exe -i --name "My Service"
// [check]
// server.exe -c
// /or/
// server.exe -c --name "My Service"
// [unstalation]
// server.exe -u
// /or/
// server.exe -u --name "My Service"
//
// Note that when arg name are not priovided, the name will be the name of
// executable, in this case, service name will be: 'server'
Now it is time to build and install the service. Note that you must run the installation as Admin. To do that, open a console as Admin like:
Then go to your server application directory and run the server using “-i", like:
E:\project.boost.app.v3\libs\application\doc\codeproject_article\Debug>server.exe -i --name="My Service App"
The operation completed successfully
To uninstall, you can use:
E:\project.boost.app.v3\libs\application\doc\codeproject_article\Debug>server.exe -u --name="My Service App"
The operation completed successfully
On SCM look for the “My Service App” service.
Control Panel\All Control Panel Items\Administrative Tools\Services:
The service still does nothing, but you can start and stop it as a test.
7. Add Application Logic to Server
Now it is time to add the application logic to our server, we will use Boost.Asio here, we will construct a basic TCP/IP server that will listen on port 9512.
7.1 Create a work thread
To make things more organized, we will put our logic on a work thread.
// ...
class my_server
{
public:
// ...
int operator()(const std::vector< application_ctrl::string_type >& args,
application_ctrl& ctrl)
{
// launch a work thread
boost::thread thread(boost::bind(&my_server::work_thread, this, &ctrl));
ctrl.wait_for_termination_request();
thread.join();
return 0;
}
protected:
void work_thread(boost::application::application_ctrl* ctrl)
{
// application logic
}
};
7.2 Add Boost.Asio to server.
Boost.Asio is a cross-platform C++ library for network and low-level I/O programming that provides developers with a consistent asynchronous model using a modern C++ approach. Refer to: http://www.boost.org/doc/libs/1_54_0/doc/html/boost_asio.html to know more.
// ...
class my_server
{
public:
// ...
protected:
void work_thread(boost::application::application_ctrl* ctrl)
{
// application logic
using boost::asio::ip::tcp;
try
{
boost::asio::io_service io_service;
tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), 9512));
for (;;)
{
if(ctrl->state() == boost::application::application_running)
{
boost::system::error_code error;
tcp::socket socket(io_service);
acceptor.accept(socket);
// our data is limited to 1024 bytes
char data[1024];
size_t length = socket.read_some(boost::asio::buffer(data), error);
if(error == boost::asio::error::eof)
break; // Connection closed cleanly by peer.
else if (error)
throw boost::system::system_error(error); // Some other error.
// response (echo)
std::string message(data, length);
boost::asio::write(socket, boost::asio::buffer(message),
boost::asio::transfer_all(), error);
}
else if(ctrl->state() == boost::application::application_stoped)
{
return;
}
}
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
}
};
This server just echoes (ECHO) the message from the client, and has a buffer of only 1024 characters, so the maximum message size is 1024.
8. Client Application
The client application will send a string to the server, and at this moment the server only echoes this message back to the client.
8.1 The client “functor” class implementation.
class my_client
{
public:
int operator()(const std::vector< application_ctrl::string_type >& args,
application_ctrl& ctrl)
{
using boost::asio::ip::tcp;
// define our simple installation schema options
po::options_description cli("client options");
cli.add_options()
("help", "produce a help message")
(",s", po::value<std::string>()->default_value("test"), "string to send")
;
po::variables_map vm;
po::store(po::parse_command_line(ctrl.argc(), ctrl.argv(), cli), vm);
if (vm.count("help"))
{
std::cout << cli << std::cout;
return 1;
}
std::string message_string = vm["-s"].as<std::string>();
if(message_string.size() > 1024)
{
std::cerr << "Message is too long!" << std::endl;
}
std::cout
<< "We will send to server : "
<< message_string
<< std::endl;
boost::system::error_code error;
boost::asio::io_service io_service;
tcp::resolver resolver(io_service);
tcp::resolver::query query(tcp::v4(), "localhost", "9512");
tcp::resolver::iterator iterator = resolver.resolve(query);
tcp::socket socket(io_service);
boost::asio::connect(socket, iterator);
boost::asio::write(socket, boost::asio::buffer(message_string),
boost::asio::transfer_all(), error);
char reply[1024];
size_t reply_length = socket.read_some(boost::asio::buffer(reply), error);
std::cout << "Reply is: ";
std::cout.write(reply, reply_length);
std::cout << "\n";
return 0;
}
}; // my_client class
8.2 Instantiate a common application
Copy the code from the server and just remove the line that tells it to be a service. By default, the application is a common application type.
int _tmain(int argc, _TCHAR* argv[])
{
return application<
// application_type<server_application>, ß remove this line,
// tells to your application to be a common application
my_application< my_server > >( args(argc, argv))();
}
9. Testing Server and Client Application
For the test we need to start the service and then open the console to run the client.
9.1 Start your server application using SCM.
9.2 Start your client, e.g.: client.exe -s "Hello Boost Application"
You must receive back: Reply is: Hello Boost Application.
10. Handle pause event
Now we will add pause functionality to our server.
10.1 Tell to class that pause needs to be handled.
int _tmain(int argc, _TCHAR* argv[])
{
try
{
return application<
application_type<server_application>,
accept_pause_and_resume<yes>, // ß
my_application< my_server > >( args(argc, argv))();
}
catch(boost::system::system_error& se)
{
std::cerr << se.what() << std::endl;
return 1;
}
}
10.2 Start service again to see “pause” link on SCM.
10.2 Identify the paused status on the application
To do that, use ctrl->state() == boost::application::application_paused
that tells the
status of the application.
// ...
class my_server
{
public:
// ...
protected:
void work_thread(boost::application::application_ctrl* ctrl)
{
// application logic
using boost::asio::ip::tcp;
try
{
boost::asio::io_service io_service;
tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), 9512));
for (;;)
{
if(ctrl->state() == boost::application::application_stoped)
{
return;
}
boost::system::error_code error;
tcp::socket socket(io_service);
acceptor.accept(socket);
// our data is limited to 1024 bytes
char data[1024];
size_t length = socket.read_some(boost::asio::buffer(data), error);
if(error == boost::asio::error::eof)
break; // Connection closed cleanly by peer.
else if (error)
throw boost::system::system_error(error); // Some other error.
// response (echo)
std::string message(data, length);
// detect pause state
if(ctrl->state() == boost::application::application_paused)
{
// if app is on pause state we will aswer a pause message.
message = "application uuid engine is paused, try again later!";
}
boost::asio::write(socket, boost::asio::buffer(message),
boost::asio::transfer_all(), error);
}
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
}
}; // my_server class
When the service is paused, the client application will receive the message ‘application UUID engine is paused, try again later!’ instead of echo.
To change the state of an application again to ‘application_running’, the user needs to click on Resume, and the service will answer with an echo message as before.
11. Add UUID engine on a plugin
An important addition of Boost.Application is a "Shared Library Class" that allows the user to extend the application using Dynamic Library Modules (DLL,SO/DSO) loaded at runtime. Using this feature the client can provide a plugin system to the application.
11.1. Create a plug-in project, add new project: “Win32 Console Application”
11.2. Select “DLL”, and set “Empty project”
Note that you need to add boost directories to the project, refer to items 4.2 and 4.3 that show how you can do this.
11.3. Add new .cpp file called “plugin.cpp”
11.3. Create plugin API interface
Crate a file called plugin_api.hpp”, this represents a plugin API interface. In this case we will build a plugin that will “transform” a string into a UUID using this interface, but other types can be done, e.g., “transform” a string into an MD5 hash, using some interface.
class my_plugin_api
{
public:
virtual ~my_plugin_api(){};
virtual float version() = 0;
virtual std::string transform_string(const std::string& source) = 0;
};
11.4. Implement a plugin behavior on “plugin.cpp”
#define BOOST_ALL_DYN_LINK
#define BOOST_LIB_DIAGNOSTIC
#include <boost/uuid/string_generator.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <boost/uuid/name_generator.hpp>
#include <boost/lexical_cast.hpp>
#include "plugin_api.hpp"
#include <iostream>
#if defined(_WIN32)
# define LIBRARY_API __declspec(dllexport)
#else
# define LIBRARY_API
#endif
extern "C" LIBRARY_API my_plugin_api* create_my_plugin(void);
extern "C" LIBRARY_API void delete_my_plugin(my_plugin_api* myplugin);
class my_plugin_sum : public my_plugin_api
{
public:
float version()
{
return 1.0;
};
std::string transform_string(const std::string& source)
{
boost::uuids::uuid dns_namespace_uuid;
boost::uuids::name_generator gen(dns_namespace_uuid);
boost::uuids::uuid u = gen(source);
return boost::lexical_cast<std::string>(u);
};
~my_plugin_sum()
{
std::cout << ";o)" << std::endl;
}
};
my_plugin_api* create_my_plugin(void)
{
my_plugin_api *myplugin = new my_plugin_sum();
return myplugin;
}
void delete_my_plugin(my_plugin_api* myplugin)
{
delete myplugin;
}
11.5. Use plugin on server
// ...
// Plugin API
#include "..\uuid_plugin\plugin_api.hpp"
// ...
class my_server
{
// plugin entry point
typedef my_plugin_api* (*pluginapi_create) (void);
typedef void (*pluginapi_delete) (my_plugin_api* myplugin);
// ...
protected:
void work_thread(boost::application::application_ctrl* ctrl)
{
// application logic
using boost::asio::ip::tcp;
try
{
boost::asio::io_service io_service;
tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), 9512));
for (;;)
{
if(ctrl->state() == boost::application::application_stoped)
{
return;
}
boost::system::error_code error;
tcp::socket socket(io_service);
acceptor.accept(socket);
// our data is limited to 1024 bytes
char data[1024];
size_t length = socket.read_some(boost::asio::buffer(data), error);
if(error == boost::asio::error::eof)
break; // Connection closed cleanly by peer.
else if (error)
throw boost::system::system_error(error); // Some other error.
// response (echo)
std::string message(data, length);
// detect pause state
if(ctrl->state() == boost::application::application_paused)
{
// if app is on pause state we will aswer a pause message.
message = "application uuid engine is paused, try again later!";
}
else
{
if(plugin_.is_loaded())
{
my_plugin_api* plugin = NULL;
if(plugin_.search_symbol(symbol(L"create_my_plugin")))
{
plugin = ((pluginapi_create)plugin_(symbol(L"create_my_plugin")))();
}
if(plugin != NULL)
{
message = plugin->transform_string(message);
((pluginapi_delete)plugin_(symbol(L"delete_my_plugin")))(plugin);
}
}
else
{
message = "some problem with plugin load, try again later!";
}
}
boost::asio::write(socket, boost::asio::buffer(message),
boost::asio::transfer_all(), error);
}
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
}
private:
shared_library plugin_;
}; // my_server class
When the client is called the result is a UUID of string, e.g.:
E:\project.boost.app.v3\libs\application\doc\codeproject_article\Debug>client.exe -s "dokfile.com"
12. Application Handles
Using Boost.Application allows you to respond to some handlers that are initiated by the user, thus handlers can be added directly to the functor class.
12.1 Change plugin with server running
In a hypothetical situation, you would like to change the behavior of your server (plugin). So you'd like to pause your server, when users connect, they would be advised to try later; you then change the behavior of the plugin and resume the service.
If you try to remove the plugin with the server in pause state now, you will receive the following message:
12.2 Add ‘pause’ and ‘resume’ handlers to unload plugin, and load again.
class my_server
{
// ...
int operator()(const std::vector< application_ctrl::string_type >& args,
application_ctrl& ctrl)
{
// ...
plugin_path_ = ctrl.executable_path().wstring() + L"\\uuid_plugin" + shared_library::suffix();
plugin_.load(library(plugin_path_));
// ...
}
int pause()
{
plugin_.unload();
return 1;
}
int resume()
{
plugin_.load(library(plugin_path_));
return 1;
}
private:
shared_library plugin_;
boost::filesystem::path plugin_path_;
}; // my_server class
Now you can pause the service, replace the plug-in, and then resume. All connections will be served.
12.3 Supported Application Handlers
The next table shows the available handlers to the user.
Handler |
Can be used to. |
Called? |
Obs. |
|
|||
|
Add setup logic to application, e.g. add windows service installation functionality. |
Before Main (functor) |
f user returns 0 from this handler, the execution continues, else program is terminated. |
|
Do some clean up or other finish task. |
After Main (functor) |
If user returns 0 from this handler, the execution continues, else program is terminated. |
|
Do some application specific logic. |
When user clicks on pause link on SCM. |
Change internal status of application to
|
|
Do some application specific logic. |
When user clicks on resume link on SCM. |
Change internal status of application to
|
|
Uniquely identify the application using a UUID. |
Before Main (functor) |
Called to check if there is already an application instance running on the system. |
|
Do some application specific logic, if application instance is already running. |
Before Main (functor), if application instance is already running. |
If user returns 0 from this handler, the execution continues, else program is terminated. |
The following diagram shows the order of calling of each available handler.
13. Add a Common Application to Server
In this final session a common application will be added to our service, this will make it easy to debug some parts of the application.
int _tmain(int argc, _TCHAR* argv[])
{
try
{
bool as_serice = true;
{
po::variables_map vm;
po::options_description desc;
desc.add_options()
(",h", "Shows help.")
(",f", "Run as common applicatio")
("help", "produce a help message")
(",i", "install service")
(",u", "unistall service")
(",c", "check service")
("name", po::value<std::string>()->default_value(""), "service name")
("display", po::value<std::string>()->default_value(""),
"service display name (optional, installation only)")
("description", po::value<std::string>()->default_value(""),
"service description (optional, installation only)")
;
po::store(po::parse_command_line(argc, argv, desc), vm);
if (vm.count("-h"))
{
// show help
std::cout << desc << std::endl;
return 0;
}
if (vm.count("-f"))
as_serice = false;
}
if (!as_serice)
{
// instantiate server application as common_app
return common_app<my_server>( args(argc, argv) )();
}
else
{
return application<
application_type<server_application>,
accept_pause_and_resume<yes>,
my_application< my_server > >( args(argc, argv))();
}
}
catch(boost::system::system_error& se)
{
std::cerr << se.what() << std::endl;
return 1;
}
catch(std::exception &e)
{
std::cerr << e.what() << std::endl;
return 1;
}
catch(...)
{
std::cerr << "Unknown error." << std::endl;
return 1;
}
return 0;
}
Now you can instantiate an application as console, e.g.:
E:\project.boost.app.v3\libs\application\doc\codeproject_article\Debug>server.exe -f
14. Conclusion
Boost.Application can save time when the task is to build a service for Windows, otherwise the developer must work directly with a complex API provided for that. Furthermore Boost.Application provides an extension mechanism for an application (based on plugins) simple and efficient, and many other ready-to-use features.