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

Буду рад советам и рекомендациям в комментах или на гитхабе в issue. Собственно пост в основном пилился для получения рекомендаций по проекту. Проект ещё в зачаточном состоянии. Документации пока нет, но есть тесты. Куцый функционал, мало поддерживаемых систем. Но работа идёт. Ну, что поехали!

В прошлой статье я негодовал о современном программном обеспечении.

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

Перед началом благословяза хочу передать привет разработчику с GameDev.ru

Zefick привет от школяра!:)

Вашему вниманию представляю библиотеку Little DirectMedia Layer в аббревиатуре LDL. Все совпадения с текущими библиотеками случайны:)

Для большей информации тема на OldGames.ru

Основные фичи библиотеки:

  • Производительность. Да лукавые языки на i9 процессорах, рассказывают сказки, что производительность не важна. Время программиста дорого и вообще мы геи пишем на Electron. Я всё же придерживаюсь другого мировоззрения, производительность не менее важна, наряду с удобством в использовании ПО.

    Процитирую Стэна Трухильо из его книги “Графика для Windows средствами DirectDraw”

Быстродействие никогда не выйдет из моды. Чтобы обеспечить максимальное быстродействие программы, ее необходимо оптимизировать. Об этом знают все программисты, занимающиеся разработкой аркадных игр. Их игры должны работать быстро, иначе они будут плохо продаваться. С каждым годом игроки желают иметь все более высокое быстродействие. Каждая новая игра-бестселлер устанавливает новые стандарты и поднимает планку еще выше — несомненно, в будущем эта тенденция только усилится.

  • Кроссплатформенность – обеспечение работы как на старых так и на новых системах. Это и все версии Windows 95, 98, Me. А так же поддержка старых систем Linux начиная с 2000-го года. В будущем планирую обеспечить поддержку Dos, Android, iOS, macOS.

  • Поддержка всевозможных графических API OpenGL, Glide, Vulkan и DirectX начиная с 5.0 по 12.0 Для максимального покрытия видеокарт.

  • Удобный и по возможности высокоуровневый API. С++ дает возможность не только эффективно работать устраивать тех порно с байтами, но и скрывать низкоуровневые вещи под высокоуровневыми абстракциями.

  • Открытый исходный код по лицензии Boost Software License.

На данный момент готов следующий функционал.

  • Поддерживаются все версии Windows начиная с Windows 95.

  • Реализован функционал по выводу и рисованию 2D графики. (Вывод картинок, рисование графических примитивов)

  • По умолчанию для GPU рендера реализована поддержка OpenGL 1.2

  • Пока отсутствует документация, но она планируется после стабилизации графического API.

Некоторые особенности в реализации.

  • Использование сырых указателей. В стандарте С++ 98 нет ни shared не unique. Радует, что при использовании библиотеки не требуется делать new. Байтоложество с uint8_t на месте:)

  • Выбор реализации на уровне header файлов.

  • Поддержка только статического связывания.

  • Поддержка старых и новых систем.

  • Отсутствие зависимости от внешних dll, все зависимости включены физически в проект.

  • Минимальное использование наследования и виртуальных методов.

  • Часть команды, часть корабля:)

Пример

Данный пример загружает картинку и выводит её на экран.

#include <iostream>
#include <LDL/Graphics/Gpu/GpuWindow.hpp>
#include <LDL/Graphics/Gpu/GpuImage.hpp>
#include <LDL/Graphics/Gpu/GpuRender.hpp>
#include <LDL/Core/RuntimeError.hpp>
#include <LDL/Loaders/ImageLoader.hpp>
#include <LDL/Time/FpsCounter.hpp>
#include <LDL/Core/IntegerToString.hpp>
#include <LDL/Allocators/FixedLinear.hpp>

