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

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

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 ").\r\n");
return 0;
}

Додати в проект файли "..\..\opensource\msvcore\VString.cpp" і "..\..\opensource\msvcore\MString.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 проксі.

Як я вже говорив, бібліотека досить сильно зміщена в бік розроблюваних програм. Як показує практика, саму по собі її складно розробляти, не маючи конкретних завдань.

Я писав цю бібліотеку для того, щоб навчитися програмувати і розуміти, що робить код від початку і до кінця. І, треба сказати, прогрес є, але ще працювати і працювати.

У подальших планах ще раз переписати з нуля, остаточно позбутися від старого коду, привести в порядок. І можливо, нарешті написати документацію. Так само в планах дописати реалізацію нових варіантів рядків, як заміну поточним, і, нові динамічні масиви.

Код бібліотеки і кілька проектів
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.