Для того, чтобы действительно научиться работать с какой-то технологией, по ней надо рассказать. Этим я сегодня и займусь. Итак. Буду я писать, как пользоваться динамическими библиотеками в C++ на GNU Linux. Использовать буду компилятор g++.
А писать мы будем библиотечку, реализующую какое-нибудь простенькое шифрование.
Возвращает она библиотеку путём вызова функции create из этой библиотеки. А библиотекописатель уже должен позаботиться, чтобы у него была функция create и чтобы эта функция была extern "C", чтоб её можно было вызвать c помощью ldsym.
Надеюсь, это будет кому-то полезно или интересно.
А писать мы будем библиотечку, реализующую какое-нибудь простенькое шифрование.
Система директорий
Для начала давайте поймём, что такое динамическая библиотека. Итак, динамическая библиотека - бинарный файл, в котором реализованы некоторые функции, а может даже содержатся целые классы. В нашем случае будет содержаться класс. Если в библиотеке содержится класс, то программе которая будет использовать эту библиотеку понадобится описание этого класса, или хотя бы описание интерфейса, который этот класс реализует. Давайте для начала создадим такие каталоги:
├── build
│ └── plugins
├── CoreProgram
├── dest
│ └── plugins
└── Plugins
├── common
└── public
Команда: mkdir -p CoreProgramm build/plugins dest/plugins Plugins/{common,public}
├── build
│ └── plugins
├── CoreProgram
├── dest
│ └── plugins
└── Plugins
├── common
└── public
Команда: mkdir -p CoreProgramm build/plugins dest/plugins Plugins/{common,public}
В каталог build будет происходить компиляция основной программы, в его подкаталог plugins будет происходить компиляция плагинов
В каталог dest будет помещаться исполняемый файл основной программы, а в его подкаталог plugins будут помешаться библиотеки.
В каталоге CoreProgram будет находиться исходный код основной программы.
В каталоге Plugins будет находиться исходный код плагинов.
В подкаталоге Plugins/public будет содержаться интерфейс библиотеки. А именно - виртуальный класс, который определяет набор функций, которые должна реализовать библиотека. Я поместил его в каталог public, так как о нём должна знать как библиотека так и программа её использующая.
В подкаталоге Plugins/common будут содержаться файлы,общие для всех плагинов, но которые не должны включатся в основную программу.
Определение интерфейса
Для того,чтобы писать библиотеку мы определим интерфейс по которому она будет работать. Прелесть подобного подхода в том, что позже. когда мы захотим обновить нашу программу, а точнее реализацию некоторых функций внутри библиотеки, мы можем перекомпилировать только библиотеку, а программа останется нетронутой. Мы можем кардинально изменить её логику работы, сохранив интерфейс, и программа даже не заметит подмены. К слову, этой лазейкой часто пользуются различные нехорошие люди для заражения ваших компьютеров.
Итак, библиотека будет шифровать текстовые данные. Если точнее, она будет принимать на входе текстовую строку и возвращать текстовую строку, и инициализироваться будет с помощью файла, в котором будут задаваться некоторые параметры, для каждой реализации библиотеки свои. Иными словами, вот интерфейс нашей библиотеки:
#ifndef PLUGIN_INTERFACE #define PLUGIN_INTERFACE #include <string> #include <fstream> #include <dlfcn.h> #include <stdlib.h> #include <iostream> using namespace std; class PluginInterface { public: virtual int init(const string& filename) = 0; virtual string crypt(const string& data) = 0; virtual string decrypt(const string& data) = 0; virtual ~PluginInterface()=0; }; PluginInterface* getInstance(void* lib) { PluginInterface*(*getinstance_f)() = (PluginInterface*(*)())dlsym(lib, "create"); PluginInterface* instance = getinstance_f(); return instance; } #endifЭтот файл мы сохраним в Plugins/public/plugin_interface.h. Это описание структуры библиотечного класса и функция, возвращающая этот класс из сообщенной в неё библиотеки.
Возвращает она библиотеку путём вызова функции create из этой библиотеки. А библиотекописатель уже должен позаботиться, чтобы у него была функция create и чтобы эта функция была extern "C", чтоб её можно было вызвать c помощью ldsym.
Добавление функционала, единого для всех библиотек.
Перед тем, как реализовывать интерфейс, определим, некоторые общие правила для всех библиотек. Первое, конечно - интерфейс, который необходимо наследовать. Ещё пусть все библиотеки будут принимать конфигурационный файл в формате JSON. Это значит, что все библиотеки должны уметь его парсить. Класть в каждую библиотеку по набору классов, для работы с JSON не умно. Поможет нам уже созданный каталог common. В этот каталог мы поместим набор классов для парсинга JSON. На просторах интернета я нашел замечательную легковесную библиотеку vjson. Она полностью нам подходит. Теперь каталог common выглядит так:
├── block_allocator.cpp
├── block_allocator.h
├── json.cpp
├── json_doc.h
├── json.h
├── utils.cpp
└── utils.h
Файлы utils* я создал самостоятельно. В них определил функцию find, которая способна по тектовому ключу найти тектовое значение в json файле.
Для этого создадим каталог SimpleCrypt в Plugins и создадим там файл. simple_crypt.h
и simple_crypt.cpp.
Вот содержание simple_crypt.h:
├── block_allocator.cpp
├── block_allocator.h
├── json.cpp
├── json_doc.h
├── json.h
├── utils.cpp
└── utils.h
Реализация интерфейса библиотеки
Для этого создадим каталог SimpleCrypt в Plugins и создадим там файл. simple_crypt.h
и simple_crypt.cpp.
Вот содержание simple_crypt.h:
#ifndef SIMPLE_CRYPT_H #define SIMPLE_CRYPT_H #include "../public/plugin_interface.h" #include "../common/json_doc.h" #include "../common/utils.h" using namespace std; class SimpleCrypt : public PluginInterface { public: SimpleCrypt(){}; int init(const string& filename); string crypt(const string& data); string decrypt(const string& data); PluginInterface() { delete this->json;} JsonDocument* json; private: char replace(char inp,json_value*vlocabruary); }; extern "C" PluginInterface* create() { return new SimpleCrypt(); } #endif
Как видите, всего-то реализация интерфейса PluginInterface c добавленной одной служебной функцией, создающей экземпляр класса SimpleCrypt. Но самый сок внутри simple_crypt.cpp
Функционал очень простой. Библиотека читает JSON файл, находит в нём соответствия между символами и производит замену.
#include "simple_crypt.h" #include <iostream> int SimpleCrypt::init(const string& filename) { this->json = new JsonDocument(); int res = this->json->parse(filename.c_str()); return 1; } string SimpleCrypt::crypt(const string& data) { json_value* volcabruary = this->json->root()->first_child->first_child; const char* data_c = data.c_str(); char* result = new char[data.size()+1]; for(int i = 0;i<data.size();i++) { char c = data_c[i]; char rep = replace(c,volcabruary); if(rep!='\0') result[i]=rep; else result[i] = ' '; } result[data.size()]='\0'; return string(result); } string SimpleCrypt::decrypt(const string& data) { json_value* volcabruary = this->json->root()->first_child->first_child->next_sibling; const char* data_c = data.c_str(); char* result = new char[data.size()]; for(int i = 0;i<data.size();i++) { char c = data_c[i]; char rep = replace(c,volcabruary); if(rep!='\0') result[i]=rep; else result[i] = ' '; } return string(result); } char SimpleCrypt::replace(char inp,json_value* vlocabruary) { char* temp = new char[2]; sprintf(temp,"%c\0",inp); char*result = 0; find(temp,vlocabruary,&result); if(result) { char res = result[0]; delete temp; return res; } return '\0'; }
Функционал очень простой. Библиотека читает JSON файл, находит в нём соответствия между символами и производит замену.
Компиляция библиотеки:
Компилировать библиотеку я буду из каталога build/plugins/SimpleCrypt и выглядеть это будет так
g++ -shared -fPIC ../../../Plugins/SimpleCript/simple_crypt.cpp ../../../Plugins/common/*.cpp ../../../Plugins/public/*.h -o ../../../dest/plugins/libSC.so
С ключ -shared указывает на то, что это будет shared library, а fPIC говорит нам о независимости разсоложения в памяти исполняемого кода. Это обязательно нужно для динамической библиотеки, так как она может поместиться во время выполнения куда угодно. Подробней про fPIC прочесть можно тут http://stackoverflow.com/questions/5311515/gcc-fpic-option .
Результатом стал libSC.so файл в каталоге /dest/plugins.
Написание клиента и тестирование библиотеки:
Самое важное - это чтобы библиотека работала. Для тестирования этого напишем такого клиента.
У неё простая логика. В качестве первого параметра принимается файл библиотеки, вторым параметром передаётся json файл с параметрами. Клиент извлекает из библиотеки реализацию класса, который уже займётся простейшим шифрованием. Вот и всё.
Выполняется это так:
dest $ ./prog ./plugins/libSC.so ./test1.json
love you
ikrb xkz
#include "../Plugins/public/plugin_interface.h" #include <iostream> int main(int argc,char**argv) { void * lib;; lib = dlopen(argv[1], RTLD_LAZY); if (!lib) { printf("cannot open library '%s'n", argv[1]); return 1; } PluginInterface* cript = getInstance(lib); cript->init(argv[2]); std::string love("love you"); std::string cripted = cript->crypt(love); std::cout<<love<<std::endl; std::cout<<cripted<<std::endl; return 0; }
У неё простая логика. В качестве первого параметра принимается файл библиотеки, вторым параметром передаётся json файл с параметрами. Клиент извлекает из библиотеки реализацию класса, который уже займётся простейшим шифрованием. Вот и всё.
Выполняется это так:
dest $ ./prog ./plugins/libSC.so ./test1.json
love you
ikrb xkz
А вот так выглядит конечное дерево проекта:
├── build
│ ├── build.sh
│ └── plugins
│ └── SimpleCrypt
│ └── build.sh
├── CoreProgramm
│ └── main.cpp
├── dest
│ ├── plugins
│ │ └── libSC.so
│ ├── prog
│ └── test1.json
└── Plugins
├── common
│ ├── block_allocator.cpp
│ ├── block_allocator.h
│ ├── json.cpp
│ ├── json_doc.h
│ ├── json.h
│ ├── utils.cpp
│ └── utils.h
├── public
│ └── plugin_interface.h
└── SimpleCript
├── simple_crypt.cpp
└── simple_crypt.h
отзывы Одна Семья
ОтветитьУдалить