一文搞懂Poco::Util::Application框架

422 阅读4分钟

以下基于Poco::Utils::Application框架,实现了一个非常简单的程序。涉及了:

  • 主程序框架(Poco::Util::Application)
  • 参数解析(Poco::Util::Option)
  • 日志文件(Poco::Logger)
  • 子系统(Poco::Util::Subsystem)

程序调用示例(参数可设置为Unix风格,或其它风格)

xxx.exe --help
xxx.exe -h
xxx.exe --file="C:\hello world\1.txt"
xxx.exe -f=D:\windows\2.docx
xxx.exe --time=13134
#include <iostream>

#include "Poco/Util/Application.h"
#include "Poco/Util/HelpFormatter.h"
#include "Poco/Util/PropertyFileConfiguration.h"
#include "Poco/File.h"
#include "Poco/SimpleFileChannel.h"
#include "Poco/PatternFormatter.h"
#include "Poco/FormattingChannel.h"
#include "Poco/DateTimeFormatter.h"
#include "Poco/DateTimeFormat.h"
#include "Poco/Format.h"

class BackgroundSubSystem : public Poco::Util::Subsystem
{
public:
    virtual const char* name() const override
    {
        return "BackgroundSubSystem";
    }

    void doSomething()
    {
    }

protected:
    virtual void initialize(Poco::Util::Application& app) override
    {
        // 初始化子系统
    }

    virtual void uninitialize() override
    {
        // 反初始化子系统
    }
};

class TheApp : public Poco::Util::Application
{
public:
    TheApp()
    {
        // 指定命令行风格为Unix风格,即:--help --key=value --key2="hello world" -t -i=12
        // Windows平台默认风格为:/help /key=value
        setUnixOptions(true);
        addSubsystem(new BackgroundSubSystem); // 子系统需要在构造函数中添加
    }

protected:
    // 回调顺序:0
    // 该接口最先被Poco::Application框架调用
    virtual void defineOptions(Poco::Util::OptionSet& options) override
    {
        __super::defineOptions(options); // 必须调用一次父类接口
        options.addOption(
            Poco::Util::Option("help", "h", "display help information on command line arguments")
            .required(false) // 是否必须
            .repeatable(false) // 是否可重复
            .callback(Poco::Util::OptionCallback<TheApp>(this, &TheApp::onHelp))); // 设置参数回调函数
        options.addOption(
            Poco::Util::Option("file", "f", "Run application as a daemon.")
            .required(false)
            .repeatable(false)
            .argument("file")); // 有值的参数,必须加argument,否则无法解析,示例:--file=1.txt
        options.addOption(
            Poco::Util::Option("time", "t", "Time Information.")
            .required(false)
            .repeatable(false)
            .argument("time").binding("application.commandline.time", this->configPtr())); // 将参数直接解析到配置信息中去
    }

    // 回调顺序:1
    // 此接口负责程序的初始化
    virtual void initialize(Application& app) override
    {
        initializeLogger();
        loadConfigurationEx();
        __super::initialize(app); // 必须调用一次父类接口
    }

    // 回调顺序:2
    // 此接口负责程序的主要逻辑
    virtual int main(const std::vector<std::string>& args) override
    {
        testConfig();
        testLogger();
        testSubSystem();
        TheApp::instance().name(); // 可以在程序的任何地方,通过单例拿到Application对象
        return Poco::Util::Application::EXIT_OK;
    }

    // 回调顺序:3
    // 此接口负责程序的反初始化
    virtual void uninitialize() override
    {
        saveConfigurationEx();
        __super::uninitialize(); // 必须调用一次父类接口
    }

    // 回调顺序:0-0
    // 函数defineOptions在执行过程中,会遍历所有匹配的命令行,并调用本接口
    virtual void handleOption(const std::string& name, const std::string& value) override
    {
        __super::handleOption(name, value); // 必须调用一次父类接口
    }

private:
    // 回调顺序:0-1
    // 函数defineOptions在执行过程中,发现参数设置了回调后,会调用对应的回调函数
    void onHelp(const std::string& name, const std::string& value)
    {
        // 打印帮助信息
        Poco::Util::HelpFormatter helpFormatter(options());
        helpFormatter.setUnixStyle(true);
        helpFormatter.setCommand(commandName());
        helpFormatter.setUsage("OPTIONS");
        helpFormatter.setHeader("A web server that serves the current date and time.");
        helpFormatter.format(std::cout);
        stopOptionsProcessing(); // 中断参数的解析,后面的参数不再继续解析
        exit(1);
    }

