msvcore – c++, кроссплатформенная библиотека, велосипед и 12 лет разработки

в 9:41, , рубрики: algorithms, c++, crossplatform, library, Алгоритмы, ненормальное программирование

Привет Хабр, я хочу рассказать о творении, на которое потратил свою молодость, уж лучше бы пил и курил.

Msvcore – это кроссплатформенная библиотека для c++, написанная с нуля по принципам оптимальности, открытости и простоты. По крайней мере, это закладывалось как базовая идея. Что получилось в итоге…

Немного истории

Все началось в далеком 2004 году, когда я начал работать кем-то вроде сисадмина на все руки, а заодно начал увлекаться c++. И, как сейчас помню, MFC с его шаблонами и строки CString. Тогда и возникла мысль написать свои строки, простые и понятные. И понеслось.

К сожалению у меня сохранился лишь архив от октября 2005, по нему и буду восстанавливать события. Взглянуть на него вы можете на гитхабе. Самая ранняя дата в архиве датируется 10 октября 2004, за неимением другого, этот день и можно считать днем рождения библиотеки (Date: Sun, 10 Oct 2004 12:50:42 GMT).

Интересно, что библиотека, в отличие от других, создавалась путем эволюции. В нее добавлялся часто используемый код, и дописывалась она под текущие задачи. Задачи усложнялись, библиотека росла. Но в этом есть и минус, вы можете не найти функций, которые, казалось бы обязаны быть в каждой библиотеке. По тому она и велосипед, который дописывается в процессе разработки.

Первым компонентом в моей библиотеке был класс строк MString, полное название MyString.

class MString{ 
	char * data; // Указатель на данные
	unsigned int sz; // Размер строки
	unsigned int rsz; // Размер выделенной памяти
}

Бинарные строки, с одним буфером, заканчивающиеся дополнительным нулевым байтом, который требовался для большинства стандартных функций: open, strlen(), …

После первой большой программы, со статическими массивами, нелогичной логикой и прочими косяками начинающего программиста, потребовались динамические массивы. Общее название первых динамических массивов — MMatrix. Основной принцип: Родительский элемент с указателями на первый и последний элементы массива и, счетчик элементов. Элементы массива имели указатели на предыдущий, следующий элементы и данные. Для каждого варианта данных делалась своя копия класса. Шаблоны? Нет, не слышали. В последствии, классы массивов разрабатывались чуть ли не раз в год.

Так же одним из первых был создан класс MSVCF для работы с конфигурационными файлами.
Писались свои аналоги стандартный функций: itos() и stoi(), itos64() и stoi64(), ftos() и stof() для перевода числа в строку и обратно для int, int64, float. itob() и btoi() тоже, но для бинарных строк. stos() конвертор short в char. explode() для разбиения строки на части. rts() (read to sumbol) и компания, для поиска символа/строки в строке. Создан класс ILink, который потребовался для разбора путей и ссылок на части и используется до сих пор. В какой-то момент потребовался класс IHeader для работы с http заголовками. До сих пор актуален набор функций MSVThreads для создания новых потоков. Создан класс MTime для работы со временем.

Это были зачатки библиотеки, которой еще много предстоит пройти. Объем текста в библиотеке 115кб.

Базовые принципы

Библиотека затачивалась на оптимальность, понятность, логичность и много других хороших слов. На деле, она до сих пор полна страшного кода, написанного в одну строку. Лишь в прошлом году код начал писаться с расстановкой отступов между знаками.

Для использования библиотеки нужно включить в проект два файла MString.cpp и VString.cpp. Попытки подключать библиотеку в виде подключаемого модуля провалились ввиду сложности постоянно пересобирать два проекта вместо одного. Так же, в подключаемой библиотеке нельзя применить изменения на лету. Изначально библиотека включалась в сам проект, но со временем сборка начала занимать продолжительное время, поэтому была разделена на части, чтобы при изменении требовалось пересобрать лишь часть проекта. Проекты на основе библиотеки обычно состоят из трех главных файлов: ProjectName.cpp, MString.cpp, VString.cpp. Код программы пишется в ProjectName.cpp, следующие два подключаются из библиотеки. Здесь часто не используется стандартное разделение кода на cpp и h файлы, для ускорения написания кода. Библиотека и программы порой писалась днями и ночами, и лишние задержки были не к чему.

