Приветствую, Хабравчане!

В прошлой статье получилось создать минимальную программу "Hello world!" размером 3,5 кб. Теперь будем рисовать нативными средствами Windows. Создадим окно, нарисуем пару примитивов.

Основной репозиторий RetroFan.

Поехали.

Видеокарта ISA Trident 9000B , память 512 кб
Видеокарта ISA Trident 9000B , память 512 кб

В те далекие года, когда видеокарта имела на борту 512 кб памяти. Подход к выводу графики был немножко другим. Рисование на экран происходило только с помощью библиотеки GDI в Windows и xlib в Linux, предоставляя свои функции рисования. Сейчас нам доступны API вроде OpenGL, Vulkan и DirectX, в которых центральный процессор принимает минимальное участие: все рисование, вывод примитивов, фигур лежит на плечах видеокарты. Но ранее всем этим занимался центральный процессор. Уже в поздние годы в некоторые видеокарты встраивался аппаратный блиттер, который реализовывал копирование байтов изображения из ОЗУ в память видеокарты без участия CPU.

Шаги рисования линии в GDI тех лет, назовем его нативной отрисовкой.

  1. Вызов функции рисования

  2. Блокировать память на видеокарте для чтения.

  3. ЦП в цикле заполняет память видеокарты.

  4. Происходит разблокировка памяти.

  5. Обновление экрана, теперь на экране видны изменения.

Ещё есть вариант ускорения отрисовки с помощью буфера экрана в озу. Каждый пиксель это 3 байта RGB. Написать свои функции рисования примитивов, вывода изображений. ЦП так же своими усилиями меняет значение байт, но не происходит блокировки и не требуется после каждого рисования примитива отправлять данные в видеокарту. И как только рисование завершено, отрисованы сотни спрайтов, примитивов, только после этого происходит копирование буфера на видеокарту, с последующим отображением на экране.

Но у такого способа есть свои недостатки, даже сейчас.

Если мы сейчас захотим таким же способом рисовать на экране при разрешении full hd. Нам понадобиться 1920 *1080 * 3 = 6 мб озу. И получается, что при анимации в 60 кадров потребуется переслать 60*6 = 360 мб памяти в секунду. Это прям очень плохо. Я уже молчу про то, что видеокарта простаивает и у нас еще даже не 4k разрешение.

А теперь представьте, что каждая программа выделяет для своего буфера 6 мегабайт. И если мы запустим 100 программ, только на буфер экрана для таких программ понадобится 600 мб.

Но данный метод отлично работал в 90-ые так как экраны были небольшими и буфер создавали к примеру под разрешение 800 на 600 при RGB цвете это всего лишь 1,5 мб. А если использовать ещё и палитру вместо RGB цветов, то экранный буфер будет занимать всего 500 кб. Таким методом рисовали графику в том числе и под MS-DOS.

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

В данной статье я буду описывать именно нативную версию рисования, мы не будем рисовать в буфер, будем использовать только нативные средства Windows.

Так я пишу не опираясь на внешние зависимости. То для создания окна нужно, объявить функции. Это сделано в данном файле.

Основная идея: Объявления функций, констант и макросов лежат в заголовочном файле Windows.h, каждый компилятор под Windows его имеет в наличии. В нашем случае, я вручную прописал требуемые функции и константы. Но для того, что бы оставить совметимость при сборке со стандартными заголовками и библиотеками такими как libc. Я создал каталог WinAPI и в данном каталоге храню уже копипаст и в итоге весь функционал экспортируется из файла Windows.h.

Если не делать так:

   include_directories("WinAPI")

Тогда не меняя код, будет использован стандартный Windows.h из поставки компилятора. И тем самым достигается совместимость. То же верно и для моих реализаций libc и libcpp.

Для версии под Windows я создал файл wWin.cpp для Linux xWin.cpp. В следующей статье проделаем тоже, самое но уже используя библиотеку xlib.

Примеры кода беру с данного сайта.

Полный код, программы.

Скрытый текст
#include <Windows.h>
#include <string.h>
#include <string>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) 
{
    switch (msg)
    {

    case WM_DESTROY:
    {
        PostQuitMessage(0);
    }
    break;

    }

    return DefWindowProc(hwnd, msg, wParam, lParam);
}

