Important
Note:<o:p>
This
article presents the old (0.3) version of Boost.Application (Library Proposal
to boost.org)!<o:p>
A new
version (0.4) with other interface is aready available to download on: <o:p>
https://github.com/retf/Boost.Application<o:p>
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)
{
boost::filesystem::path executable_path_name = ctrl.executable_path_name();
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)
{
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)
{
}
};
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)
{
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);
char data[1024];
size_t length = socket.read_some(boost::asio::buffer(data), error);
if(error == boost::asio::error::eof)
break; else if (error)
throw boost::system::system_error(error);
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;
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;
}
};
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<
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)
{
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);
char data[1024];
size_t length = socket.read_some(boost::asio::buffer(data), error);
if(error == boost::asio::error::eof)
break; else if (error)
throw boost::system::system_error(error);
std::string message(data, length);
if(ctrl->state() == boost::application::application_paused)
{
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;
}
}
};
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
#include "..\uuid_plugin\plugin_api.hpp"
class my_server
{
typedef my_plugin_api* (*pluginapi_create) (void);
typedef void (*pluginapi_delete) (my_plugin_api* myplugin);
protected:
void work_thread(boost::application::application_ctrl* ctrl)
{
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);
char data[1024];
size_t length = socket.read_some(boost::asio::buffer(data), error);
if(error == boost::asio::error::eof)
break; else if (error)
throw boost::system::system_error(error);
std::string message(data, length);
if(ctrl->state() == boost::application::application_paused)
{
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_;
};
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_;
};
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. |
|
setup
| 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. |
stop
| 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. |
pause
| Do some application specific logic. | When user clicks on pause link on SCM. | Change internal status of application to
application_paused ! use ctrl.status() to check the current status. |
resume
| Do some application specific logic. | When user clicks on resume link on SCM. | Change internal status of application to
application_running ! use ctrl.status() to check the current status. |
limit_single_instance
| Uniquely identify the application using a UUID. | Before Main
(functor) | Called to check if there is already an application instance
running on the system. |
single_instance
| 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"))
{
std::cout << desc << std::endl;
return 0;
}
if (vm.count("-f"))
as_serice = false;
}
if (!as_serice)
{
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.