Слово о кроссплатформенности. После знакомства с линуксом в 2006 году библиотека была допилена для сборки в gcc. Потом под WinCE, Android(Jni) и даже Flash(Crossbridge). Стоит заметить, что все программы изначально пишутся в Windows и MSVS и, лишь потом переносятся на конечную платформу для дописывания платформозависимой части и тестирования. Что экономит уйму времени и сил.

Я принципиально не использовал библиотеки, за исключением тех, которые бессмысленно и сложно переписывать: zlib, openssl, pcre, mysql.

Строки

Концепция строк выстраивалась довольно долго. Варианты с использованием счетчика ссылок, когда несколько переменных указывают на один блок памяти, работали не достаточно оптимально, для них требовалось использовать блокировки и более сложная логика, а в те времена я делал серьезный упор на экономии и простоте.

В какой-то момент появились виртуальные строки VString, сначала как отдельный класс, а впоследствии, решая проблему конвертации строк между классами, как базовый.
В итоге, на сегодняшний день картина такова:

Класс VString (Virtual String) – виртуальные строки, содержит указатель на данные и их размер. Класс не работает с выделением/освобождением памяти. Позволяет быстро выполнять операции со строками, не требующие изменения данных: получение части строки, поиск по строке. Класс заменяет собой часто используемый указатель на строки char*, оканчивающиеся нулем. Я считаю последний вариант работы со строками не оптимальным и попросту опасным, неверные данные в строке не должны вызывать ошибки. Главная проблема в VString – следить за актуальностью данных, на которые указывает переменная этого класса.

class VString{
public:
	unsigned char *data;
	unsigned int sz;
functions...
};

Класс MString (My String) – стандартные строки с выделением памяти под них. Память перевыделяется при каждом изменении размера строки, что делает использование этих строк крайне медленным. Данные строки следует использовать там, где другие варианты строк не подходят. Используются в роли переменных в классах. Здесь основная проблема – необходимость использования блокировок при доступе из нескольких потоков.

class MString: public VString{ functions... };

Класс SString (Stack String) – строки на стеке. Те же строки, но с выделением памяти на стеке, по умолчанию выделяют 1кб, для строк большего размера используется MString. Огромная скорость, но большой лишний объем. Используются как временные переменные. Рождены в погоне за уменьшением операций выделения/освобождения памяти.

class SStringX : public VString{
	unsigned char sdata[stacksize];
	MString mdata;
functions...
};

Класс HLString (Line String) – хранит строки в цепочке из блоков памяти. По умолчанию выделяются блоки памяти по 4кб или под размер данных. Выделение памяти заранее, ускоряет работу при операциях добавления данных. Класс перегружает оператор + и позволяет писать код в виде: MString text(HLString() + “text” + a + 111); Так же класс выделяет память на стеке, для первого блока памяти, по умолчанию 4кб. Варианты использования: сложение множества строк, чисел в одну строку. Так же часто используется финт ушами для временного хранения строк. HLString ls; VString s = ls.addnfr(“Text”); — Добавляет нефрагментированную строку. Здесь плюсы в выделении/освобождении больших блоков памяти, что гораздо быстрее, чем при использовании MString для того же количества строк.

Класс TString (Temp String, Thread String) – Временные или потоковые строки. Идея, пришедшая лишь год назад, а ей стоило поторопиться лет на пять. В принципе, эта идея построена на HLString, нефрагментированных строках и __thread переменных. Каждый поток имеет свой экземпляр HLString переменной, из чего вытекают интересные перспективы. TString выделяет память в HLString, привязанном к потоку, что определенно быстрее, чем выделение памяти через malloc()/free(). Проблема этого класса в том, что он не освобождает память до уничтожения всех переменных TString. В определенные моменты программы все переменные должны уничтожаться, иначе программа постепенно использует всю доступную память с соответствующими последствиями.

