poco-1.3.6-all-doc/ 0000777 0000765 0000120 00000000000 11302760032 014546 5 ustar guenter admin 0000000 0000000 poco-1.3.6-all-doc/00100-GuidedTour.html 0000666 0000765 0000120 00000051055 11302760027 020157 0 ustar guenter admin 0000000 0000000
The POCO C++ Libraries are a collection of open source C++ class libraries that simplify and accelerate the development of network-centric, portable applications in C++. The libraries integrate perfectly with the C++ Standard Library and fill many of the functional gaps left open by it. Their modular and efficient design and implementation makes the POCO C++ Libraries extremely well suited for embedded development, an area where the C++ programming language is becoming increasingly popular, due to its suitability for both low-level (device I/O, interrupt handlers, etc.) and high-level object-oriented development. Of course, POCO is also ready for enterprise-level challenges.
POCO consists of four core libraries, and a number of add-on libraries. The core libraries are Foundation, XML, Util and Net. Two of the add-on libraries are NetSSL, providing SSL support for the network classes in the Net library, and Data, a library for uniformly accessing different SQL databases. POCO aims to be for network-centric, cross-platform C++ software development what Apple's Cocoa is for Mac development, or Ruby on Rails is for Web development — a powerful, yet easy and fun to use platform to build your applications upon. POCO is built strictly using standard ANSI/ISO C++, including the standard library. The contributors attempt to find a good balance between using advanced C++ features and keeping the classes comprehensible and the code clean, consistent and easy to maintain.
The Foundation library makes up the heart of POCO. It contains the underlying platform abstraction layer, as well as frequently used utility classes and functions. The Foundation library contains types for fixed-size integers, functions for converting integers between byte orders, an Poco::Any class (based on boost::Any), utilities for error handling and debugging, including various exception classes and support for assertions. Also available are a number of classes for memory management, including reference counting based smart pointers, as well as classes for buffer management and memory pools. For string handling, POCO contains a number of functions that among other things, trim strings, perform case insensitive comparisons and case conversions. Basic support for Unicode text is also available in the form of classes that convert text between different character encodings, including UTF-8 and UTF-16. Support for formatting and parsing numbers is there, including a typesafe variant of sprintf. Regular expressions based on the well-known PCRE library (http://www.pcre.org) are provided as well.
POCO gives you classes for handling dates and times in various variants. For accessing the file system, POCO has Poco::File and Poco::Path classes, as well as a Poco::DirectoryIterator class. In many applications, some parts of the application need to tell other parts that something has happened. In POCO, Poco::NotificationCenter, Poco::NotificationQueue and events (similar to C# events) make this easy. The following example shows how POCO events can be used. In this example, class Source has a public event named theEvent, having an argument of type int. Subscribers can subscribe by calling operator += and unsubscribe by calling operator -=, passing a pointer to an object and a pointer to a member function. The event can be fired by calling operator (), as its done in Source::fireEvent().
#include "Poco/BasicEvent.h" #include "Poco/Delegate.h" #include <iostream> using Poco::BasicEvent; using Poco::Delegate; class Source { public: BasicEvent<int> theEvent; void fireEvent(int n) { theEvent(this, n); } }; class Target { public: void onEvent(const void* pSender, int& arg) { std::cout << "onEvent: " << arg << std::endl; } }; int main(int argc, char** argv) { Source source; Target target; source.theEvent += Delegate<Target, int>( &target, &Target::onEvent); source.fireEvent(42); source.theEvent -= Delegate<Target, int>( &target, &Target::onEvent); return 0; }
The stream classes available in POCO have already been mentioned. These are augmented by Poco::BinaryReader and Poco::BinaryWriter for writing binary data to streams, automatically and transparently handling byte order issues.
In complex multithreaded applications, the only way to find problems or bugs is by writing extensive logging information. POCO provides a powerful and extensible logging framework that supports filtering, routing to different channels, and formatting of log messages. Log messages can be written to the console, a file, the Windows Event Log, the Unix syslog daemon, or to the network. If the channels provided by POCO are not sufficient, it is easy to extend the logging framework with new classes.
For loading (and unloading) shared libraries at runtime, POCO has a low-level Poco::SharedLibrary class. Based on it is the Poco::ClassLoader class template and supporting framework, allowing dynamic loading and unloading of C++ classes at runtime, similar to what's available to Java and .NET developers. The class loader framework also makes it a breeze to implement plug-in support for applications in a platform-independent way.
Finally, POCO Foundation contains multithreading abstractions at different levels. Starting with a Poco::Thread class and the usual synchronization primitives (Poco::Mutex, Poco::ScopedLock, Poco::Event, Poco::Semaphore, Poco::RWLock), a Poco::ThreadPool class and support for thread-local storage, also high level abstractions like active objects are available. Simply speaking, an active object is an object that has methods executing in their own thread. This makes asynchronous member function calls possible — call a member function, while the function executes, do a bunch of other things, and, eventually, obtain the function's return value. The following example shows how this is done in POCO. The ActiveAdder class in defines an active method add(), implemented by the addImpl() member function. Invoking the active method in main() yields an Poco::ActiveResult (also known as a future), that eventually receives the function's return value.
#include "Poco/ActiveMethod.h" #include "Poco/ActiveResult.h" #include <utility> using Poco::ActiveMethod; using Poco::ActiveResult; class ActiveAdder { public: ActiveObject(): activeAdd(this, &ActiveAdder::add) { } ActiveMethod<int, std::pair<int, int>, ActiveAdder> add; private: int addImpl(const std::pair<int, int>& args) { return args.first + args.second; } }; int main(int argc, char** argv) { ActiveAdder adder; ActiveResult<int> sum = adder.add(std::make_pair(1, 2)); // do other things sum.wait(); std::cout << sum.data() << std::endl; return 0; }
The POCO XML library provides support for reading, processing and writing XML. Following one's of POCO's guiding principles — don't try to reinvent things that already work — POCO's XML library supports the industry-standard SAX (version 2) and DOM interfaces, familiar to many developers with XML experience. SAX, the Simple API for XML (http://www.saxproject.org), defines an event-based interface for reading XML. A SAX-based XML parser reads through the XML document and notifies the application whenever it encounters an element, character data, or other XML artifact. A SAX parser does not need to load the complete XML document into memory, so it can be used to parse huge XML files efficiently. In contrast, DOM (Document Object Model, http://www.w3.org/DOM/) gives the application complete access to an XML document, using a tree-style object hierarchy. For this to work, the DOM parser provided by POCO has to load the entire document into memory. To reduce the memory footprint of the DOM document, the POCO DOM implementation uses string pooling, storing frequently occuring strings such as element and attribute names only once. The XML library is based on the Expat open source XML parser library (http://www.libexpat.org). Built on top of Expat are the SAX interfaces, and built on top of the SAX interfaces is the DOM implementation. For strings, the XML library uses std::string, with characters encoded in UTF-8. This makes interfacing the XML library to other parts of the application easy. Support for XPath and XSLT will be available in a future release.
The Util library has a somewhat misleading name, as it basically contains a framework for creating command-line and server applications. Included is support for handling command line arguments (validation, binding to configuration properties, etc.) and managing configuration information. Different configuration file formats are supported — Windows-style INI files, Java-style property files, XML files and the Windows registry.
For server applications, the framework provides transparent support for Windows services and Unix daemons. Every server application can be registered and run as a Windows service, with no extra code required. Of course, all server applications can still be executed from the command line, which makes testing and debugging easier.
POCO's Net library makes it easy to write network-based applications. No matter whether your application simply needs to send data over a plain TCP socket, or whether your application needs a full-fledged built-in HTTP server, you will find something useful in the Net library.
At the lowest level, the Net library contains socket classes, supporting TCP stream and server sockets, UDP sockets, multicast sockets, ICMP and raw sockets. If your application needs secure sockets, these are available in the NetSSL library, implemented using OpenSSL (http://www.openssl.org). Based on the socket classes are two frameworks for building TCP servers — one for multithreaded servers (one thread per connection, taken from a thread pool), one for servers based on the Acceptor-Reactor pattern. The multithreaded Poco::Net::TCPServer class and its supporting framework are also the foundation for POCO's HTTP server implementation (Poco::Net::HTTPServer). On the client side, the Net library provides classes for talking to HTTP servers, for sending and receiving files using the FTP protocol, for sending mail messages (including attachments) using SMTP and for receiving mail from a POP3 server.
The following example shows the implementation of a simple HTTP server using the POCO libraries. The server returns a HTML document showing the current date and time. The application framework is used to build a server application that can run as a Windows service, or Unix daemon process. Of course, the same executable can also directly be started from the shell. For use with the HTTP server framework, a TimeRequestHandler class is defined that servers incoming requests by returning a HTML document containing the current date and time. Also, for each incoming request, a message is logged using the logging framework. Together with the TimeRequestHandler class, a corresponding factory class, TimeRequestHandlerFactory is needed; an instance of the factory is passed to the HTTP server object. The HTTPTimeServer application class defines a command line argument help by overriding the defineOptions() member function of Poco::Util::ServerApplication. It also reads in the default application configuration file (in initialize()) and obtains the value of some configuration properties in main(), before starting the HTTP server.
#include "Poco/Net/HTTPServer.h" #include "Poco/Net/HTTPRequestHandler.h" #include "Poco/Net/HTTPRequestHandlerFactory.h" #include "Poco/Net/HTTPServerParams.h" #include "Poco/Net/HTTPServerRequest.h" #include "Poco/Net/HTTPServerResponse.h" #include "Poco/Net/HTTPServerParams.h" #include "Poco/Net/ServerSocket.h" #include "Poco/Timestamp.h" #include "Poco/DateTimeFormatter.h" #include "Poco/DateTimeFormat.h" #include "Poco/Exception.h" #include "Poco/ThreadPool.h" #include "Poco/Util/ServerApplication.h" #include "Poco/Util/Option.h" #include "Poco/Util/OptionSet.h" #include "Poco/Util/HelpFormatter.h" #include <iostream> using Poco::Net::ServerSocket; using Poco::Net::HTTPRequestHandler; using Poco::Net::HTTPRequestHandlerFactory; using Poco::Net::HTTPServer; using Poco::Net::HTTPServerRequest; using Poco::Net::HTTPServerResponse; using Poco::Net::HTTPServerParams; using Poco::Timestamp; using Poco::DateTimeFormatter; using Poco::DateTimeFormat; using Poco::ThreadPool; using Poco::Util::ServerApplication; using Poco::Util::Application; using Poco::Util::Option; using Poco::Util::OptionSet; using Poco::Util::OptionCallback using Poco::Util::HelpFormatter; class TimeRequestHandler: public HTTPRequestHandler { public: TimeRequestHandler(const std::string& format): _format(format) { } void handleRequest(HTTPServerRequest& request, HTTPServerResponse& response) { Application& app = Application::instance(); app.logger().information("Request from " + request.clientAddress().toString()); Timestamp now; std::string dt(DateTimeFormatter::format(now, _format)); response.setChunkedTransferEncoding(true); response.setContentType("text/html"); std::ostream& ostr = response.send(); ostr << "<html><head><title>HTTPTimeServer powered by " "POCO C++ Libraries</title>"; ostr << "<meta http-equiv=\"refresh\" content=\"1\"></head>"; ostr << "<body><p style=\"text-align: center; " "font-size: 48px;\">"; ostr << dt; ostr << "</p></body></html>"; } private: std::string _format; }; class TimeRequestHandlerFactory: public HTTPRequestHandlerFactory { public: TimeRequestHandlerFactory(const std::string& format): _format(format) { } HTTPRequestHandler* createRequestHandler( const HTTPServerRequest& request) { if (request.getURI() == "/") return new TimeRequestHandler(_format); else return 0; } private: std::string _format; }; class HTTPTimeServer: public Poco::Util::ServerApplication { public: HTTPTimeServer(): _helpRequested(false) { } ~HTTPTimeServer() { } protected: void initialize(Application& self) { loadConfiguration(); ServerApplication::initialize(self); } void uninitialize() { ServerApplication::uninitialize(); } void defineOptions(OptionSet& options) { ServerApplication::defineOptions(options); options.addOption( Option("help", "h", "display argument help information") .required(false) .repeatable(false) .callback(OptionCallback<HTTPTimeServer>( this, &HTTPTimeServer::handleHelp))); } void handleHelp(const std::string& name, const std::string& value) { HelpFormatter helpFormatter(options()); helpFormatter.setCommand(commandName()); helpFormatter.setUsage("OPTIONS"); helpFormatter.setHeader( "A web server that serves the current date and time."); helpFormatter.format(std::cout); stopOptionsProcessing(); _helpRequested = true; } int main(const std::vector<std::string>& args) { if (!_helpRequested) { unsigned short port = (unsigned short) config().getInt("HTTPTimeServer.port", 9980); std::string format( config().getString("HTTPTimeServer.format", DateTimeFormat::SORTABLE_FORMAT)); ServerSocket svs(port); HTTPServer srv(new TimeRequestHandlerFactory(format), svs, new HTTPServerParams); srv.start(); waitForTerminationRequest(); srv.stop(); } return Application::EXIT_OK; } private: bool _helpRequested; }; int main(int argc, char** argv) { HTTPTimeServer app; return app.run(argc, argv); }
POCO Data is POCO's database abstraction layer which allows users to easily send/retrieve data to/from various different SQL databases. The following complete example shows how to use it:
#include "Poco/Data/Common.h" #include "Poco/Data/SQLite/Connector.h" #include <iostream> using namespace Poco::Data; void init() { SQLite::Connector::registerConnector(); } void shutdown() { SQLite::Connector::unregisterConnector(); } int main(int argc, char* argv[]) { init(); Session ses("SQLite", "sample.db"); int count = 0; ses << "SELECT COUNT(*) FROM PERSON", into(count), now; std::cout << "People in DB " << count; shutdown(); }
The above example is pretty much self explanatory. The Poco/Data/Common.h file pulls in some common includes, the SQLite::Connector is used to register the SQLite connector so that we can later create an SQLite session via the SessionFactory. The two-argument constructor
Sesssion ses("SQLite", "sample.db");
is actually equivalent to:
Session ses(SessionFactory::instance()::create("SQLite", "sample.db"));
The << operator is used to send SQL statements to the Session, the into(count) simply informs the session where to store the result of the query. Take note of the now at the end of the SQL statement. It is required, otherwise the statement would not be executed. The using namespace Poco::Data is for convenience only but highly recommended for good readable code (while ses << "SELECT COUNT(*) FROM PERSON", Poco::Data::into(count), Poco::Data::now; is valid, it simply looks... strange).
The remainder of this tutorial is split up into the following parts:
Sessions are always created via the SessionFactory create method, or implicitly via the two-argument Session constructor.
Session create(const std::string& connectorKey, const std::string& connectionString); The first parameter contains the type of the Session one wants to create. For the moment "SQLite" is supported directly, and via the ODBC driver support for Oracle, SQLite, DB2, SQLServer and PostgreSQL is available. The second parameter contains the (connector-specific) connection string. In the case of SQLite, the location of the database file is sufficient.
Inserting data works by using the content of other variables. Assume we have a table that stores only forenames:
ForeName (Name VARCHAR(30))
If we want to insert one single forename we could simply write:
std::string aName("Peter"); ses << "INSERT INTO FORENAME VALUES(" << aName << ")", now;
Well, we could do that, but we won't. A much better solution is to use placeholders and connect each placeholder via a use expression with a variable that will provide the value during execution. Placeholders are recognized by having a : in front of their name. Rewriting the above code now simply gives
std::string aName("Peter"); ses << "INSERT INTO FORENAME VALUES(:name)", use(aName), now;
In this example the use expression matches the :name with the Peter value. Note that apart from the nicer syntax, the real benefit of placeholders - which is performance - doesn't show here. Check the Working with Statements section to find out more.
Retrieving data from the Database works similar. The into expression matches the returned database values to C++ objects, it also allows to provide a default value in case null data is returned from the database:
std::string aName; ses << "SELECT NAME FROM FORENAME", into(aName), now; // the default is the empty string ses << "SELECT NAME FROM FORENAME", into(aName, "default"), now;
It is also possible to combine into and use expressions:
std::string aName; std::string match("Peter") ses << "SELECT NAME FROM FORENAME WHERE NAME=:name", into(aName), use(match), now; poco_assert (aName == match);
Typically, tables will not be so trivial, ie. they will have more than one column which allows for more than one into/use. Lets assume we have a Person table that contains an age, a first and a last name:
std::string firstName("Peter"; std::string lastName("Junior"); int age = 0; ses << INSERT INTO PERSON VALUES (:fn, :ln, :age)", use(firstName), use(lastName), use(age), now; ses << "SELECT (firstname, lastname, age) FROM Person", into(firstName), into(lastName), into(age), now;
Most important here is the order of the into and use expressions. The first placeholder is matched by the first use, the 2nd by the 2nd use etc. The same is true for the into statement. We select firstname as the first column of the result set, thus into(firstName) must be the first into clause.
A common case with databases are optional data fields that can contain NULL. To accomodate for NULL, the into expression allows you to define default values. For example, assume that age is such an optional field and we want to provide as default value -1 which is done by writing into(age, -1):
std::string firstName("Peter"; std::string lastName("Junior"); int age = 0; ses << INSERT INTO PERSON VALUES (:fn, :ln, :age)", use(firstName), use(lastName), use(age), now; ses << "SELECT (firstname, lastname, age) FROM Person", into(firstName), into(lastName), into(age, -1), now;
While you can achieve the same effect by initializing age previously to -1 (int age = -1), this won't work with collection types. Here you must provide the second parameter to init. Otherwise, values will be initialized to compiler specific values.
We often mentioned the term Statement in the previous section, yet we only worked with database session objects so far, or at least, that's what you have been made believe ;-). In reality, you have already worked with Statements. Lets take a look at the method signature of the << operator at Session:
template <typename T> Statement Session::operator << (const T& t)
Simply ignore the template stuff in front, you won't need it. The only thing that counts here is that the operator << creates a Statement internally and returns it. What happened in the previous examples is that the returned Statement was never assigned to a variable but simply passed on to the now part which executed the statement. Afterwards the statement was destroyed. Let's take one of the previous examples and change it so that we assign the statement:
std::string aName("Peter"); Statement stmt = ( ses << "INSERT INTO FORENAME VALUES(:name)", use(aName) );
Note that we must put brackets around the right part of the assignment, otherwise the compiler will complain. If you don't like the above syntax, the following alternative is equivalent:
Statement stmt(ses); stmt << "INSERT INTO FORENAME VALUES(:name)", use(aName);
What did we achieve by assigning the statement to a variable? Well, currently nothing, apart that we can control when to execute:
std::string aName("Peter"); Statement stmt = ( ses << "INSERT INTO FORENAME VALUES(:name)", use(aName) ); stmt.execute(); poco_assert (stmt.done());
By calling execute we asserted that our query was executed and that the value was inserted. The check to stmt.done() simply guarantees that the statement was fully completed.
A prepared statement is created by omitting the now clause.
Statement stmt = ( ses << "INSERT INTO FORENAME VALUES(:name)", use(aName) );
The advantage of a prepared statement is performance. Assume the following loop:
std::string aName(); Statement stmt = ( ses << "INSERT INTO FORENAME VALUES(:name)", use(aName) ); for (int i = 0; i < 100; ++i) { aName.append("x"); stmt.execute(); }
Instead of creating and parsing the Statement 100 times, we only do this once and then use the placeholder in combination with the use clause to insert 100 different values into the database. Still, this isn't the best way to insert a collection of values into a database.
use expects as input a reference parameter, which is bound later during execution. Thus, one can only use variables, but never constants. The following code will very likely fail (but this is platform/compiler dependent and also depends if your building in release or debug mode, it will work from Monday to Thursday but will always fail on Friday, so shortly spoken: the kind of bugs software developers really love):
Statement stmt = (ses << INSERT INTO PERSON VALUES (:fn, :ln, :age)", use("Peter"), use("Junior"), use(4)); //ERR! stmt.execute();
The constant values Junior, Peter and 4 must be assigned to variables prior, otherwise their values will be invalid when execute is called.
If one needs to handle many values at once, one ought to use a collection class. Per default, the following collection types are supported:
A bulk insert example via vector would be:
std::string aName(""); std::vector<std::string> data; for (int i = 0; i < 100; ++i) { aName.append("x"); data.push_back(aName); } ses << "INSERT INTO FORENAME VALUES(:name)", use(data), now;
The same example would work with set or multiset but not with map and multimap (std::string has no () operator). Note that use requires non-empty collections!
Now reconsider the following example:
std::string aName; ses << "SELECT NAME FROM FORENAME", into(aName), now;
Previously, it worked because the table contained only one single entry but now the database table contains at least 100 strings, yet we only offer storage space for one single result. Thus, the above code will fail and throw an exception. One possible way to handle this is:
std::vector<std::string> names; ses << "SELECT NAME FROM FORENAME", into(names), now;
And again, instead of vector, one could use set or multiset.
Working with collections might be convenient to bulk process data but there is also the risk that large operations will block your application for a very long time. In addition, you might want to have better fine-grained control over your query, e.g. you only want to extract a subset of data until a condition is met. To elevate that problem, one can use the limit keyword.
Let's assume we are retrieving thousands of rows from a database to render the data to a GUI. To allow the user to stop fetching data any time (and to avoid having the user franatically click inside the GUI because it doesn't show anything for seconds), we have to partition this process:
std::vector<std::string> names; ses << "SELECT NAME FROM FORENAME", into(names), limit(50), now;
The above example will retrieve up to 50 rows from the database (note that returning nothing is valid!) and append it to the names collection, i.e. the collection is not cleared! If one wants to make sure that exactly 50 rows are returned one must set the 2nd limit parameter (which per default is set to false) to true:
std::vector<std::string> names; ses << "SELECT NAME FROM FORENAME", into(names), limit(50, true), now;
Iterating over a complete result collection is done via the Statement object until statement.done() returns true. For the next example, we assume that our system knows about 101 forenames:
std::vector<std::string> names; Statement stmt = (ses << "SELECT NAME FROM FORENAME", into(names), limit(50)); stmt.execute(); //names.size() == 50 poco_assert (!stmt.done()); stmt.execute(); //names.size() == 100 poco_assert (!stmt.done()); stmt.execute(); //names.size() == 101 poco_assert (stmt.done());
We previously stated that if no data is returned this is valid too. Thus, executing the following statement on an empty database table will work:
std::string aName; ses << "SELECT NAME FROM FORENAME", into(aName), now;
To guarantee that at least one valid result row is returned use the lowerLimit clause:
std::string aName; ses << "SELECT NAME FROM FORENAME", into(aName), lowerLimit(1), now;
If the table is now empty, an exception will be thrown. If the query succeeds, aName is guaranteed to be initialized. Note that limit is only the short name for upperLimit. To iterate over a result set step-by-step, e.g. one wants to avoid using a collection class, one would write:
std::string aName; Statement stmt = (ses << "SELECT NAME FROM FORENAME", into(aName), lowerLimit(1), upperLimit(1)); while (!stmt.done()) stmt.execute();
And for the lazy ones, there is the range command:
std::string aName; Statement stmt = (ses << "SELECT NAME FROM FORENAME", into(aName), range(1,1)); while (!stmt.done()) stmt.execute();
The third parameter to range is an optional boolean value which specifies if the upper limit is a hard limit, ie. if the amount of rows returned by the query must match exactly. Per default exact matching is off.
All the previous examples were contented to work with only the most basic data types: integer, string, ... a situation, unlikely to occur in real-world scenarios. Assume you have a class Person:
class Person { public: // default constructor+destr. // getter and setter methods for all members [...] bool operator <(const Person& p) const /// we need this for set and multiset support { return _socialSecNr < p._socialSecNr; } Poco::UInt64 operator()() const /// we need this operator to return the key for the map and multimap { return _socialSecNr; } private: std::string _firstName; std::string _lastName; Poco::UInt64 _socialSecNr; }
Ideally, one would like to use a Person as simple as one used a string. All that is needed is a template specialization of the TypeHandler template. Note that template specializations must be declared in the same namespace as the original template, i.e. Poco::Data. The template specialization must implement the following methods:
namespace Poco { namespace Data { template <> class TypeHandler<class Person> { public: static std::size_t size() { return 3; // we handle three columns of the Table! } static void bind(std::size_t pos, const Person& obj, AbstractBinder* pBinder) { poco_assert_dbg (pBinder != 0); // the table is defined as Person (FirstName VARCHAR(30), lastName VARCHAR, SocialSecNr INTEGER(3)) // Note that we advance pos by the number of columns the datatype uses! For string/int this is one. TypeHandler<std::string>::bind(pos++, obj.getFirstName(), pBinder); TypeHandler<std::string>::bind(pos++, obj.getLastName(), pBinder); TypeHandler<Poco::UInt64>::bind(pos++, obj.getSocialSecNr(), pBinder); } static void prepare(std::size_t pos, const Person& obj, AbstractPreparation* pPrepare) { poco_assert_dbg (pBinder != 0); // the table is defined as Person (FirstName VARCHAR(30), lastName VARCHAR, SocialSecNr INTEGER(3)) // Note that we advance pos by the number of columns the datatype uses! For string/int this is one. TypeHandler<std::string>::prepare(pos++, obj.getFirstName(), pPrepare); TypeHandler<std::string>::prepare(pos++, obj.getLastName(), pPrepare); TypeHandler<Poco::UInt64>::prepare(pos++, obj.getSocialSecNr(), pPrepare); } static void extract(std::size_t pos, Person& obj, const Person& defVal, AbstractExtractor* pExt) /// obj will contain the result, defVal contains values we should use when one column is NULL { poco_assert_dbg (pExt != 0); std::string firstName; std::string lastName; Poco::UInt64 socialSecNr = 0; TypeHandler<std::string>::extract(pos++, firstName, defVal.getFirstName(), pExt); TypeHandler<std::string>::extract(pos++, lastName, defVal.getLastName(), pExt); TypeHandler<Poco::UInt64>::extract(pos++, socialSecNr, defVal.getSocialSecNr(), pExt); obj.setFirstName(firstName); obj.setLastName(lastName); obj.setSocialSecNr(socialSecNr); } }; } } // namespace Poco::Data
And that's all you have to do. Working with Person is now as simple as working with a string:
std::map<Poco::UInt64, Person> people; ses << "SELECT * FROM Person", into(people), now;
The Poco::Data::RecordSet class provides a generic way to work with database tables. Using a RecordSet, one can:
To work with a RecordSet, first create a Statement, execute it, and create the RecordSet from the Statement, as follows:
Statement select(session); select << "SELECT * FROM Person"; select.execute(); RecordSet rs(select);
The number of rows in the RecordSet can be limited by specifying a limit for the Statement.
Following example demonstrates how to iterate over all rows and columns in a RecordSet:
bool more = rs.moveFirst(); while (more) { for (std::size_t col = 0; col < cols; ++col) { std::cout << rs[col].convert<std::string>() << " "; } std::cout << std::endl; more = rs.moveNext(); }
As mentioned above, the number of rows retrieved into a RecordSet at a time can be limited using the limit or range clause. Iterating over all rows in a table a bunch of rows at a time can thus be done as follows:
Statement select(session); select << "SELECT * FROM Person", range(0, 10); RecordSet rs(select); while (!select.done()) { select.execute(); bool more = rs.moveFirst(); while (more) { for (std::size_t col = 0; col < cols; ++col) { std::cout << rs[col].convert<std::string>() << " "; } std::cout << std::endl; more = rs.moveNext(); } }
Poco::Tuple and vectors of Poco::Tuple provide a convenient way to work with rows when column types are known, because TypeHandlers for them are readily available.
Consider the following example:
typedef Poco::Tuple<std::string, std::string, int> Person; typedef std::vector<Person> People; People people; people.push_back(Person("Bart Simpson", "Springfield", 12)); people.push_back(Person("Lisa Simpson", "Springfield", 10)); Statement insert(session); insert << "INSERT INTO Person VALUES(:name, :address, :age)", use(people), now;
Of course, tuples can also be used in queries:
Statement select(session); select << "SELECT Name, Address, Age FROM Person", into(people), now; for (People::const_iterator it = people.begin(); it != people.end(); ++it) { std::cout << "Name: " << it->get<0>() << ", Address: " << it->get<1>() << ", Age: " << it->get<2>() <<std::endl; }
Creating a connection to a database is often a time consuming operation. Therefore it makes sense to save a session object for later reuse once it is no longer needed.
A Poco::Data::SessionPool manages a collection of sessions. When a session is requested, the SessionPool first looks in its set of already initialized sessions for an available object. If one is found, it is returned to the client and marked as "in-use". If no session is available, the SessionPool attempts to create a new one for the client. To avoid excessive creation of sessions, a limit can be set on the maximum number of objects.
The following code fragment shows how to use the SessionPool:
SessionPool pool("ODBC", "..."); // ... Session sess(pool.get());
Pooled sessions are automatically returned to the pool when the Session variable holding them is destroyed.
Developing one's own Data Connector implementation is rather straight-forward. Just implement the following interfaces:
It is recommended to implement the classes from top to down (ie. start with Binder and Extractor) and to use a namespace that has Poco::Data as parent, e.g. Poco::Data::SQLite .
An AbstractBinder is a class that maps values to placeholders. It is also responsible to bind primitive C++ data types to database data types. The constructor of the subclass should receive everything needed to bind variables to placeholders by position. An example taken from the SQLite implementation would be:
Binder::Binder(sqlite3_stmt* pStmt): _pStmt(pStmt) { } void Binder::bind(std::size_t pos, const Poco::Int32& val) { int rc = sqlite3_bind_int(_pStmt, (int)pos, val); checkReturn(rc); } void Binder::bind(std::size_t pos, const Poco::Int16& val) { Poco::Int32 tmp = val; bind(pos, tmp); }
SQLite only needs an sqlite3_stmt as internal state, Int32 is bound via sqlite3_bind_int and Int16 values are mapped to Int32 values.
All methods are public.
AbstractBinder(); /// Creates the AbstractBinder. virtual ~AbstractBinder(); /// Destroys the AbstractBinder. virtual void bind(std::size_t pos, const Poco::Int8 &val) = 0; /// Binds an Int8. virtual void bind(std::size_t pos, const Poco::UInt8 &val) = 0; /// Binds an UInt8. virtual void bind(std::size_t pos, const Poco::Int16 &val) = 0; /// Binds an Int16. virtual void bind(std::size_t pos, const Poco::UInt16 &val) = 0; /// Binds an UInt16. virtual void bind(std::size_t pos, const Poco::Int32 &val) = 0; /// Binds an Int32. virtual void bind(std::size_t pos, const Poco::UInt32 &val) = 0; /// Binds an UInt32. virtual void bind(std::size_t pos, const Poco::Int64 &val) = 0; /// Binds an Int64. virtual void bind(std::size_t pos, const Poco::UInt64 &val) = 0; /// Binds an UInt64. virtual void bind(std::size_t pos, const bool &val) = 0; /// Binds a boolean. virtual void bind(std::size_t pos, const float &val) = 0; /// Binds a float. virtual void bind(std::size_t pos, const double &val) = 0; /// Binds a double. virtual void bind(std::size_t pos, const char &val) = 0; /// Binds a single character. virtual void bind(std::size_t pos, const char* const &pVal) = 0; /// Binds a const char ptr. virtual void bind(std::size_t pos, const std::string& val) = 0; /// Binds a string. virtual void bind(std::size_t pos, const BLOB& val) = 0; /// Binds a BLOB. virtual void reset() = 0; /// Resets the internal state, called before a rebind
An AbstractExtractor takes a result row and extracts from a given position one single value. It performs the reverse operation to the AbstractBinder, ie. it maps database types to primitive C++ types. An AbstractExtractor also has to handle null values. If it detects a null value, it is not allowed to modify the incoming value but will simply return false. An example taken from the SQLite implementation:
Extractor::Extractor(sqlite3_stmt* pStmt): _pStmt(pStmt) { } bool Extractor::extract(std::size_t pos, Poco::Int32& val) { if (isNull(pos<[ return false; val = sqlite3_column_int(_pStmt, (int)pos); return true; } bool Extractor::extract(std::size_t pos, Poco::Int16& val) { if (isNull(pos<[ return false; val = sqlite3_column_int(_pStmt, (int)pos); return true; }
All methods are public.
AbstractExtractor(); /// Creates the AbstractExtractor. virtual ~AbstractExtractor(); /// Destroys the AbstractExtractor. virtual bool extract(std::size_t pos, Poco::Int8& val) = 0; /// Extracts an Int8. Returns false if null was received. virtual bool extract(std::size_t pos, Poco::UInt8& val) = 0; /// Extracts an UInt8. Returns false if null was received. virtual bool extract(std::size_t pos, Poco::Int16& val) = 0; /// Extracts an Int16. Returns false if null was received. virtual bool extract(std::size_t pos, Poco::UInt16& val) = 0; /// Extracts an UInt16. Returns false if null was received. virtual bool extract(std::size_t pos, Poco::Int32& val) = 0; /// Extracts an Int32. Returns false if null was received. virtual bool extract(std::size_t pos, Poco::UInt32& val) = 0; /// Extracts an UInt32. Returns false if null was received. virtual bool extract(std::size_t pos, Poco::Int64& val) = 0; /// Extracts an Int64. Returns false if null was received. virtual bool extract(std::size_t pos, Poco::UInt64& val) = 0; /// Extracts an UInt64. Returns false if null was received. virtual bool extract(std::size_t pos, bool& val) = 0; /// Extracts a boolean. Returns false if null was received. virtual bool extract(std::size_t pos, float& val) = 0; /// Extracts a float. Returns false if null was received. virtual bool extract(std::size_t pos, double& val) = 0; /// Extracts a double. Returns false if null was received. virtual bool extract(std::size_t pos, char& val) = 0; /// Extracts a single character. Returns false if null was received. virtual bool extract(std::size_t pos, std::string& val) = 0; /// Extracts a string. Returns false if null was received. virtual bool extract(std::size_t pos, BLOB& val) = 0; /// Extracts a BLOB. Returns false if null was received.
AbstractPreparation is an optional interface responsible for preparing an extract. If you need it depends on the DataConnector you implement. For example, SQLite can do perfectly without it, ODBC instead requires it. SQLite doesn't need it because it works as follows:
This works because SQLites getNextResult provides the data as string, i.e. it doesn't need any type information.
The ODBC implementation is different:
AbstractPreparation is responsible for the first step. A typical prepare implementation will look like that:
void prepare(std::size_t pos, Poco::Int32 val) { _myVec[pos] = Poco::Any a(val); int* i = AnyCast<int>(&_myVec[pos]); //register int* i for output, Db specific }
Extract now changes to:
bool Extractor::extract(std::size_t pos, Poco::Int16& val) { if (isNull(pos)) return false; val = AnyCast<int>(_myVec[pos]); return true; }
AbstractPreparation(); /// Creates the AbstractPreparation. virtual ~AbstractPreparation(); /// Destroys the AbstractPreparation. virtual void prepare(std::size_t pos, Poco::Int8) = 0; /// Prepares an Int8. virtual void prepare(std::size_t pos, Poco::UInt8) = 0; /// Prepares an UInt8. virtual void prepare(std::size_t pos, Poco::Int16) = 0; /// Prepares an Int16. virtual void prepare(std::size_t pos, Poco::UInt16) = 0; /// Prepares an UInt16. virtual void prepare(std::size_t pos, Poco::Int32) = 0; /// Prepares an Int32. virtual void prepare(std::size_t pos, Poco::UInt32) = 0; /// Prepares an UInt32. virtual void prepare(std::size_t pos, Poco::Int64) = 0; /// Prepares an Int64. virtual void prepare(std::size_t pos, Poco::UInt64) = 0; /// Prepares an UInt64. virtual void prepare(std::size_t pos, bool) = 0; /// Prepares a boolean. virtual void prepare(std::size_t pos, float) = 0; /// Prepares a float. virtual void prepare(std::size_t pos, double) = 0; /// Prepares a double. virtual void prepare(std::size_t pos, char) = 0; /// Prepares a single character. virtual void prepare(std::size_t pos, const std::string& ) = 0; /// Prepares a string. virtual void prepare(std::size_t pos, const BLOB&) = 0;
Note that it is recommended to prepare a statement only once in the compileImpl of StatementImpl. The AbstractPrepare objects (which make use of AbstractPreparation can be created by iterating over the Extractor objects of the StatementImpl:
Poco::Data::AbstractExtractingVec::iterator it = extractings().begin(); Poco::Data::AbstractExtractingVec::iterator itEnd = extractings().end(); std::size_t pos = 0; // sqlite starts with pos 0 for results! your DB maybe with 1 for (; it != itEnd; ++it) { AbstractPrepare* pPrep = (*it)->createPrepareObject(pPreparation, pos); _prepareVec.push_back(pPrep); (*it)->extract(pos); pos += (*it)->numOfColumnsHandled(); }
A StatementImpl stores as member a Binder and an Extractor (optional a Preparation object) and is responsible for compiling, binding, fetching single rows from the database and invoking the Extracting objects. The interface it has to implement is given as:
public: StatementImpl(); /// Creates the StatementImpl. virtual ~StatementImpl(); /// Destroys the StatementImpl. protected: virtual bool hasNext() = 0; /// Returns true if a call to next() will return data. Note that the /// implementation must support several consecutive calls to hasNext /// without data getting lost, ie. hasNext(); hasNext(); next() must /// be equal to hasNext(); next(); virtual void next() = 0; /// Retrieves the next row from the resultset. /// Will throw, if the resultset is empty. /// Expects the statement to be compiled and bound virtual bool canBind() const = 0; /// Returns if another bind is possible. virtual void compileImpl() = 0; /// Compiles the statement, doesn't bind yet. /// From now on AbstractBinder and AbstractExtractor /// will be used virtual void bindImpl() = 0; /// Binds parameters. virtual AbstractExtractor& extractor() = 0; /// Returns the concrete extractor used by the statement. virtual AbstractBinder& binder() = 0; /// Returns the concrete binder used by the statement.
The Extracting and Binding objects can be accessed via the calls to the super-class methods extractings() and bindings(). A high-level bind implementation will look like this:
[...] Poco::Data::AbstractBindingVec& binds = bindings(); std::size_t pos = 1; // or 0 depending on your database Poco::Data::AbstractBindingVec::iterator it = binds.begin(); Poco::Data::AbstractBindingVec::iterator itEnd = binds.end(); for (; it != itEnd && (*it)->canBind(); ++it) { (*it)->bind(pos); pos += (*it)->numOfColumnsHandled(); }
A high-level next implementation:
if (!hasNext()) throw Poco::Data::DataException("No data received"); int nCol = countColumnsInResult...; poco_assert (columnsHandled() == nCol); Poco::Data::AbstractExtractingVec::iterator it = extractings().begin(); Poco::Data::AbstractExtractingVec::iterator itEnd = extractings().end(); std::size_t pos = 0; // sqlite starts with pos 0 for results! your DB maybe with 1 for (; it != itEnd; ++it) { (*it)->extract(pos); pos += (*it)->numOfColumnsHandled(); } enableHasNext();
A high-level hasNext implementation:
if (enabledhasNext()) { checkIfItHasMoreData cacheResult disablehasNext() } return cachedResult;
A high-level compileImpl:
if (compiled) return; std::string sqlStmt(toString()); f database expects placeholders in different format than ":name", parse and replace them compile statement; create Binder; create Extractor;
A high-level canBind:
bool ret = false; if (!bindings().empty() && validCompiledStatement) ret = (*bindings().begin())->canBind(); return ret;
The purpose of the SessionImpl is simply to open/close a connection to the database, to act as factory for StatementImpl objects, and to handle transactions. The connection is opened in the constructor, and closed in the destructor.
Poco::Data::StatementImpl* createStatementImpl(); /// Returns an SQLite StatementImpl void begin(); /// Starts a transaction void commit(); /// Commits and ends a transaction void rollback(); /// Aborts a transaction
Finally, one needs to implement the Connector. Each Connector should have a public static const string member named KEY and must have a factory method to create Poco::AutoPtr objects of type SessionImpl. It should also have a static addToFactory() and a static removeFromFactory() method:
class My_API Connector: public Poco::Data::Connector /// Connector instantiates SessionImpl objects. { public: static const std::string KEY; /// Keyword for creating sessions Connector(); /// Creates the Connector. ~Connector(); /// Destroys the Connector. Poco::AutoPtr < Poco::Data::SessionImpl > createSession(const std::string& connectionString); /// Creates a SessionImpl object and initializes it with the given connectionString. static void registerConnector(); /// Registers the Connector under the Keyword Connector::KEY at the Poco::Data::SessionFactory static void unregisterConnector(); /// Unregisters the Connector under the Keyword Connector::KEY at the Poco::Data::SessionFactory };
If you got stuck with a problem when developing with the POCO C++ Libraries, here's some information how to get help and support.
First, and most important, DON'T PANIC!
There is help and support available. First, try the POCO C++ Libraries Community resources on the Web, where you can find:
If these resources still not answer your question, there is also a mailing list named poco-develop, read by the maintainers of POCO . In order to defend against spam, you are required to subscribe to the list before you can send a message to it.
Portions of the POCO C++ Libraries utilize the following copyrighted material, the use of which is hereby acknowledged.
Copyright (c) 1998, 1999, 2000 Thai Open Source Software Center Ltd and Clark Cooper Copyright (c) 2001, 2002, 2003 Expat maintainers. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Copyright (c) 1983, 1993 The Regents of the University of California. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 4. Neither the name of the University nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Copyright (C) 1990-2, RSA Data Security, Inc. Created 1990. All rights reserved. License to copy and use this software is granted for non-commercial Internet Privacy-Enhanced Mail provided that it is identified as the "RSA Data Security, Inc. MD2 Message Digest Algorithm" in all material mentioning or referencing this software or this function. RSA Data Security, Inc. makes no representations concerning either the merchantability of this software or the suitability of this software for any particular purpose. It is provided "as is" without express or implied warranty of any kind. These notices must be retained in any copies of any part of this documentation and/or software.
Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All rights reserved. License to copy and use this software is granted provided that it is identified as the "RSA Data Security, Inc. MD4 Message-Digest Algorithm" in all material mentioning or referencing this software or this function. License is also granted to make and use derivative works provided that such works are identified as "derived from the RSA Data Security, Inc. MD4 Message-Digest Algorithm" in all material mentioning or referencing the derived work. RSA Data Security, Inc. makes no representations concerning either the merchantability of this software or the suitability of this software for any particular purpose. It is provided "as is" without express or implied warranty of any kind. These notices must be retained in any copies of any part of this documentation and/or software.
Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All rights reserved. License to copy and use this software is granted provided that it is identified as the "RSA Data Security, Inc. MD5 Message-Digest Algorithm" in all material mentioning or referencing this software or this function. License is also granted to make and use derivative works provided that such works are identified as "derived from the RSA Data Security, Inc. MD5 Message-Digest Algorithm" in all material mentioning or referencing the derived work. RSA Data Security, Inc. makes no representations concerning either the merchantability of this software or the suitability of this software for any particular purpose. It is provided "as is" without express or implied warranty of any kind.
PCRE is a library of functions to support regular expressions whose syntax and semantics are as close as possible to those of the Perl 5 language. Release 5 of PCRE is distributed under the terms of the "BSD" licence, as specified below. The documentation for PCRE, supplied in the "doc" directory, is distributed under the same terms as the software itself. Written by: Philip Hazel <ph10@cam.ac.uk> University of Cambridge Computing Service, Cambridge, England. Phone: +44 1223 334714. Copyright (c) 1997-2004 University of Cambridge All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the University of Cambridge nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Copyright (C) 1995-2004 Jean-loup Gailly and Mark Adler This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. Jean-loup Gailly jloup@gzip.org Mark Adler madler@alumni.caltech.edu
The original author of SQLite has dedicated the code to the public domain (http://www.sqlite.org/copyright.html). Anyone is free to copy, modify, publish, use, compile, sell, or distribute the original SQLite code, either in source code form or as a compiled binary, for any purpose, commerical or non-commerical, and by any means.
Release 1.3 of the POCO C++ Libraries is the first official release containing the Data library. The Data library has been available in a development state for the 1.2 release. For the 1.3 release, a few things have been changed in an incompatible way that requires changes to existing code.
Release 1.3 of the POCO C++ Libraries contains major improvements and new features throughout all libraries.
The (now deprecated) Poco::HashFunction class template has been changed in an incompatible way. The member function formerly named hash() is now the function call operator. If you have defined your own HashFunction classes, you have to update your code. Sorry for the inconvenience.
On Windows, POCO now builds with Unicode/UTF-8 support (POCO_WIN32_UTF8) enabled by default. If you need the previous (1.2) behavior, remove the corresponding #define from Poco/Config.h