int main()
{
	try
	{
      // Создание окна
		LDL::Graphics::GpuWindow window(LDL::Graphics::Point2u(0, 0), LDL::Graphics::Point2u(800, 600), "Window!");
      // Создание рендера
		LDL::Graphics::GpuRender render(&window);

		LDL::Events::Event report;
      // Создаём линейный фиксированный аллокатор
      // и выделяем 4 мб памяти
		LDL::Allocators::FixedLinear allocator(LDL::Allocators::Allocator::Mb * 4);
	  // Инициализируем загрузчик картинок аллокатором
        LDL::Loaders::ImageLoader loader(&allocator);
      // Загружаем картинку, теперь вся память для работы
      // загрузчика берётся из аллокатора
		loader.Load("trehmachtovyiy-korabl-kartina-maslom-60x50_512x.jpg");
      // Создаём текстуру из загруженных данных
        LDL::Graphics::GpuImage image(loader.Size(), loader.BytesPerPixel(), loader.Pixels());
      // Создаём счётчик FPS
		LDL::Time::FpsCounter fpsCounter;
     //Создаём класс конвертера из числа в строку
        LDL::Core::IntegerToString convert;
     // Главный цикл приложения
		while (window.GetEvent(report))
		{
			fpsCounter.Start();

			render.Begin();

			render.Color(LDL::Graphics::Color(0, 162, 232));
			render.Clear();

			if (report.Type == LDL::Events::IsQuit)
			{
				window.StopEvent();
			}

			render.Draw(&image, window.Pos(), window.Size());

			render.End();

			if (fpsCounter.Calc())
			{
				if (convert.Convert(fpsCounter.Fps()))
				{
					window.Title(convert.Result());
				}

				fpsCounter.Clear();
			}
		}
	}
	catch (const LDL::Core::RuntimeError& error)
	{
		std::cout << error.what() << '\n';
	}

	return 0;
}

Не так и страшен С++ 98 года выпуска!

Архитектура

Для каждой реализуемой операционной системы есть свой каталог для исходников и заголовочных файлов. Каждой системе требуется реализовать свой класс MainWindow инкапсулирующий очередь событий ОС, а так же переводящий события в общий класс LDL::Events::Event.

Для поддержки 2D графики, каждая система включает в себя общий класс BaseRender и свою реализацию основанную на графическом API, к примеру ниже показан пример OpenGL.

#ifndef LDL_Graphics_GL1Render_hpp
#define LDL_Graphics_GL1Render_hpp

#include <LDL/Graphics/Base/BaseRender.hpp>
#include <LDL/Graphics/GL1/GL1Window.hpp>
#include <LDL/Graphics/GL1/GL1Image.hpp>
#include <LDL/Graphics/GL1/GL1Screen.hpp>

namespace LDL
{
	namespace Graphics
	{
		class GL1Render
		{
		public:
			GL1Render(LDL::Graphics::GL1Window* window);
			void Begin();
			void End();
			const LDL::Graphics::Point2u& Size();
			const LDL::Graphics::Color& Color();
			void Clear();
			void Color(const LDL::Graphics::Color& color);
			void Pixel(const LDL::Graphics::Point2u& pos);
			void Fill(const LDL::Graphics::Point2u& pos, const LDL::Graphics::Point2u& size);
			void Line(const LDL::Graphics::Point2u& pos1, const LDL::Graphics::Point2u& pos2);
			void Draw(LDL::Graphics::GL1Image* image, const LDL::Graphics::Point2u& pos, const LDL::Graphics::Point2u& size);
			void Draw(LDL::Graphics::GL1Image* image, const LDL::Graphics::Point2u& pos);
			void Draw(LDL::Graphics::CpuImage* image, const LDL::Graphics::Point2u& pos, const LDL::Graphics::Point2u& size);
			void Draw(LDL::Graphics::CpuImage* image, const LDL::Graphics::Point2u& pos);
		private:
			LDL::Graphics::GL1Window* _Window;
			LDL::Graphics::BaseRender _BaseRender;
			LDL::Graphics::GL1Screen _Screen;
		};
	}
}

#endif  

Вся библиотека устроена похожим образом.

Я так и не смог разработать единый API для GPU и CPU версии библиотеки. Поэтому принял решение разделить реализацию и поддерживать две версии.

При компиляции через объявленный define, выбирается указанная реализация.

#ifndef LDL_Graphics_GpuRender_hpp
#define LDL_Graphics_GpuRender_hpp

#if defined(LDL_GPU_SUPPORT_OPENGL1)
#include <LDL/Graphics/GL1/GL1Render.hpp>
namespace LDL
{
	namespace Graphics
	{
		typedef LDL::Graphics::GL1Render GpuRender;
	}
}
#elif defined(LDL_GPU_SUPPORT_DIRECTX9)
#include <LDL/Graphics/DX9/DX9Render.hpp>
namespace LDL
{
	namespace Graphics
	{
		typedef LDL::Graphics::DX9Render GpuRender;
	}
}
#elif defined(LDL_GPU_SUPPORT_DIRECTX5)
#include <LDL/Graphics/DX5/DX5Render.hpp>
namespace LDL
{
	namespace Graphics
	{
		typedef LDL::Graphics::DX5Render GpuRender;
	}
}
#else
#error Not implementation: Graphics::GpuRender
#endif