Это пять типов строк, использующиеся мной при написании программ. VString — для работы со строками и фрагментами строк, MString — для хранения данных в классах, SString – для сбора строк из подстрок на стеке, HLString – для сбора больших строк на лету, TString – для временных строк.

Массивы

Первая программа, построенная с использованием статических массивов, показала, что с этим что-то надо делать. С тех пор идея писать массивы приходила чуть ли не каждый год.

MMatrix (My Matrix) – первые попытки работать с указателями, постоянные падения и бесконечный поиск ошибок. Состояли из родительского элемента с указателями на первый и последний элемент массива, и, собственно элементов массива, с указателями на предыдущий и следующий элементы, а так же данные. Размножались простым копированием класса и дописыванием нужных функций. Шаблоны это не наш метод. Так же оптимизировались под задачу: а давайте выкинем указатель на предыдущий элемент и сэкономим целых четыре байта.

LMatrix (Live Matrix) ~ 2007г. – вечно живая, как дедушка Ленин. Вы можете взглянуть на код, но даже мне не хочется в этом копаться и вспоминать, по каким принципам она работала.

UMatrix (Unlimited Matrix) ~2008г. – динамический массив из цепочки блоков памяти с хранением нескольких элементов массива в одном блоке. Умеет объединять все элементы в один блок памяти. Здесь была реализована идея выделять память сразу под блок элементов, сокращая работу с функциями памяти. Для определения свободных/занятых элементов используется битовая маска. Эти идеи будут использоваться в следующих вариантах массивов. Шаблоны все еще не наш метод, но и руками копировать достаточно сложно, поэтому был написан кодогенератор массивов.

IMatrix (Ideal Matrix) ~ 2009г. – вектор, весь массив в одном блоке памяти, при нехватке места перемещается на блок памяти большего размера. Впоследствии оказался малополезен и практически не используется.

OMatrix (Object Matrix) ~2010г. – общей идеей повторяет UMatrix, но если идея первой – цепочка объектов, то здесь идея разделенности. Здесь, в отличие от UMatrix, реализован список свободных объектов и проход по ним. Данный класс используется как аллокатор памяти, позволяя быстро получать/освобождать память под переменные.
Матрицы закончились, начались листы. А вместе с ними были заброшены кодогенераторы и приняты к использованию шаблоны.

MList (My List) ~ 2013г. – Класс автоматически оборачивает пользовательский класс в свой, добавляя указатели на предыдущий и следующий элементы.

IList (Ideal List) ~ 2014г. – IMatrix с заменой кодогенератора на шаблоны, все тот же вектор.

OList (Object List) ~ 2015г. – Аналогично, OMatrix переписанный под шаблоны.

UList (Unlimited List) ~ 2015г. – Замена UMatrix, шаблоны, красивый и логичный код.

AList (Auto List) 2015г. – Динамический массив с набором аллокаторов памяти, от стандартного new/free, UList, HLString, OList до возможности написать свой.

TrieList 2016г. – Реализация Trie дерева для быстрого поиска, на базе AList.

Как это не печально, массивы постепенно обрастают шаблонами, с одной стороны это упрощает разработку, с другой усложняет понимание и изменение под свои задачи.

Использование библиотеки

Простейший пример, создать пустой проект и добавить cpp файл с содержимым:

#define USEMSV_GENERALCPP
#define PROJECTNAME "projectname"
#define PROJECTVER PROJECTNAME ## _versions

#include "../../opensource/msvcore/msvcore.cpp"

Versions PROJECTVER[]={
	// new version to up
	"0.0.0.1", "10.10.2013 21:24"
};

int main(int args, char* arg[]){
	ILink link; mainp(args, arg, link);
	print(PROJECTNAME, " v.", PROJECTVER[0].ver," (", PROJECTVER[0].date, ").rn");
return 0;
}

