- PVSM.RU - https://www.pvsm.ru -
В этой статье я опишу процесс разработки класса «таблицы вызовов» и применение получившегося класса для расширения функциональности программы с помощью модулей.
Есть сервер, принимающий команды. На вход он получает индекс нужной команды и ее параметры, выполняет действия и возвращает результат. Индексы команд последовательны: 0,1,2,3 и т.д. При старте у сервера есть несколько базовых команд(в моем случае 20), остальные добавляются модулями во время работы. Для решения этой задачи хорошо подходит CallTable.
Класс CallTable должен быть:
В ядре Linux используется механизм calltable для системных вызовов. Мы должны получить нечто похожее, но лишённое ограничений, присущих ядру.
class CallTable
{
private:
CallTable( const CallTable& ) = delete; //Запрещаем копирование
void operator=( const CallTable& ) = delete; //Запрещаем копирование
public:
typedef message_result::results CmdResult;
typedef CmdResult (*CallCell)(std::string);
CallCell default_cell;
CallCell* table;
unsigned int size;
unsigned int autoincrement;
CallTable(unsigned int size,CallCell _default);
unsigned int add(CallCell c);
bool realloc(unsigned int newsize);
~CallTable();
};
Указатель table будет указывать на нашу таблицу
size хранит текущий размер таблицы
autoincrement хранит последний добавленный элемент
Конструктор должен будет выделить нужный объем памяти и инициализировать нашу таблицу вызовом-по-умолчанию
CallTable::CallTable(unsigned int size,CallCell _default)
{
table = new CallCell[size];
this->size = size;
for(unsigned int i=0;i<size;++i)
{
table[i] = _default;
}
default_cell = _default;
autoincrement = 0;
}
Функция add нужна для добавления элемента в таблицу. Так как мы заранее не знаем какие индексы попадутся какому вызову, мы должны вернуть индекс только что добавленного вызова из функции add:
unsigned int CallTable::add(CallCell c)
{
if(autoincrement == size) return -1;
table[autoincrement] = c;
autoincrement++;
return autoincrement - 1;
}
Функция realloc займется расширением таблицы. Для этого нужно выделить память нужного размера, скопировать элементы, оставшиеся неинициализированные области заполнить вызовом-по-умолчанию
bool CallTable::realloc(unsigned int newsize)
{
if(newsize < size) return false;
CallCell* newtable = new CallCell[newsize];
memcpy(newtable,table,size*sizeof(CallCell));
delete[] table;
for(unsigned int i=size;i<newsize;++i)
{
newtable[i] = default_cell;
}
table = newtable;
size = newsize;
return true;
}
Деструктор отчистит выделенную память
CallTable::~CallTable()
{
delete[] table;
}
Напишем программу, использующую CallTable. Для упрощения кода в статье команды будут приниматься через stdin вместо сокетов.
Функционал тестовой программы:
Программу мы расширим с помощью модуля. Комманда loadmodule загрузит нужный модуль и выполнит функцию инициализации.
Функционал тестового модуля:
Инициализируем CallTable
std::cout << "Инициализация CallTable ";
table = new CallTable(4,&cmd_unknown);
std::cout << "OK" << std::endl;
Напишем функции для команд echo, stop и дефолтной:
message_result::results cmd_unknown(std::string)
{
return message_result::results::ERROR_CMDUNKNOWN;
}
message_result::results cmd_stop(std::string)
{
isContinue = false;
std:: cout << "[CMD STOP] Stopping" << std::endl;
return message_result::results::OK;
}
message_result::results cmd_echo(std::string e)
{
std:: cout << "[CMD ECHO] " << e << std::endl;
return message_result::results::OK;
}
Загружать модуль будем с помощью функций библиотеки libdl. Для Windows команда загрузки модуля будет отличаться.
message_result::results cmd_loadmodule(std::string file)
{
void* fd = dlopen(file.c_str(), RTLD_LAZY);
if(fd == NULL) {
return message_result::results::ERROR_FILENOTFOUND;
}
void (*test_module_main)(CallTable*);
test_module_main = (void (*)(CallTable*))dlsym(fd,"test_module_call_main");
if(test_module_main == NULL) {
dlclose(fd);
return message_result::results::ERROR_FILENOTFOUND;
}
test_module_main(table);
return message_result::results::OK;
};
Теперь эти комманды нужно добавить в таблицу:
std::cout << "Запись команд ";
table->add(&cmd_echo);
table->add(&cmd_loadmodule);
table->add(&cmd_stop);
std::cout << "OK" << std::endl;
В главном цикле нам нужно обработать команду, получить результат и вывести его в удобочитаемом виде
while(isContinue)
{
unsigned int cmdnumber = 0;
std::string param;
std::cin >> cmdnumber >> param;
if(cmdnumber >= table->size) {
std::cerr << "Команда не существует" << std::endl;
continue;
}
message_result::results r = table->table[cmdnumber](param);
using message_result::results;
if(r == results::OK) {}
else if(r == results::ERROR_FILENOTFOUND)
{
std::cout << "Файл не найден" << std::endl;
}
else if(r == results::ERROR_CMDUNKNOWN)
{
std::cout << "Вызвана default комманда" << std::endl;
}
}
Запускаем программу и смотрим на результат:
0 test
[CMD ECHO] test
2 stop
[CMD STOP] Stopping
Напишем команды для модуля: echomodule и testprogram
message_result::results cmd_testprogram(std::string)
{
std:: cout << "I am module!" << std::endl;
return message_result::results::OK;
}
message_result::results cmd_echomodule(std::string e)
{
std:: cout << "[MODULE ECHO] " << e << std::endl;
return message_result::results::OK;
}
Функция инициализации должна вызвать realloc и добавить две своих функции в таблицу вызовов
extern "C"
{
void test_module_call_main(CallTable* table);
};
void test_module_call_main(CallTable* table)
{
std::cout << "Инициализация модуля" << std::endl;
table->realloc(5);
table->add(&cmd_testprogram);
table->add(&cmd_echomodule);
std::cout << "Инициализация модуля завершена" << std::endl;
}
С помощьюю CallTable можно создавать гибкие сервисы, функционал которых может расширяться с помощью модулей. Кроме добавления собственных команд, модули могут вызывать, переопределять, удалять уже существующие команды, вставлять свои действия до и/или после команды, запускать свои потоки и всячески расширять функционал приложения. Ссылка на github [1] с кодом этой статьи.
Автор: Gravit
Источник [2]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/277813
Ссылки в тексте:
[1] Ссылка на github: https://github.com/gravit0/calltable_example
[2] Источник: https://habrahabr.ru/post/353456/?utm_source=habrahabr&utm_medium=rss&utm_campaign=353456
Нажмите здесь для печати.