unsigned int random(unsigned int start_range, unsigned int end_range)
{
    static unsigned int rand = 0xACE1U; /* Any nonzero start state will work. */

    /*check for valid range.*/
    if (start_range == end_range) 
    {
        return start_range;
    }

    /*get the random in end-range.*/
    rand += 0x3AD;
    rand %= end_range;

    /*get the random in start-range.*/
    while (rand < start_range)
    {
        rand = rand + end_range - start_range;
    }

    return rand;
}

int main()
{
    MSG         message;
    HWND        handleWindow;
    HDC         handleDeviceContext;
    WNDCLASSA   windowClass;
    PAINTSTRUCT paintStruct;
    std::string title = "Hello Habr!";

    memset(&windowClass, 0, sizeof(WNDCLASSA));

    windowClass.style         = CS_HREDRAW | CS_VREDRAW;
    windowClass.cbClsExtra    = 0;
    windowClass.cbWndExtra    = 0;
    windowClass.lpszClassName = "Window";
    windowClass.hInstance     = GetModuleHandle(NULL);
    windowClass.hbrBackground = (HBRUSH)COLOR_APPWORKSPACE;
    windowClass.lpszMenuName  = NULL;
    windowClass.lpfnWndProc   = WndProc;

    RegisterClass(&windowClass);
    handleWindow = CreateWindow(windowClass.lpszClassName, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, 0, 0, 800, 600, NULL, NULL, windowClass.hInstance, NULL);

    handleDeviceContext = BeginPaint(handleWindow, &paintStruct);

    for (size_t i = 0; i < 100; i++)
    {
        MoveToEx(handleDeviceContext, 0, 0, NULL);
        LineTo(handleDeviceContext, random(0, 600), random(0, 800));
    }

    for (size_t i = 0; i < 100; i++)
    {
        RECT rect;
        rect.left   = random(0, 600);
        rect.top    = random(0, 800);
        rect.right  = random(0, 600);
        rect.bottom = random(0, 300);

        HBRUSH brush = CreateSolidBrush(RGB(random(0, 255), random(0, 255), random(0, 255)));

        FillRect(handleDeviceContext, &rect, brush);

        DeleteObject(brush);
    }

    EndPaint(handleWindow, &paintStruct);

    ShowWindow(handleWindow, SW_SHOW);
    UpdateWindow(handleWindow);

    while (GetMessage(&message, NULL, 0, 0))
    {
        TranslateMessage(&message);
        DispatchMessage(&message);
    }

    return 0;
}

104 строки не так уж и много. Опишу код для понимая происходящего.

#include <Windows.h> //В данном файле обявлены весь функционал Windows
#include <string.h>  // Нужен для экспорта функции meset
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) 
{
    switch (msg)
    {

    case WM_DESTROY:
    {
        PostQuitMessage(0);
    }
    break;

    }

    return DefWindowProc(hwnd, msg, wParam, lParam);
}

Функция WndProc нужна для обработки событий. При создании окна мы указываем, что все события слать в нашу функцию. Подробнее об этом чуть ниже. В msg прилетает событие, я обрабатываю только одно событие закрытия окна WM_DESTROY. Если пользователь нажал на крестик в правой части окна генерируется событие и я вызываю функцию PostQuitMessage, которая завершает исполняемый поток, то есть нашу программу с окном и выделенными ресурсами.

    MSG         message;             //Тип для сообщений Windows
    HWND        handleWindow;        //Идентификатор окна
    HDC         handleDeviceContext; //Идентификатор устройства для рисования в окне
    WNDCLASSA   windowClass;         //Структура содержит свойства окна
    PAINTSTRUCT paintStruct;         //Структура содержит контест рисования