    void loadConfigurationEx()
    {
#ifndef _LOAD_POCO_DEFAULT_CONFIGURATION
        // 加载自定义配置文件(读写)
        // 不使用自定义配置文件时,所有配置信息都存储在内存中,随着程序的退出,配置信息将不复存在。
        // 如果希望配置信息记录到文件中,需要将文件配置信息的优先级数值设为最低,且有写权限。
        // 每次设置配置时,会按优先级顺序找到第一个具有写权限的。
        Poco::Path confPath(config().getString("application.path"));
        confPath.setExtension("properties");
        Poco::File(confPath).createFile();
        config().add(new Poco::Util::PropertyFileConfiguration(confPath.toString()), "properties", PRIO_APPLICATION, true);
#else
        // 加载默认路径下的配置文件(只读)
        // 后缀名支持4种:properties;ini;json;xml
        // 注意:这里的配置是只读的,需要我们预先准备好配置文件,放置在程序所在路径下,文件名与程序同名
        // 没有只读配置,无需调用该接口
        loadConfiguration();
#endif

        // 框架默认自带了两层配置信息:
        // 第一层是系统级(只读),优先级PRIO_SYSTEM,提供了系统名称,版本号,CPU框架等信息,具体参见Poco::Util::SystemConfiguration类说明
        // 第二层是应用级(读写),优先级PRIO_APPLICATION,提供了程序的目录,名称等信息,也可以存储一些我们自定义的配置信息
        // 以上配置均存储于内存中,程序退出即销毁。
        //
        // 设置配置信息,会按优先级顺序找到第一个有写权限的配置接口,写入后返回。
        // 优先级相同的配置,按插入顺序,后插入的排在前面。
    }

    void saveConfigurationEx()
    {
#ifndef _LOAD_POCO_DEFAULT_CONFIGURATION
        // 保存到配置文件(程序中更新的配置信息只更新了内存,并没有更新到文件中)
        auto autoPtr = config().find("properties");
        auto pConfig = dynamic_cast<Poco::Util::PropertyFileConfiguration*>(autoPtr.get());
        if (pConfig)
        {
            Poco::Path confPath(config().getString("application.path"));
            confPath.setExtension("properties");
            pConfig->save(confPath.toString());
        }
    }
#endif

    void initializeLogger()
    {
        // 日志默认输出到命令行,如果要输出到文件,需要调整个日志输出通道(Channel)
        // 日志内置了一些属性,用于设置文件路径、限制文件大小等,具体参见Poco::SimpleFileChannel类说明

        // 简单文件输出通道
        Poco::AutoPtr<Poco::SimpleFileChannel> pFileChannel(new Poco::SimpleFileChannel);
        pFileChannel->setProperty("path", "sample.log");
        pFileChannel->setProperty("rotation", "2 K");
        // Poco::Logger::root().setChannel(pFileChannel);

        // 格式化输出通道(支持的格式化语句参见Poco::PatternFormatter类说明)
        Poco::AutoPtr<Poco::PatternFormatter> pPatternFmt(new Poco::PatternFormatter("%H:%M:%S.%i [%q] [%O@%u] %t"));
        Poco::AutoPtr<Poco::FormattingChannel> pFmtChannel(new Poco::FormattingChannel(pPatternFmt, pFileChannel));

        // 设置输出通道
        Poco::Logger::root().setChannel(pFmtChannel);
    }

    void testConfig()
    {
        // 读取参数配置信息(该配置信息由参数绑定而来)
        if (config().hasProperty("application.commandline.time"))
        {
            auto cmdtime = config().getInt64("application.commandline.time");
        }

        // 读取文件配置信息(该配置信息由配置文件解析得到)
        if (config().hasProperty("global.application.name"))
        {
            auto appname = config().getString("global.application.name");
        }
        else
        {
            config().setString("global.application.name", commandName());
        }
    }

    void testLogger()
    {
        poco_information(logger(),
            Poco::format(u8"================ 程序启动 (%s) ================ ",
                Poco::DateTimeFormatter::format(startTime(), Poco::DateTimeFormat::SORTABLE_FORMAT)));

        poco_warning(logger(), u8"警告信息");
        poco_fatal(logger(), u8"致命信息");
    }

    void testSubSystem()
    {
        getSubsystem<BackgroundSubSystem>().doSomething();
    }
};

POCO_APP_MAIN(TheApp)