#endif 

Планы на будущее.

  • Портирование на Linux (xlib следом wayland).

  • Дописать тесты на готовый функционал.

  • Начать готовить документацию.

Ответы на часто задаваемые вопросы:

  • В конце 2022 года пилить свой костыль с поддержкой Windows 95? Ты серьезно?

    Таки да!

  • У вас есть справка из психдиспансера?

    Да, но это не точно:)

  • Почему не на Rust?

    Примерно потому:

Вопросы требующие решения:

  1. Реализация Fast Pimpl'a для поддержки старых компиляторов.

  2. Как реализовать юникод, что бы и старые системы работали.

  3. Как более модульно разбить проект.

  4. Имеет ли смысл выпилить исключения и есть ли альтернатива.

  5. Как лучше организовать тестирование связанное с графикой.

Примеры:

По клику рисуем картинку.

Работа с картинками из ОЗУ.

Вывод картинки с указанный цветом для альфы.

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


  1. skyblade
    22.11.2022 02:37
    +2

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


    1. JordanCpp Автор
      22.11.2022 03:13

      В этом году уже два раза порывался писать статью о тормозах. Но будет само повтор.


  1. Pastoral
    22.11.2022 02:54
    +2

    «В будущем планирую обеспечить поддержку Dos, Android, iOS, macOS.»

    Поддержку Здравствуймира сначала для всего этого реализуйте, а потом планируйте. А то как бы не оказалось что уже написали так, что подобное невозможно. Тысячу раз.

    «Пока отсутствует документация, но она планируется после стабилизации графического API.»

    Ничто не помогает увидеть что делаешь не то лучше, чем написание (черновика) документации.

    «Но работа идёт.»

    О, Вам платят за торможение прогресса и ретроградную эволюцию человечества? Если нет, не называйте это работой. Хинт: в SDL ещё есть чем заняться, можно даже улучшением.


    1. JordanCpp Автор
      22.11.2022 03:06
      +4

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

      Это устоявшееся выражение. Работа идёт, колесо крутится, вода течёт. :)

      Поддержку Здравствуймира сначала для всего этого реализуйте, а потом планируйте. А то как бы не оказалось что уже написали так, что подобное невозможно. Тысячу раз.


  1. IvanPetrof
    22.11.2022 04:22

    Использование сырых указателей. В стандарте С++ 98 нет ни shared не unique

    Можно найти какой-нить старенький boost под этот стандарт. Там бывают умные указатели.


    1. JordanCpp Автор
      22.11.2022 08:10

      Была мысль для старых версий компиляторов портировать из бородатых gcc STL. На данный момент библиотека успешно компилируется Visual C++ 6.0 из коробки. Скорость компиляции огонь, но и уровень оптимизации хромает. К примеру вывод картинки на CPU различается в три раза на одном железе но с современным компилятором.


  1. codecity
    22.11.2022 06:47
    +2

    Понравилась ясность мысли в приведенных примерах.

    планирую обеспечить поддержку Dos, Android, iOS, macOS

    Нафига Dos, лучше WebAssembly добавьте. Т.е. чтобы в самой главной ОС, которой сейчас однозначно является Web-браузер - можно было использовать. Зачем вчерашний день, когда для дня сегодняшнего нет решений?

    И вопрос по Android/iOS. Работали ли вы с ними? Там все это можно реализовать технически? К примеру Android - системные библиотеки, разрешенные для использования обычным пользователем - на Java-машине, верно?


    1. JordanCpp Автор
      22.11.2022 07:51
      +1

      И вопрос по Android/iOS. Работали ли вы с ними? Там все это можно реализовать технически? К примеру Android - системные библиотеки, разрешенные для использования обычным пользователем - на Java-машине, верно?

      Нет не работал. Но всегда есть куда посмотреть как сделано и сделать так же. Библиотеки SDL2 и SFML. Я как то ставил Android Studio, для обогрева комнаты:)

      Нафига Dos, лучше WebAssembly добавьте. Т.е. чтобы в самой главной ОС, которой сейчас однозначно является Web-браузер - можно было использовать. Зачем вчерашний день, когда для дня сегодняшнего нет решений?

      Со временем руки дойдут и до этого варианта. Dos привлекает своим ретро статусом. Конечно я понимаю, что оно нужно где то 1.5 человекам в мире:)


  1. Zara6502
    22.11.2022 07:33
    +1

    как 8-битник до мозга и костей приветствую вариант для DOS хотя бы для i386/87.


    1. JordanCpp Автор
      22.11.2022 07:52

      Для портирования буду юзать Open Watcom. Он позволяет писать 32 битный код для Dos.

      Рассказывает много интересных моментов.


      1. Zara6502
        22.11.2022 09:34

        Да, я про Watcom и хотел спросить следом, а вы уже и ответ написали.

        Было бы интересно разобраться с аппаратными функциями CGA/EGA/VGA и VESA. Это я в целом, о своих планах.

        На ютубе есть дядя https://www.youtube.com@The8BitGuyу него своя игра портирована на кучу ПК, включая i8088 DOS в разных графических режимах. Но он не рассказывал на чем пишет.


        1. JordanCpp Автор
          22.11.2022 09:49

          Было бы интересно разобраться с аппаратными функциями CGA/EGA/VGA и VESA. Это я в целом, о своих планах.

          Отличный ресурс с документацией и ссылками на статьи и сайты авторов, программирующих под Dos. Присутствуют статьи о CGA/EGA но в основном примеры для VGA.

          https://github.com/balintkissdev/awesome-dos

          На ютубе есть интересный плейлис по программированию в Dos. VGA режим Mode X.


        1. JordanCpp Автор
          22.11.2022 09:51

          На ютубе есть дядя https://www.youtube.com@The8BitGuyу него своя игра портирована на кучу ПК, включая i8088 DOS в разных графических режимах. Но он не рассказывал на чем пишет.

          У него и видео есть о разработке данной игры. И он начинал писать игру на Commodore 64.


          1. Zara6502
            22.11.2022 11:12

            Есть, там рассказываются аппаратные особенности, но нет информации и движке например..

            Спасибо за наводки, подписался чтобы сохранить данные.


            1. JordanCpp Автор
              22.11.2022 11:57

              Я как то нагуглил исходники досовских игр, на С,С++,Pascal. Возможно на исходниках ру. Для изучения полезно.


      1. kovserg
        22.11.2022 16:40

        Я бы лучше Symantec C++ использовал вместо медленного WATCOM-а.
        Symantec C++ 7.5 DOS/DOSx32/Pharlap32, win16/win32


        1. JordanCpp Автор
          22.11.2022 16:48

          Возьму на заметку. Спасибо.


        1. pharo
          22.11.2022 18:51

          1. JordanCpp Автор
            22.11.2022 19:34

            Я писал об Open Watcom. Данный компилятор умеет компилировать С++ код на Windows (и Linux?) в бинарники для Dos'а. Поддерживает 32 битный режим и встраивает в исполняемый файл расширитель Dos4gw. Исполняемые файлы запускаю под dosbox.


          1. kovserg
            22.11.2022 21:09

            У меня он на болванке есть с кранами. Там нет Pharlap и link386, а на www.digitalmars.com ссылки уже давно ведут на не существующий сайт.


    1. JordanCpp Автор
      22.11.2022 08:21

      Есть видео о портировании современной 2D игры под Dos. Видео на английском. Я смотрел через переводчик яндекса. На слух я английский не понимаю.

      Промахнулся видео в предыдущем ответе.


      1. Zara6502
        22.11.2022 12:18

        посмотрю, спасибо.

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


  1. lymes
    22.11.2022 07:51

    А чем не устраивает SDL? Впрочем, это скорее просто дидактический проект, чтобы самому разобраться, верно?


    1. JordanCpp Автор
      22.11.2022 08:04
      +1

      В большей мере да. Я специально скачивал старые исходники SDL версии 1.0 Посмотрев исходники вкралась мысль, а почему собственно не повторить. Да и закрыть внутренний гештальт заодно. Мне с детства нравилось разбираться в каких то штуках и узнавать как оно в унутрях работает.


    1. JordanCpp Автор
      22.11.2022 08:05

      И конечно хотелось бы сделать законченный проект. Пока да, проект выглядит как студенческая поделка, это мягко сказано. :)


  1. cdriper
    22.11.2022 10:16
    +1

    если что, SDL умеется переключать CPU/GPU render в рантайме, без всякой условной компиляции


    1. JordanCpp Автор
      22.11.2022 11:51

      Я такое не осилил. Для CPU рендера свой API.


  1. Tujh
    22.11.2022 11:12
    +2

    Отсутствие зависимости от внешних dll, все зависимости включены физически в проект.

    А значит любой "проэкт" на этой библиотэке для Linux ни когда не попадёт ни в один репозиторий, единственный путь распространния flatpack и компания подобных.


    1. JordanCpp Автор
      22.11.2022 11:50

      В планах есть переход на impl. Но хочется сделать не теряя производительность на выделение памяти. Вот этой статьей вдохновлялся. Не хочется делать new на ровном месте. Будут отдельные dll/so библиотеки.


      1. Tujh
        22.11.2022 13:01
        +1

        мы о разных вещах видимо говорим.


        1. JordanCpp Автор
          22.11.2022 16:53

          Да я понял о чем вы. Подумаю.


  1. domix32
    22.11.2022 12:18
    +1

    То есть если берём дос, то самое длинное у нас становится 32 бита, верно?

    Заход про производительность это конечно хорошо, но почему первый же пример не на ErrorCodes, а на исключениях?
    Ну и если про раст понятно, то непонятно почему не Си?


    1. JordanCpp Автор
      22.11.2022 12:31

      То есть если берём дос, то самое длинное у нас становится 32 бита, верно?

      Да.

      Заход про производительность это конечно хорошо, но почему первый же пример не на ErrorCodes, а на исключениях?

      Удобство. Исключения невозможно игнорировать, как коды ошибок.

      Ну и если про раст понятно, то непонятно почему не Си?

      Личное решение, для меня си уж слишком дубовый.


      1. domix32
        22.11.2022 13:02
        +2

        Удобство

        Тогда можно смело выпиливать клейм про производительность. Думается мне, что ECS тоже не появится в пользу классов с классами.


        1. JordanCpp Автор
          22.11.2022 13:16

          Про производительность с исключениями спорно. Соглашусь, что могут быть проблемы на старых компиляторах. Скорее всего я перейду на коды ошибок, почитаю о приемлемых альтернативах. Я реализую базовый функционал 2d графики + переносимое взаимодействие с событиями ОС и т. д Тот кто будет юзать библиотеку для создания игры или движка, может встраивать ECS. Но в базовой библиотеке я использовать данный паттерн не вижу смысла.


          1. JordanCpp Автор
            22.11.2022 13:26

            Пакетный режим для отрисовки графики я планирую ввести.


    1. khajiit
      22.11.2022 17:20

      То есть если берём дос, то самое длинное у нас становится 32 бита, верно?

      Там наверняка придется уходить в unreal mode и ваять свои драйвера для графики, хотя бы для vesa. Так что самое длинное может быть абсолютно любое, если автор осилит.


      1. JordanCpp Автор
        22.11.2022 19:37

        Пока нацелен на VGA. После порта под Linux займусь портом под Dos. Сначала поставлю заглушки и просто добьюсь компиляции и запуска exe под Dos. После уже буду по туториалам допиливать до работоспособности.


        1. khajiit
          22.11.2022 19:46

          Успехов в этом нелегком деле )
          В любом случае, вы получите опыт. А это в любительских проектах — самое ценное.


  1. Kotofay
    22.11.2022 16:53

    Рекомендую ознакомиться с библиотекой Anti-Grain Geometry, для 2D субпиксельной графики.


  1. JordanCpp Автор
    22.11.2022 20:33

    Реализуя идиому pimpl, я буду хранить класс в статической памяти как в следующем примере

    https://habr.com/ru/post/111602/

    //GeneralSocket.h
    Class GeneralSocket
    {
    public:
     GeneralSocket();
     void connect();
    private:
     static const size_t sizeOfImpl = 42;/* or whatever space needed*/
     char socket [sizeOfImpl];
    }
    
    //GeneralSocket.cxx
    #include “UnixSocketImpl.h”
    
    GeneralSocket::GeneralSocket() : {
     assert(sizeOfImpl >= sizeof (UnixSocketImpl));
     new(&socket[0]) UnixSocketImpl;
    }
    
    GeneralSocket::~GeneralSocket() {
     (reinterpret_cast<UnixSocketImpl *> (&socket[0]))->~UnixSocketImpl();
    }
    
    GeneralSocket::connect() {
     socket->connectImpl();
    }

    Далее автор пишет

    Проблемы с выравниванием памяти. Данный способ не гарантирует что память будет выравнена должным образом для всех членов UnixSocketImpl. Решение, которое не гарантирует полную переносимость, но все же работает в большинстве случаев – использование union:

    Как сделать универсально, что бы работало на всех системах и могло быть реализовано на стандартам е С++ 98?


    1. KanuTaH
      22.11.2022 22:17
      +3

      Простите, но нафига городить такое чудо, которое мало того что UB на UB, и UB погоняет, так еще и бессмысленно? PIMPL нужен для того, чтобы размеры объектов на стеке никогда не менялись, а здесь, стоит не дай бог чему-то серьезно поменяться, и уже нужно увеличивать ваш волшебный sizeOfImpl, место на стеке стало требоваться другое, код, который был собран с предыдущей версией библиотеки, начал крашиться.


      1. Kotofay
        22.11.2022 22:38

        Чтобы не выделять через кучу.

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


        1. KanuTaH
          22.11.2022 22:41
          +1

          Чтобы не выделять через кучу.

          Ну так оно же не работает так, как должно. Проблем не оберешься.


          1. Kotofay
            22.11.2022 22:53

            Так проблем там одна -- запрещено копирование по значению.

            Т.е. передача объекта из/в ф-ию только через указатель.

            Непонятно только зачем тут С++, менее громоздко наверно было бы структурами с указателями на ф-ии обойтись.


            1. JordanCpp Автор
              22.11.2022 23:15

              Хочется пользоваться неймспэйсами, ООП, конструкторами и деструкторами, исключениями, более строгой проверкой типов и т. д


            1. KanuTaH
              22.11.2022 23:24

              Так проблем там одна -- запрещено копирование по значению.

              Если вы про "стандартный" PIMPL через кучу, то да, увы. Жизнь могли бы скрасить конструкторы копирования и operator=(), если бы не вопросы к стабильности ABI.


      1. JordanCpp Автор
        22.11.2022 22:40

        Есть ли вариант применить Pimpl и избежать динамического выделения через new? Возможно следует думать в сторону аллокатора для инициализации классов?

        Сохранить преимущества Pimpl, но избежать его минусов.


        1. KanuTaH
          22.11.2022 23:22

          Сомневаюсь, что можно полноценно использовать PIMPL со всеми его гарантиями, и совсем избежать динамического выделения памяти. Можно конечно выделять на стеке "заведомо побольше", но во-первых этого "побольше" тоже может не хватить, а во-вторых при копированиях объекта будет копироваться лишнее.


          1. JordanCpp Автор
            23.11.2022 11:16

            На первое время думаю сделать через new, а потом уже выбрать подходящий вариант.


    1. Kotofay
      22.11.2022 22:22
      +1

      Выделите больше чем надо с учётом выравнивания

      ::align = alignof( UnixSocketImpl ) / 8; // зависит от компилятора

      ::sizeOfImpl = sizeof( UnixSocketImpl ) + align;
      После разместите *SocketImpl в этом массиве с учётом выравнивания, т.е. со смещением от начала на размер выравнивания в байтах

      new(&socket[align]) UnixSocketImpl;


      1. JordanCpp Автор
        22.11.2022 22:41

        Но вроде как только для С++ 11. Общий механизм вы прояснили, спасибо.


  1. mpa4b
    22.11.2022 22:17

    1. В гитхабе почему-то написано "I'm using the C++11 standard. To support old and new platforms."

    2. А если например дум захочется запилить под эту показывалку, где C API?


    1. JordanCpp Автор
      22.11.2022 22:37

      1. Поправлю редми, это копипаста из другого проекта.

      2. Обертка на С, будет в будущем.


      1. Tujh
        23.11.2022 00:26

        Обертка на С, будет в будущем.

        По опыту скажу, что С++ обёртку над С API сделать легко, а вот С обёртку над С++ API (если она не была заложена изначально) уже вряд ли получится, ну или итоговый Франкенштейн будет реализовывать только малую часть.


        1. JordanCpp Автор
          23.11.2022 11:17

          Для меня пока это не приоритет. Но галочку для себя поставил.


        1. Kotofay
          23.11.2022 12:08

          Да ну. Даже проще чем кажется.

          Ведь Сишные экстерны это всего лишь указания компоновщику и компилятору С++ не делать ОО расширение имён функций.

          Прологи и эпилоги остаются тему же самыми. Конструкторы и деструкторы работают.


          1. Tujh
            23.11.2022 12:25

            Если это "проще чем кажется" покажите на примере как использовать std::string, в С-коде.


            1. Kotofay
              23.11.2022 13:53
              -1

              Оберните все нужные вам операции над std::string в вызовы С с сырыми указателями на массивы байт.

              Чем строки отличаются от любых других классов, которые можно завернуть во всё что угодно и потом передавать через void* в С? Правильно, ничем.

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

              #include <stdio.h>
              #include <string>
              
              extern "C" void * new_string ( char * s ){
                  return new std::string( s );
              }
              
              extern "C" const char * string_c( void * std_str ){
                  std::string * s = reinterpret_cast< std::string* >( std_str );
                  return s->data();
              }
              
              extern "C" void * add_string( void * std_str, char * add_s ){
                  std::string * s = reinterpret_cast< std::string* >( std_str );
                  *s += std::string( add_s );
                  return s;    
              };
              
              int main(int, char **)
              {
                  void * cppstr = new_string( "Hello" );
                  printf( "%s\n", string_c( cppstr ) );
                  cppstr = add_string( cppstr, " from C!" );
                  printf( "%s\n", string_c( cppstr ) );
                  return 0;
              }
              -------------------------
              Hello
              Hello from C!
               

              Консультации -- 1000 р/час.


              1. Tujh
                24.11.2022 17:23

                extern "C" void * new_string ( char * s )
                { return new std::string( s ); }

                и как с эти работать? Вы хоть раз API разрабатывали?

                В примере со строками, я имел в виду что-то подобное (просто для примера):

                // C++ API
                // __attribute__((visibility("default"))) for Linux
                __declspec(dllexport) class system_info {
                public:
                  inline std::string version() {
                    return m_version;
                  }
                ...
                };
                
                // C API
                int get_version(char *string_buffer, size_t buffer_length) {
                  ...
                  strncpy(string_buffer, sys_info.version().c_str(), MAX_BUFFER_LEN);
                  ...
                }


                1. Kotofay
                  24.11.2022 18:37

                  и как с эти работать? Вы хоть раз API разрабатывали?

                  Просто до безобразия. А вот вы похоже что нет.

                  И не надо умничать.

                  Для особо непонятливых повторю -- оборачивание С интерфейсом С++ классов не представляет никакой проблемы.

                  #include <stdio.h>
                  #include <string>
                  #include <vector>
                  
                  // type control
                  // no strict           // strict
                  typedef void* string;  // struct String { std::string s };
                  typedef void* strings; // struct Strings { std::vector< std::string > v; };
                  
                  extern "C" string new_string ( const char * s ){
                      return new std::string( s );
                  }
                  
                  extern "C" void delete_string ( string std_str ){
                      std::string * s = reinterpret_cast< std::string* >( std_str );
                      delete s;
                  }
                  
                  extern "C" void delete_strings ( string * std_strs ){
                      std::string ** s = reinterpret_cast< std::string** >( std_strs );
                      delete[] s;
                  }
                  
                  extern "C" void delete_string_vector ( strings std_strs ){
                      std::vector<std::string> * v = reinterpret_cast< std::vector<std::string> * >( std_strs );
                      delete v;
                  }
                  
                  extern "C" const char * string_c( const string std_str ){
                      const std::string * s = reinterpret_cast< const std::string * >( std_str );
                      return s->data();
                  }
                  
                  extern "C" string add_string( string std_str, const char * add_s ){
                      std::string * s = reinterpret_cast< std::string* >( std_str );
                      *s += std::string( add_s );
                      return s;    
                  };
                  
                  // так делать обёртку нежелательно -- лишнее копирование памяти
                  extern "C" string * split(const string std_str, const char * delim, int * n)
                  {
                      if( n == nullptr || delim == nullptr )
                         return nullptr;
                      
                      const std::string * s = reinterpret_cast< const std::string * >( std_str );
                      const char * c_str = s->c_str();
                      std::vector<std::string> splitted;
                      char* tp = strdup( c_str );      
                      char* chunk = strtok( tp, delim );       
                     
                      while( chunk != nullptr ) {
                          splitted.push_back( chunk );
                          chunk = strtok( nullptr, delim );        
                      }
                     
                      free( tp ); 
                      
                      *n = splitted.size();
                      string * ret = new string[ *n ];
                      int i = 0;
                      
                      for( std::vector<std::string>::iterator it = splitted.begin(), end = splitted.end(); it != end ; ++it )
                          ret[ i++ ] = new std::string( *it );
                      
                      return ret;
                  }
                  
                  extern "C" strings split2(const string std_str, const char * delim)
                  {
                      if( delim == nullptr )
                         return nullptr;
                      
                      const std::string * s = reinterpret_cast< const std::string * >( std_str );
                      const char * c_str = s->c_str();
                      std::vector<std::string> * ret = new std::vector<std::string>;
                      char* tp = strdup( c_str );      
                      char* chunk = strtok( tp, delim );       
                      
                      while( chunk != nullptr ) {
                          ret->push_back( chunk );
                          chunk = strtok( nullptr, delim );        
                      }
                      
                      free( tp ); 
                  
                      return ret;
                  }
                  
                  extern "C" int strings_size( const strings s ){
                      const std::vector<std::string> * v = reinterpret_cast< const std::vector<std::string> * >( s );
                      return v->size();
                  }
                  
                  extern "C" string strings_get( strings s, int idx ){
                      std::vector<std::string> * v = reinterpret_cast< std::vector<std::string> * >( s );
                      return &( v->at( idx ) );
                  }
                  
                  int main(int, char **)
                  {
                      string cppstr = new_string( "Hello" );
                      printf( "%s\n", string_c( cppstr ) );
                      
                      cppstr = add_string( cppstr, " from C!" );
                      printf( "%s\n", string_c( cppstr ) );
                      
                      int n = 0, i = 0;
                      string * sp = split( cppstr, " ", &n );
                      printf( "std::string \"%s\" splitted to %d chunks :\n", string_c( cppstr ), n );
                      for( i = 0; i < n; i++ )
                         printf( " -- \"%s\"\n", string_c( sp[ i ] ) );
                      delete_strings( sp );
                      
                      strings sp2 = split2( cppstr, " " );
                      n = strings_size( sp2 );
                      if( n >= 2 )
                         add_string( strings_get( sp2, 1 ), " wroom" );
                      printf( "std::string \"%s\" splitted(2) to %d chunks :\n", string_c( cppstr ), n );
                      for( i = 0; i < n; i++ )
                         printf( " -- \"%s\"\n", string_c( strings_get( sp2, i ) ) );
                      delete_string_vector( sp2 );
                      
                      delete_string( cppstr );
                      
                      return 0;
                  }
                  -----------------
                  Hello
                  Hello from C!
                  std::string "Hello from C!" splitted to 3 chunks :
                   -- "Hello"
                   -- "from"
                   -- "C!"
                  std::string "Hello from C!" splitted(2) to 3 chunks :
                   -- "Hello"
                   -- "from wroom"
                   -- "C!"
                   


                  1. Tujh
                    24.11.2022 19:12

                    Просто до безобразия. А вот вы похоже что нет.

                    Ещё раз, в ващем примере нет API в принципе, си код не имеет доступа к данным строки, то есть вы передаёте некий хендл, которые хранит данные, но собственно сишный код работать с этими данными не может - это не API, это именно обёртка.

                    Из вашего же примера - вызвать printf() без дополнительных телодвижений невозможно:

                    string cppstr = new_string( "Hello" );
                    printf( "%s\n", string_c( cppstr ) );

                    Помимо этого, весь этот код, как минимум, совершенно не безопасен (при таком "качестве" не удивительны постоянные статьи на Хабре про "небезопасность" С и С++ и "безопасность" Rust, видимо пишут их такие же как вы)

                    std::string ** s = ...

                    Если у вас на ревью пропускают что-то подобно - то я бы не хотел работать в вашей компании.

                    char* chunk = strtok( tp, delim );

                    И вся многопоточность идёт лесом, так как strtok использует внутренние глобальные переменные.

                    char* tp = strdup( c_str );
                    while( chunk != nullptr ) {
                    splitted.push_back( chunk );
                    chunk = strtok( nullptr, delim );
                    }
                    free( tp );

                    Потенциальная утечка памяти, разваленный стек в многопотоном приложении, ну и операции с сырыми указателями (там, где это севершенно не требуется) в современном С++

                    Можете общение не продолжать, я вас даже на позицию Junior не нанял бы.


                    1. JordanCpp Автор
                      24.11.2022 20:23

                      Что то, мне расхотелось делать сишный API:)


                      1. Tujh
                        24.11.2022 23:18

                        „Си (язык программирования) позволяет легко выстрелить себе в ногу; с C++ это сделать сложнее, но, когда вы это делаете, вы отстреливаете себе ногу целиком.“ —  Бьярне Строуструп


                  1. kovserg
                    24.11.2022 19:13

                    А как в таком апи определять, если функция вернула строку, надо-ли её освобождать или не очень. Особенно если оно разрастётся.


  1. JordanCpp Автор
    23.11.2022 19:18

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


  1. JordanCpp Автор
    23.11.2022 23:10

    Добавил поддержку компилятора Open Watcom v2. Должен работать и с более старыми версиями, но я не проверял. Будет основа для порта Dos.