📜 ⬆️ ⬇️

Portable Components, software development tools


Continuing my previous article on the POCO ( Portable Components ) library, I would like to talk about the POCO Application snap-in and its derivatives such as ServerApplication and ConsoleApplication.
The Application snap-in is designed to simplify software development and, as a rule, save time. Using this snap-in, we can create console applications, Windows services and UNIX daemons in minutes.


Description


Derivatives from Application are divided into 2 groups: console and server .
Equipment includes such things that the application needs, such as:

Practice


To create a program using this snap-in, you must inherit from Poco :: Util :: Application and overload the following methods:

Application launch options

Application startup parameters in POCO are implemented using the Option class.
Each parameter has the following properties:

Parameters can be grouped and can be optional. Validators can be attached to each parameter. Two types of validators are predefined in POCO: IntValidator - checks numerical values, RegExpValidator - checks a parameter for consistency with a regular expression. If the program is started with parameters that failed to validate, the program will return an error and show all possible options, which in turn are generated automatically. You can “hang” handler functions (callbacks) on the parameters, which will be called when these parameters are used during initialization.
 class myApp : public Application { public: myApp(int argc, char** argv) : Application(argc,argv) {} void initialize(Application& self) { cout << "" << endl; loadConfiguration(); //    Application::initialize(self); } void reinitialize() { cout << "" << endl; Application::uninitialize(); } void uninitialize(Application& self) { cout << "" << endl; Application::reinitialize(self); } void HelpHim(const std::string& name, const std::string& value) { cout << "  -   " << endl; } void Configure(const std::string& name, const std::string& value) { cout << "     " << endl; } void defineOptions(OptionSet& options) { cout << " " << endl; Application::defineOptions(options); options.addOption( Option("help", "h", " . ") .required(false) //  .repeatable(false) //  //myApp::handleOption - -  .callback(OptionCallback<myApp>(this, &myApp::handleOption))); options.addOption( Option("config-file", "f", "   ") .required(false) .repeatable(true) .argument("file") .callback(OptionCallback<myApp>(this, &myApp::Configure))); options.addOption( Option("bind", "b", "  =") .required(false) //  -   .argument("value") // ,  ,       [0; 100] .validator(new IntValidator(0, 100)) .binding("test.property")); //     } int main(const std::vector<std::string>& args) { cout << " -" << endl; } }; //  POCO_APP_MAIN   -  : // int wmain(int argc, wchar_t** argv) // { // myApp A(argc,argv); // return A.run(); // } POCO_APP_MAIN(myApp) 


Tools for creating UNIX daemons and Windows services.

To create a server, it is sometimes necessary that its process be launched from another user (for example, from a system) and does not occupy resources from the latter. Also, this function is useful for launching an application when the OS starts and did not depend on the user status. The implementation of a service or daemon in POCO is reduced to inheritance from Poco :: Util :: ServerApplication .
')
We implement a class of a task, which will be the logic of our server, for example, every second it will write to the log how much our program has completed:
 class myServerTask: public Task { public: myServerTask(): Task("MyTask") //    "MyTask" { } //  void runTask() { Application& app = Application::instance(); while (!isCancelled()) { //  sleep(1000); //    Application::instance().logger().information ("  " + DateTimeFormatter::format(app.uptime())); } } }; 

Next, we implement the server directly:
 class myServer: public ServerApplication { protected: void initialize(Application& self) { //  loadConfiguration(); // ServerApplication ServerApplication::initialize(self); //       logger().setChannel(AutoPtr<FileChannel>(new FileChannel("C:\\log.log"))); //    logger().information(""); } void uninitialize() { logger().information(""); // ServerApplication ServerApplication::uninitialize(); } int main(const std::vector<std::string>& args) { if (!config().getBool("application.runAsDaemon") && !config().getBool("application.runAsService")) { //     //       cout << "   ,      " << endl; } else { //        //  //   TaskManager tm; //     tm.start(new myServerTask); //     waitForTerminationRequest(); //     tm.cancelAll(); tm.joinAll(); } // return Application::EXIT_OK; } }; //  POCO_SERVER_MAIN(myServer) 


Everything, service and demon are written.
Now compile and register the Windows service with the following keys:


The application is launched and terminated as follows:


Loading configuration

Configuration loaded method:
 void loadConfiguration(const std::string& path, int priority = PRIO_DEFAULT); 

File type is determined by the extension:

Once the data is loaded you can use it. In POCO, the data model is a tree in which access to each element is specified by a string.
For example XML:
 <?xml version="1.0" encoding="UTF-8"?> <recipe name="" preptime="5" cooktime="180"> <title> </title> <composition> <ingredient amount="3" unit=""></ingredient> <ingredient amount="0.25" unit=""></ingredient> <ingredient amount="1.5" unit=""> </ingredient> <ingredient amount="1" unit=" "></ingredient> </composition> <instructions> <step>     .</step> <step>         .</step> <!-- <step>  .</step> -   ... --> <step>  ,       .</step> </instructions> </recipe> 

Ship as follows:
 void initialize(Application& self) { ofstream file("out.txt"); cout << "" << endl; loadConfiguration("a:\\conf.xml"); file << " : " << config().getString("title") << endl << "   : " << config().getString("composition.ingredient[0]") << " : " << config().getString("composition.ingredient[0][@amount]") << " " << config().getString("composition.ingredient[0][@unit]") << endl << config().getString("composition.ingredient[1]") << " : " << config().getString("composition.ingredient[1][@amount]") << " " << config().getString("composition.ingredient[1][@unit]") << endl << config().getString("composition.ingredient[2]") << " : " << config().getString("composition.ingredient[2][@amount]") << " " << config().getString("composition.ingredient[2][@unit]") << endl << config().getString("composition.ingredient[3]") << " : " << config().getString("composition.ingredient[3][@amount]") << " " << config().getString("composition.ingredient[3][@unit]") << endl << " : " << endl << config().getString("instructions.step[0]") << endl << config().getString("instructions.step[1]") << endl << config().getString("instructions.step[2]") << endl; int timeToCook = config().getInt("[@cooktime]"); file << "  : " << timeToCook << endl; file.close(); } 

The result is:
We cook: Simple bread
For this we need: Flour: 3 glass
Yeast: 0.25 grams
Warm water: 1.5 glass
Salt: 1 tsp.
Perform the steps:
Mix all ingredients and knead thoroughly.
Cover with a cloth and leave for one hour in a warm room.
Knead again, put on a baking sheet and put in the oven.
Cooking time: 180

Similarly, you can parse and INI. Accordingly, there will always be an identifier of the form "category.key".
for example
 ;INI-File [Group] ValueText = "hello world" IntValue = 123 

Ship as follows:
 std::string text = config().getString("Group.ValueText"); // text == "Hello world" int value = config().getInt("Group.IntValue"); // value == 123 

The .property files have the name of the variable itself in the file.
; Java property file
Value.Text = "hello world"
Int.Value = 123

Ship as follows:
 std::string text = config().getString("Value.Text"); // text == "Hello world" int value = config().getInt("Int.Value"); // value == 123 


Logging tools

Logging tools consist of four main parts:

Logger is a link in the given chain that is accessed by our application to send data to the log. The unit of the logging process is the message.
The message is an object that has:

Priorities are set in the following sequence (from low to high):

The data is represented by a string, but other data can be encoded into it. A time stamp is created with an accuracy of a microsecond.

The channel is the link between the logger and the storage object.
There are several base channels:


Example of using the logger:
 //  AutoPtr<ConsoleChannel> console(new ConsoleChannel); //  AutoPtr<PatternFormatter> formater(new PatternFormatter); formater->setProperty("pattern", "%Y-%m-%d %H:%M:%S %s: %t"); //  AutoPtr<FormattingChannel> formatingChannel(new FormattingChannel(formater, console)); //  Logger::root().setChannel(formatingChannel); //   Logger::get("Console").information("  "); //      AutoPtr<FormattingChannel> file(new FormattingChannel(formater, AutoPtr<FileChannel>(new FileChannel("A:\\123.txt")))); //  Logger::create("File", file); //  Logger::get("File").fatal("I want to play a game.    "); //   AutoPtr<SplitterChannel> splitter(new SplitterChannel); //       splitter->addChannel(file); splitter->addChannel(console); //    Logger::create("AllChannel", file); //    Logger::get("AllChannel").fatal("    "); //    AutoPtr<EventLogChannel> event(new EventLogChannel); //  Logger::create("Event", event); //     (  Windows) Logger::get("Event").fatal("   "); 


We make classes in separate modules

In POCO, the basic concept is modularity at any cost, and such modularity at runtime can be achieved with a good tool — the class loader (ClassLoader), which allows loading from dynamic libraries.
Implement an abstract class for sorting an array.
To export, you need to implement a default constructor and a virtual destructor in the base class, as well as create a pure virtual method virtual string name () const = 0; and in the heir class to implement it.
 // sort.h class ABaseSort { protected: vector<int> array; //   public: ABaseSort () {} // - virtual ~ABaseSort() {} // virtual string name() const = 0; //  name ,    //    virtual void sort() = 0; //  - void loadVector(vector<int>& lArray) { array.assign(lArray.begin(), lArray.end()); } vector<int> getArray() { return array; } //Xor-swap static void swap(int &A, int &B) { A ^= B ^= A ^= B; } }; 

Next, create 2 sorting classes: using the bubble method and the standard STL method (stable_sort)
 //    // sort.cpp #include "sort.h" class bubbleSort : public ABaseSort { public: //   string name() const { return "Bubble Sort"; } //     void sort() { size_t size = array.size(); for (int i=0; i<size-1; ++i) for (int j=i; j<size; ++j) if (array[i] > array[j]) swap(array[i],array[j]); } }; //   STL (std::stable_sort) class stableSort : public ABaseSort { public: //   string name() const { return "Stable Sort"; } //     void sort() { stable_sort(array.begin(), array.end()); } }; 

It remains to add export options
 POCO_BEGIN_MANIFEST(ABaseSort) //   POCO_EXPORT_CLASS(bubbleSort) //     POCO_EXPORT_CLASS(stableSort) //    stable_sort POCO_END_MANIFEST 

We compile the project as a dynamic library.
And now let's use our classes.
 // logic.cpp #include "sort.h" //     ABaseSort Poco::ClassLoader<ABaseSort> loader; loader.loadLibrary("myImportedFile.dll"); //   if (loader.isLibraryLoaded("myImportedFile.dll")) { //    cout << "   : " << endl; for (auto it = loader.begin(); it != loader.end(); ++it) { cout << "  '" << it->first << "': " << endl; for (auto jt = it->second->begin(); jt != it->second->end(); ++jt) { cout << jt->name() << endl; } } //  int arr[13] = {32,41,23,20,52,67,52,34,2,5,23,52,3}; vector<int> A (arr,arr+13); //   if (ABaseSort *sort = loader.create("bubbleSort")) { //    sort->loadVector(A); // sort->sort(); //  auto vect = sort->getArray(); // for (auto it = vect.begin(); it != vect.end(); ++it) cout << *it << " "; cout << endl; //    loader.classFor("bubbleSort").autoDelete(sort); } //     stableSort if (ABaseSort *sort = loader.create("stableSort")) { sort->loadVector(A); sort->sort(); auto vect = sort->getArray(); for (auto it = vect.begin(); it != vect.end(); ++it) cout << *it << " "; cout << endl; loader.classFor("stableSort").autoDelete(sort); } } 

Thus, we can change the logic of the program, without recompiling it completely. It is enough to recompile its individual modules and “feed” them to the program.

Conclusion


The above examples show some features of the development using the POCO library. You may notice that creating an application or service on a POCO is not a hard work. In the future, I would like to tell you in detail about the modules XML, ZIP, Data, Net. More in detail will stop on creation of high-performance servers on POCO. Disassemble the notification and event system (Notifications & Events), the caching system and the cryptography module.

Thank you for reading the article. Reasoned criticism and suggestions are welcome.

Source: https://habr.com/ru/post/148227/


All Articles