Обнуляем переменную windowClass и заполняем ее корректными данными. Если не обнулить и не заполнить структуру В ней будут некорректные данные, и окно просто не получится создать.

  memset(&windowClass, 0, sizeof(WNDCLASSA));

    windowClass.style         = CS_HREDRAW | CS_VREDRAW;
    windowClass.cbClsExtra    = 0;
    windowClass.cbWndExtra    = 0;
    windowClass.lpszClassName = "Window";
    windowClass.hInstance     = GetModuleHandle(NULL);
    windowClass.hbrBackground = (HBRUSH)COLOR_APPWORKSPACE;
    windowClass.lpszMenuName  = NULL;
    windowClass.lpfnWndProc   = WndProc;

Опишу основное.

Это стиль окна, в нашем случае мы задаем, что бы окно можно было растягивать по высоте и ширине. В Windows.h довольно много констант для стиля. Отличный сайт по Windows API из которого я черпаю информацию.

windowClass.style         = CS_HREDRAW | CS_VREDRAW;

А вот где мы указываем указатель на нашу функцию обработки событий.

windowClass.lpfnWndProc   = WndProc;

После заполнения, создаем окно функцией CreateWindow. Передаем заполненную структуру windowClass, позиция по x и y и размер 800 на 600. Пока проверки корректности излишни, обработку ошибок добавлю при создании мини фреймворка. А сейчас будет только отвлекать.

CreateWindow(windowClass.lpszClassName, title.c_str(), 
             WS_OVERLAPPEDWINDOW | WS_VISIBLE, 
             0, 0, 800, 600, NULL, NULL, windowClass.hInstance, NULL);

Вызываем функции. показать окно и обновить окно.

  ShowWindow(handleWindow, SW_SHOW);
  UpdateWindow(handleWindow);

Это главный цикл обработки сообщений окна. В цикле получаем сообщение и транслируем его в наше окно. Что бы уже окно вызвала функцию WndProc в которой происходит уже непосредственное реагирование на события.

    while (GetMessage(&message, NULL, 0, 0))
    {
        TranslateMessage(&message);
        DispatchMessage(&message);
    }

Не так уж и сложно и кода получилось не так много.

Теперь код рисования. Будем выводить случайным образом линии и закрашенные прямоугольники. Но пока функционала в минимальной libc нет, поэтому в этом нам поможет stackoverflow, так сказать random курильщика:)

Я только изменил название на random. И он коряво рандомизирует, но всё же работает. Для визуализации данных, нам его вполне хватит.

unsigned int random(unsigned int start_range, unsigned int end_range)
{
    static unsigned int rand = 0xACE1U; /* Any nonzero start state will work. */

    /*check for valid range.*/
    if (start_range == end_range) 
    {
        return start_range;
    }

    /*get the random in end-range.*/
    rand += 0x3AD;
    rand %= end_range;

    /*get the random in start-range.*/
    while (rand < start_range)
    {
        rand = rand + end_range - start_range;
    }

    return rand;
}

Все рисование средствами GDI библиотеки происходит между вызовами BeginPaint и EndPaint.

    handleDeviceContext = BeginPaint(handleWindow, &paintStruct);

    for (size_t i = 0; i < 100; i++)
    {
        MoveToEx(handleDeviceContext, 0, 0, NULL);
        LineTo(handleDeviceContext, random(0, 600), random(0, 800));
    }

    for (size_t i = 0; i < 100; i++)
    {
        RECT rect;
        rect.left   = random(0, 600);
        rect.top    = random(0, 800);
        rect.right  = random(0, 600);
        rect.bottom = random(0, 300);

        HBRUSH brush = CreateSolidBrush(RGB(random(0, 255), random(0, 255), random(0, 255)));

        FillRect(handleDeviceContext, &rect, brush);

        DeleteObject(brush);
    }

    EndPaint(handleWindow, &paintStruct);

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

    for (size_t i = 0; i < 100; i++)
    {
        MoveToEx(handleDeviceContext, 0, 0, NULL);
        LineTo(handleDeviceContext, random(0, 600), random(0, 800));
    }

Теперь рисуем разноцветные закрашенные прямоугольники.

    for (size_t i = 0; i < 100; i++)
    {
        RECT rect;
        rect.left   = random(0, 600);
        rect.top    = random(0, 800);
        rect.right  = random(0, 600);
        rect.bottom = random(0, 300);

        HBRUSH brush = CreateSolidBrush(RGB(random(0, 255), random(0, 255), random(0, 255)));

        FillRect(handleDeviceContext, &rect, brush);

        DeleteObject(brush);
    }