Добавить в проект файлы "....opensourcemsvcoreVString.cpp" и "....opensourcemsvcoreMString.cpp" и писать код.

При добавлении нескольких cpp файлов следует использовать тот же код до #include … msvcore.cpp включительно, удалив строчку #define USEMSV_GENERALCPP. Так же можно подключать дополнительные расширения библиотеки, указывая их до подключения msvcore.cpp, например #define USEMSV_OLIST дает возможность использовать массивы OList. Список расширений можно увидеть в msvcore.cpp

По умолчанию в библиотеке:

  • Подключаются платформозависимые файлы, определяются различные дефайны.
  • Доступны классы строк: VString, MString, TString, SString(), HLString.
  • Доступны наборы функций: rts() – поиск по строке, PartLine() – аналог для работы с VString.
  • Класс ILink – для работы с путями и ссылками, класс MTime – для работы со временем, класс ConfLine – работа с конфигурационными файлами, класс Logs — для ведения логов, класс MSVEF – аналог регулярных выражений.
  • Класс UList – динамические массивы, класс MMatrix – первые динамические массивы, набор классов MTE универсальных классов для хранения различных типов данных.
  • Класс MRect для работы с регионами, класс MRGB для работы с цветами.
  • Класс mbool – для работы с битовыми масками, функции конвертации строк в числа, кодировки, различные форматы и наоборот.
  • Наборы библиотек под wince, flash.
  • Набор кроссплатформенных функций для работы с файлами: CreateFile(), ReadFile(), WriteFile(), GetFilePointer()… GetFileInfo(), CloseHandle(), MkDir(), LoadFile(), SaveFile().
  • Класс Readdir для получения списка файлов в папке, использует MSVEF в качестве фильтра.
  • Функция вывода в консоль print(VString string, VString string1 = VString(), …).
  • Функции для создания потоков StartThread().
  • Классы блокировки: TLock – критические секции, CLock – condition variables / условные переменные, UGLock – автоматическая блокировка/разблокировка TLock.
  • Классы работы с буферами: SendDataL – линейный буфер, SendDataRing – кольцевой буфер, RecvData – буфер для приема данных.
  • Классы работы с сетью: ConIp – для установки соединения и открытия порта. Функции: GetIP() – получение ip по имени домена, ifrecv() – проверка доступности данных в сокете, ifsend() – проверка разрешения на запись в сокет, gettip() и getcip() – возвращает ip и порт, сервера и клиента.

Дополнения, указываются перед включением msvcore.cpp:

#define USEMSV_ITOS
Класс ITos, предыдущая версия SString. Устарел.

#define USEMSV_INTERCEPT
Набор функций для анализа машинных кодов и перехвата функций.

#define USEMSV_CPPXCC
Класс XCC – парсер кода на C++.

#define USEMSV_INTERCEPT_MALLOC
Код для перехвата системных функций работы с памятью, используется для поиска утечек памяти.

#define USEMSV_XDATACONT
Классы для работы с форматами данных. Класс XDataCont разбирает JSON и XML, остальные классы устарели.

#define USEMSV_CJX
Класс CjxCont с реализацией бинарного формата данных, бинарный json.

#define USEMSV_MLIST, USEMSV_ILIST, USEMSV_OLIST, USEMSV_ALIST, USEMSV_TRIELIST
Подключает соответствующие классы динамических массивов: MList, IList, OList, AList, TrieList.

#define USEMSV_AMF
Классы amfe и amfd для конвертирования/разбора AMF формата.

#define USEMSV_PCRE
Подключает библиотеки с функциями регулярных выражений pcre2.

#define USEMSV_CONSOLE
Классы PipeLine и PipeLine2 для запуска других процессов.

#define USEMSV_MWND
Практически отдельная библиотека для работы с окнами, графикой, изображениями. Позволяет работать с графикой на всех поддерживающих библиотекой платформах. Содержит функции рисования примитивов, не зависящие от платформы. Про нее стоит рассказывать отдельно. Использует библиотеку CxImage для кодирования/декодирования форматов изображений.