Для начала мы создаем перо и устанавливаем цвет RGB. И передаем его в функцию рисования FillRect.

В итоге получим такое.

Добавил нормальный rand в libc. Реализация с просторов интернета

static size_t next = 1;

int rand(void)
{
    next = next * 1103515245 + 12345;
    return (size_t)(next / 65536) % 32768;
}

void srand(size_t seed)
{
    next = seed;
}

И если поменять реализацию, то получаем такой вариант.

int random(unsigned int min, unsigned int max)
{
    return rand() % ((max + min) + min);
}
    for (size_t i = 0; i < 500; i++)
    {
        RECT rect;
        rect.left   = random(0, 800);
        rect.top    = random(0, 600);
        rect.right  = random(25, 50);
        rect.bottom = random(25, 50);

        HBRUSH brush = CreateSolidBrush(RGB(random(0, 255), random(0, 255), random(0, 255)));

        FillRect(handleDeviceContext, &rect, brush);

        DeleteObject(brush);
    }

Windows API умеет рисовать разные примитивы, эллипсы, полигоны, окружности, текст. Но сейчас это не важно. Я об этом расскажу в следующих статьях. Когда начну написание мини графического фреймворка, абстрагирующий от windows и linux.

Под Windows бинарник подрос на 1 кб. Теперь для 32-ух битной сборки он равен 4,5 для 64-ёх битной целых 5,0 кб.

Вывод изображений, оставим до реализации мини фреймворка. Так как потребуется не только вывод RGB изображений, но и паллитровых. Для этого нужно будет написать свой загрузчик 8-ми битных bmp изображений, с палитрой. Пока код довольно примитивен, поэкспериментирую, какой компилятор тех лет, сможет нативно собрать исходник под Windows 3.1.