#define USEMSV_CONSOLELINE
Набор классов для работой с консолью.

#define USEMSV_OPENSSL
Функции и классы для работы с openssl и шифрованием. Класс MySSL для установки/приема ssl соединений и работы с ними. Функции: RsaCreateKeys() – создает два Rsa ключа, функции RsaPublicEncode(), RsaPublicDecode(), RSAPrivateEncode(), RsaPrivateDecode() – шифруют/расшифровывают открытым/закрытым ключом, функции AesEncode() и AesDecode() кодируют/декодируют алгоритмом Aes. Так же содержит функции работы с сертификатами.

#define USEMSV_WEBSOCKETS
Набор функций для работы с WebSockets и класс WebSocketsCli – реализация WebSockets клиента.

#define USEMSV_MYSQL
Класс MySQLCon — обертка для работы с MySQL, использует mysql-connector-c.

#define USEMSV_MSL_FL
Подключает интерпретатор MSL – мой вариант языка программирования, больше всего напоминающий php. Msl Fast Line – 4 версия msl. Без генерации псевдокода, выполняет текстовые команды. Написан где-то за неделю в октябре 2013. Про него тоже стоит рассказывать отдельно.

#define USEMSV_MSL_FV
Msl Five – пятая версия языка. С генерацией байт кода и прочими плюшками. Разрабатывалась в сентябре 2015 года.

#define USEMSV_HTTP
Классы и функции для работы с http запросами. Классы GetHttp и GetHttp2. При подключении openssl функций поддерживает https запросы. Класс IHeader для работы с http заголовками. Класс MCookie для работы с куками.

#define USEMSV_CONFLINE
Класс ConfLineOptions для работы с конфигурационными файлами.

#define USEMSV_NESTAPI, USEMSV_NESTAPI2
Классы и функции для серверной части моего протокола NestApi.

#define USEMSV_STORMSERVER
Серверная платформа, о которой я бы хотел написать отдельный пост. Порядка десяти лет я пытался писать хорошие сервера и это наконец то удалось. Практически идеальное решение.

#define USEMSV_LIGHTSERVER
Класс LightServer — простой и легкий сервер, под который можно написать свой обработчик. Класс LightServerHttp – простой https сервер, возвращающий тестовую страничку.

#define USEMSV_TGBOTS
Классы TgBot и TgBots реализация ботов для телеграмма.

#define USEMSV_ANDROID
Функции и классы упрощающие работу при компиляции под андроид.

#define USEMSV_TRAFFIX
Классы для прослушивания трафика.

#define USEMSV_BUFECHO
Функции для изменения опций консоли.

Заключение

Надеюсь, вас заинтересует этот велосипед имени меня и мне будет, что рассказать вам еще. На мой взгляд, здесь есть довольно интересные вещи, начиная со строк, массивов, написаны обертки для openssl, может заинтересовать msl, или класс для создания ботов в телеграмме, и наконец, stormserver – платформу для создания различных серверов. Про последний я напишу отдельную статью, о разработке серверов, от простого эхо сервера до сложных http и прокси.

Как я уже говорил, библиотека довольно сильно смещена в сторону разрабатываемых программ. Как показывает практика, саму по себе ее сложно разрабатывать, не имея конкретных задач.

Я писал эту библиотеку для того, чтобы научиться хорошо программировать и понимать, что делает код от начала и до конца. И, надо сказать, прогресс есть, но еще работать и работать.

В дальнейших планах еще раз переписать с нуля, окончательно избавиться от старого кода, привести в порядок. И возможно, наконец-то написать документацию. Так же в планах дописать реализацию новых вариантов строк, как замену текущим, и, новые динамические массивы.

Код библиотеки и несколько проектов

Автор: MikelSV

Источник

Поделиться новостью

* - обязательные к заполнению поля