Комментарии (31)


  1. vvviperrr
    13.01.2025 14:18

    на своей первой работе делал кросплатформенный гуй для кассовой проги на основе sdl и opengl. до сих пор в шоке.


    1. JordanCpp Автор
      13.01.2025 14:18

      Это здорово. Я все как то намеревался разобраться в данной теме, но дальше кнопки никогда не уходил. Что бы именно с нуля рисовать все контролы и писать их логику.


      1. Jijiki
        13.01.2025 14:18

        там не сложно, вьюшку сделать с кнопками под проект, например вью для базы данных по назначению, или еще чонить на СДЛ не так сложно сделать, или лаунчер, да любую вьюшку, opengl если, то скорее всего вы так отозвались потомучто есть 2 вида матриц 3д матрица и 2д - ортогональная, вы задаёте просто координаты квадрата в кратце и рисуете вашу область нужную 2д, а 2д в 3д просто убираете z в шейдере, вообщем просто матешу чуть чуть потдянуть, там не сложно на самом деле, тоесть это двух матричное рисование в окне

        Скрытый текст

        это вводная в партиклы, в отображение текста в мире и прочее, ну кароче плоская плоскость в 3д

        вот вы делаете сейчас функционал под окна я хотел еще вчера вам напомнить что в рисовании окон тоже матеша есть


        1. Arenoros
          13.01.2025 14:18

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


      1. MasterMentor
        13.01.2025 14:18

        При случае сделайте в оупенсор создание нативного GUI (мобильники+винда) из Json.

        Заслуживающих внимание GUI библиотек - туча. Есть библиотеки:

        • дающие кросплатформенный канвас - рисуй что хошь:

        This library provides the most minimal and highly opinionated way to display a cross-platform 2D canvas. If you remember Borland BGI or drawing things in QBASIC or INT 10h- you know what I mean. One C99 header of ~300LOC, easy to understand and extend.

        https://github.com/zserge/fenster

        • дающие zerodependency (почти) stb Ebedded GUI - встраивай:

        µGUI is a free and open source graphic library for embedded systems. It is platform-independent and can be easily ported to almost any microcontroller system. As long as the display is capable of showing graphics

        https://github.com/achimdoebler/UGUI

        GLFW is an Open Source, multi-platform library for OpenGL, OpenGL ES and Vulkan application development. It provides a simple, platform-independent API for creating windows, contexts and surfaces, reading input, handling events, etc. GLFW natively supports Windows, macOS and Linux and other Unix-like systems. On Linux both X11 and Wayland are supported.

        https://github.com/glfw/glfw/

        • дающие кросплатформенные native GUI

        tighter OS integration (especially for document-based programs), to allow programs to fully feel native, rather than merely look and act native

        https://github.com/andlabs/libui

        A library for creating native cross-platform GUI apps.
        https://github.com/yue/yue

        Но, чтоб строили кросплатформенный stb GUI из Json, я не встречал.

        GUI Json можно генерить здесь:

        GUI->Json (Editor)


        1. JordanCpp Автор
          13.01.2025 14:18

          Уже добавил в закладки. Надо будет посмотреть. Спасибо.


          1. NosferatuDima2
            13.01.2025 14:18

            а https://www.wxwidgets.org/ не думали посмотреть?


            1. vdudouyt
              13.01.2025 14:18

              Прежде чем кому-либо советовать wxWidgets рекомендую обратить внимание на то, что главная страница данного проекта содержит политические заявления. Было бы не очень здорово, если бы кому-нибудь из пользователей софтины ничего не подозревающего JordanCpp отформатировало ЖД за неправильную юрисдикцию.


        1. Jijiki
          13.01.2025 14:18

          GLFW только пк, SDL даёт порты в большее количество платформ

          да жесон это просто способ настройки коммуникации с бинарником, суть в том что сдл на 2д своём даёт простоту кода для создания нужного 2д функционала, а на опенжль просто тоже самое, но с шейдерами, но есть прям гуй либы тут да вы правы

          я крайнюю игру делал под пк/андроид на сдл3 и там у меня был свой бинарник ассета(менеджер - загрузчик), врятли на данном этапе сделаю жесон, жесон же можно получить из SQLite - пока я пользовался максимум скулайтом, а жесоном не пользовался

          это надо gltf читать разбираться, а тут ассимп он поудобнее и нет имгуя как вы видите всё родное то что в игре будет (ну скорее не в игре, а демосцене - игра очень амбициозно)


          1. MasterMentor
            13.01.2025 14:18

            жесон же можно получить из SQLite

            SQLite - излишен. Я немного математик.

            В качестве бонуса. Полноценная реализация SQL на Pure JS в 482 LOC (не моя).

            https://github.com/myfoundation/SQLike

            То есть, таблицы в +- пару млн записей (что для GUI, и не только, - крыша), можно гонять в Json-е. :)


        1. Jijiki
          13.01.2025 14:18

          https://github.com/moleium/imjson вот посмотрите вроде чтото такое, есть еще gltf-viewer и сам imgui


  1. eugeneyp
    13.01.2025 14:18

    Очень интересно было в DOS работать с экраном char (*screen)[80][2]=0xB000;
    И для вывода на экран можно просто заполнять массив char.

    И правила именования с типом переменной вначале lpszClassNameя не встречал с тех времен.

    Также тогда в Windows активно использовались диалоги, и хранение форм для окна в ресурсах. Что-то типа VCL форм от Delphi.


    1. AlexeyK77
      13.01.2025 14:18

      охх, тем диалоговым формам до VCL было как до луны. Там эти диалоги делались через редактор ресурса, так-же как и иконки, строки и прочее. Во всяком случае в четвертом борланде (не билдер естественно). Это был мрак, закат и рассвет солнца вручную.


  1. MasterMentor
    13.01.2025 14:18

    Заплюсовал за codestyle, олдскульно.

    Теперь по сути: на 2025-й год называть рисование через MoveToEx, LineTo, FillRect со всеми прелестями вроде HWND, WNDCLASSA, PAINTSTRUCT "ненормальным программированием" - можно, "демосценой" - нельзя.

    Ненормальность в том, в каждом процессоре уже сидит тензорная алгебра (о шейдерах - я молчу), а мы всё юзаем BRUSH.

    Что по демосцене - так это вообще от другом.

    PS Из истории

    "Демосцен" было две:

    1. стерильная русская демосцена (https://ru.wikipedia.org/wiki/Русскоязычная_демосцена) , где пара-тройка мажор-кексов из МГУ, МФТИ и иже с ними с хорошей физ и мат. подготовкой создало пару графических нетленок вроде Zoom 3 by AND https://www.youtube.com/watch?v=n92Is47luCI .
      В годы расцвета ~1993-2003 эта "демосцена" умещалась в ~30 человек. Затем - всё.
      (нельзя писания на ассемблере Zilog Z80 называть "демосценой" в 2025 году).

      Краткая история русской демосцены
    2. "демосцена" - как часть международного андеграунда, где демки - были лишь intro (вступлениями) и greetings (приветами) в среде андеграундных групп.

    Так что, статья:
    - на олдскул - тянет;
    - на демосцену "по-русски" *** (ассемблер Zilog Z80+) - да;
    - на демосцену иную - увольте. Впрочем, к этому не привыкать. ;)

    ***

    игра слов, аллюзия на Пинбол по-русски https://small-games.info/?go=game&c=21&i=2617 (кто знает - тот знает)


    1. Jijiki
      13.01.2025 14:18

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


      1. MasterMentor
        13.01.2025 14:18

        Да можно. Выделяй в памяти буфер и рисуй на нём чем угодно - хоть тензорами (хотя они не для этого), хоть - попиксельно, а затем через memcpy перекидывай битовую картинку в Виндовое окно.

        Кроссплатформ до повсеместной аппаратной поддержки Graphics Languages (вроде DX, OpenGL, GLSL итд) так и писался

        *

        Кто такой Андре Ламот и почему вам следует прочесть его книги?

        https://habr.com/ru/articles/693660/


        1. Jijiki
          13.01.2025 14:18

          я сейчас вот что делаю https://github.com/richKirl/TestDSAWorld


          1. MasterMentor
            13.01.2025 14:18

            Милые обезьянки на GL-е.

            Что до меня, так считаю, что писать графику на Си-подобных языках - мовитон. Правильней делать специальный язык+vm-ку и кодировать графику на нём. Поэтому у меня любовь с первой ложки к SideFX Houdini (хоть язык там и кривоват).

            есть и такие проекты


  1. Shaman_RSHU
    13.01.2025 14:18

    Использовал для графики DOS/4GW на Watcom C.

    Много экономило времени и сил.


  1. JordanCpp Автор
    13.01.2025 14:18

    Ненормальность в том, в каждом процессоре уже сидит тензорная алгебра, а мы всё юзаем BRUSH.

    Это только начало. Этот режим нужен для совместимости со старыми windows. Потом будет режим рисования в буфер, потом opengl и directx. Поверх 2d абстракции с единып api. В этом цель.


    1. MasterMentor
      13.01.2025 14:18

      Идея понятна сразу - уйти от тухлятины вроде многотомных stdlib-ов с их несовместимостями в разных версиях винды. Сделать lightweight + zero dependency в бинарниках.

      Но я не вижу продолжения. Нет аудитории , понимающей и принимающей задумку.

      PS Картинка IDE VC6 в Пилим движок Arcanum. Урок 03 - "резанула глаз" ;) . Это самая продуманная и лучшая IDE (+ VisualAssist от Tomato Software) за время существовования кодирования. ;)


      1. JordanCpp Автор
        13.01.2025 14:18

        Но я не вижу продолжения. Нет аудитории , понимающей и принимающей задумку.

        В первую очередь это интересно мне. Это мое желание рассказать и показать. И я понимаю и принимаю, что это скорей всего интересно паре десятков человек. Я не создаю платный контент, просто пилю статьи в удовольствие. Поэтому мне не нужны подписчики в ТГ, на Ютубе, что бы впаривать им рекламу. Меня это полностью устраивает.

        И у меня нет таймингом и сроков. Захотел написал одно, потом другое.

        Идея понятна сразу - уйти от тухлятины вроде многотомных stdlib-ов с их несовместимостями в разных версиях винды. Сделать lightweight + zero dependency в бинарниках.

        Да именно так.