Приветствую хабравчане!
Буду рад советам и рекомендациям в комментах или на гитхабе в 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?
Примерно потому:
Вопросы требующие решения:
Реализация Fast Pimpl'a для поддержки старых компиляторов.
Как реализовать юникод, что бы и старые системы работали.
Как более модульно разбить проект.
Имеет ли смысл выпилить исключения и есть ли альтернатива.
Как лучше организовать тестирование связанное с графикой.
Примеры:
Комментарии (67)
Pastoral
22.11.2022 02:54+2«В будущем планирую обеспечить поддержку Dos, Android, iOS, macOS.»
Поддержку Здравствуймира сначала для всего этого реализуйте, а потом планируйте. А то как бы не оказалось что уже написали так, что подобное невозможно. Тысячу раз.
«Пока отсутствует документация, но она планируется после стабилизации графического API.»
Ничто не помогает увидеть что делаешь не то лучше, чем написание (черновика) документации.
«Но работа идёт.»
О, Вам платят за торможение прогресса и ретроградную эволюцию человечества? Если нет, не называйте это работой. Хинт: в SDL ещё есть чем заняться, можно даже улучшением.
JordanCpp Автор
22.11.2022 03:06+4О, Вам платят за торможение прогресса и ретроградную эволюцию человечества? Если нет, не называйте это работой.
Это устоявшееся выражение. Работа идёт, колесо крутится, вода течёт. :)
Поддержку Здравствуймира сначала для всего этого реализуйте, а потом планируйте. А то как бы не оказалось что уже написали так, что подобное невозможно. Тысячу раз.
IvanPetrof
22.11.2022 04:22Использование сырых указателей. В стандарте С++ 98 нет ни shared не unique
Можно найти какой-нить старенький boost под этот стандарт. Там бывают умные указатели.
JordanCpp Автор
22.11.2022 08:10Была мысль для старых версий компиляторов портировать из бородатых gcc STL. На данный момент библиотека успешно компилируется Visual C++ 6.0 из коробки. Скорость компиляции огонь, но и уровень оптимизации хромает. К примеру вывод картинки на CPU различается в три раза на одном железе но с современным компилятором.
codecity
22.11.2022 06:47+2Понравилась ясность мысли в приведенных примерах.
планирую обеспечить поддержку Dos, Android, iOS, macOS
Нафига Dos, лучше WebAssembly добавьте. Т.е. чтобы в самой главной ОС, которой сейчас однозначно является Web-браузер - можно было использовать. Зачем вчерашний день, когда для дня сегодняшнего нет решений?
И вопрос по Android/iOS. Работали ли вы с ними? Там все это можно реализовать технически? К примеру Android - системные библиотеки, разрешенные для использования обычным пользователем - на Java-машине, верно?
JordanCpp Автор
22.11.2022 07:51+1И вопрос по Android/iOS. Работали ли вы с ними? Там все это можно реализовать технически? К примеру Android - системные библиотеки, разрешенные для использования обычным пользователем - на Java-машине, верно?
Нет не работал. Но всегда есть куда посмотреть как сделано и сделать так же. Библиотеки SDL2 и SFML. Я как то ставил Android Studio, для обогрева комнаты:)
Нафига Dos, лучше WebAssembly добавьте. Т.е. чтобы в самой главной ОС, которой сейчас однозначно является Web-браузер - можно было использовать. Зачем вчерашний день, когда для дня сегодняшнего нет решений?
Со временем руки дойдут и до этого варианта. Dos привлекает своим ретро статусом. Конечно я понимаю, что оно нужно где то 1.5 человекам в мире:)
Zara6502
22.11.2022 07:33+1как 8-битник до мозга и костей приветствую вариант для DOS хотя бы для i386/87.
JordanCpp Автор
22.11.2022 07:52Для портирования буду юзать Open Watcom. Он позволяет писать 32 битный код для Dos.
Рассказывает много интересных моментов.
Zara6502
22.11.2022 09:34Да, я про Watcom и хотел спросить следом, а вы уже и ответ написали.
Было бы интересно разобраться с аппаратными функциями CGA/EGA/VGA и VESA. Это я в целом, о своих планах.
На ютубе есть дядя https://www.youtube.com@The8BitGuyу него своя игра портирована на кучу ПК, включая i8088 DOS в разных графических режимах. Но он не рассказывал на чем пишет.
JordanCpp Автор
22.11.2022 09:49Было бы интересно разобраться с аппаратными функциями CGA/EGA/VGA и VESA. Это я в целом, о своих планах.
Отличный ресурс с документацией и ссылками на статьи и сайты авторов, программирующих под Dos. Присутствуют статьи о CGA/EGA но в основном примеры для VGA.
https://github.com/balintkissdev/awesome-dos
На ютубе есть интересный плейлис по программированию в Dos. VGA режим Mode X.
JordanCpp Автор
22.11.2022 09:51На ютубе есть дядя https://www.youtube.com@The8BitGuyу него своя игра портирована на кучу ПК, включая i8088 DOS в разных графических режимах. Но он не рассказывал на чем пишет.
У него и видео есть о разработке данной игры. И он начинал писать игру на Commodore 64.
Zara6502
22.11.2022 11:12Есть, там рассказываются аппаратные особенности, но нет информации и движке например..
Спасибо за наводки, подписался чтобы сохранить данные.
JordanCpp Автор
22.11.2022 11:57Я как то нагуглил исходники досовских игр, на С,С++,Pascal. Возможно на исходниках ру. Для изучения полезно.
kovserg
22.11.2022 16:40Я бы лучше Symantec C++ использовал вместо медленного WATCOM-а.
Symantec C++ 7.5 DOS/DOSx32/Pharlap32, win16/win32pharo
22.11.2022 18:51JordanCpp Автор
22.11.2022 19:34Я писал об Open Watcom. Данный компилятор умеет компилировать С++ код на Windows (и Linux?) в бинарники для Dos'а. Поддерживает 32 битный режим и встраивает в исполняемый файл расширитель Dos4gw. Исполняемые файлы запускаю под dosbox.
kovserg
22.11.2022 21:09У меня он на болванке есть с кранами. Там нет Pharlap и link386, а на www.digitalmars.com ссылки уже давно ведут на не существующий сайт.
JordanCpp Автор
22.11.2022 08:21Есть видео о портировании современной 2D игры под Dos. Видео на английском. Я смотрел через переводчик яндекса. На слух я английский не понимаю.
Промахнулся видео в предыдущем ответе.
Zara6502
22.11.2022 12:18посмотрю, спасибо.
с исходниками для меня всё плохо, я чужой код совсем не понимаю, так как у меня в голове всё строится от идеи, а её реализация в голове у меня никогда не задерживается, когда читаю чужое просто не понимаю это нагромождение кода, особенно когда используют твики или хитрые приёмы. (недавно тут же читал статью про то как формировались уровни в Pifall, тот кто в этом разобрался для меня уровня - Бог)
lymes
22.11.2022 07:51А чем не устраивает SDL? Впрочем, это скорее просто дидактический проект, чтобы самому разобраться, верно?
JordanCpp Автор
22.11.2022 08:04+1В большей мере да. Я специально скачивал старые исходники SDL версии 1.0 Посмотрев исходники вкралась мысль, а почему собственно не повторить. Да и закрыть внутренний гештальт заодно. Мне с детства нравилось разбираться в каких то штуках и узнавать как оно в унутрях работает.
JordanCpp Автор
22.11.2022 08:05И конечно хотелось бы сделать законченный проект. Пока да, проект выглядит как студенческая поделка, это мягко сказано. :)
Tujh
22.11.2022 11:12+2Отсутствие зависимости от внешних dll, все зависимости включены физически в проект.
А значит любой "проэкт" на этой библиотэке для Linux ни когда не попадёт ни в один репозиторий, единственный путь распространния flatpack и компания подобных.
domix32
22.11.2022 12:18+1То есть если берём дос, то самое длинное у нас становится 32 бита, верно?
Заход про производительность это конечно хорошо, но почему первый же пример не на ErrorCodes, а на исключениях?
Ну и если про раст понятно, то непонятно почему не Си?JordanCpp Автор
22.11.2022 12:31То есть если берём дос, то самое длинное у нас становится 32 бита, верно?
Да.
Заход про производительность это конечно хорошо, но почему первый же пример не на ErrorCodes, а на исключениях?
Удобство. Исключения невозможно игнорировать, как коды ошибок.
Ну и если про раст понятно, то непонятно почему не Си?
Личное решение, для меня си уж слишком дубовый.
domix32
22.11.2022 13:02+2Удобство
Тогда можно смело выпиливать клейм про производительность. Думается мне, что ECS тоже не появится в пользу классов с классами.
JordanCpp Автор
22.11.2022 13:16Про производительность с исключениями спорно. Соглашусь, что могут быть проблемы на старых компиляторах. Скорее всего я перейду на коды ошибок, почитаю о приемлемых альтернативах. Я реализую базовый функционал 2d графики + переносимое взаимодействие с событиями ОС и т. д Тот кто будет юзать библиотеку для создания игры или движка, может встраивать ECS. Но в базовой библиотеке я использовать данный паттерн не вижу смысла.
khajiit
22.11.2022 17:20То есть если берём дос, то самое длинное у нас становится 32 бита, верно?
Там наверняка придется уходить в unreal mode и ваять свои драйвера для графики, хотя бы для vesa. Так что самое длинное может быть абсолютно любое, если автор осилит.
JordanCpp Автор
22.11.2022 19:37Пока нацелен на VGA. После порта под Linux займусь портом под Dos. Сначала поставлю заглушки и просто добьюсь компиляции и запуска exe под Dos. После уже буду по туториалам допиливать до работоспособности.
khajiit
22.11.2022 19:46Успехов в этом нелегком деле )
В любом случае, вы получите опыт. А это в любительских проектах — самое ценное.
Kotofay
22.11.2022 16:53Рекомендую ознакомиться с библиотекой Anti-Grain Geometry, для 2D субпиксельной графики.
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?
KanuTaH
22.11.2022 22:17+3Простите, но нафига городить такое чудо, которое мало того что UB на UB, и UB погоняет, так еще и бессмысленно? PIMPL нужен для того, чтобы размеры объектов на стеке никогда не менялись, а здесь, стоит не дай бог чему-то серьезно поменяться, и уже нужно увеличивать ваш волшебный
sizeOfImpl
, место на стеке стало требоваться другое, код, который был собран с предыдущей версией библиотеки, начал крашиться.Kotofay
22.11.2022 22:38Чтобы не выделять через кучу.
Ну и второе -- чтобы вдоволь нажонглироваться указателями на память.
KanuTaH
22.11.2022 22:41+1Чтобы не выделять через кучу.
Ну так оно же не работает так, как должно. Проблем не оберешься.
Kotofay
22.11.2022 22:53Так проблем там одна -- запрещено копирование по значению.
Т.е. передача объекта из/в ф-ию только через указатель.
Непонятно только зачем тут С++, менее громоздко наверно было бы структурами с указателями на ф-ии обойтись.
JordanCpp Автор
22.11.2022 23:15Хочется пользоваться неймспэйсами, ООП, конструкторами и деструкторами, исключениями, более строгой проверкой типов и т. д
KanuTaH
22.11.2022 23:24Так проблем там одна -- запрещено копирование по значению.
Если вы про "стандартный" PIMPL через кучу, то да, увы. Жизнь могли бы скрасить конструкторы копирования и
operator=()
, если бы не вопросы к стабильности ABI.
JordanCpp Автор
22.11.2022 22:40Есть ли вариант применить Pimpl и избежать динамического выделения через new? Возможно следует думать в сторону аллокатора для инициализации классов?
Сохранить преимущества Pimpl, но избежать его минусов.
KanuTaH
22.11.2022 23:22Сомневаюсь, что можно полноценно использовать PIMPL со всеми его гарантиями, и совсем избежать динамического выделения памяти. Можно конечно выделять на стеке "заведомо побольше", но во-первых этого "побольше" тоже может не хватить, а во-вторых при копированиях объекта будет копироваться лишнее.
JordanCpp Автор
23.11.2022 11:16На первое время думаю сделать через new, а потом уже выбрать подходящий вариант.
Kotofay
22.11.2022 22:22+1Выделите больше чем надо с учётом выравнивания
::align = alignof(
UnixSocketImpl
) / 8; // зависит от компилятора::sizeOfImpl = sizeof(
UnixSocketImpl
) + align;
После разместите *SocketImpl в этом массиве с учётом выравнивания, т.е. со смещением от начала на размер выравнивания в байтахnew(&socket[align]) UnixSocketImpl;
JordanCpp Автор
22.11.2022 22:41Но вроде как только для С++ 11. Общий механизм вы прояснили, спасибо.
mpa4b
22.11.2022 22:17В гитхабе почему-то написано "I'm using the C++11 standard. To support old and new platforms."
А если например дум захочется запилить под эту показывалку, где C API?
JordanCpp Автор
22.11.2022 22:37Поправлю редми, это копипаста из другого проекта.
Обертка на С, будет в будущем.
Tujh
23.11.2022 00:26Обертка на С, будет в будущем.
По опыту скажу, что С++ обёртку над С API сделать легко, а вот С обёртку над С++ API (если она не была заложена изначально) уже вряд ли получится, ну или итоговый Франкенштейн будет реализовывать только малую часть.
Kotofay
23.11.2022 12:08Да ну. Даже проще чем кажется.
Ведь Сишные экстерны это всего лишь указания компоновщику и компилятору С++ не делать ОО расширение имён функций.
Прологи и эпилоги остаются тему же самыми. Конструкторы и деструкторы работают.
Tujh
23.11.2022 12:25Если это "проще чем кажется" покажите на примере как использовать std::string, в С-коде.
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 р/час.
Tujh
24.11.2022 17:23extern "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); ... }
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!"
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 не нанял бы.
kovserg
24.11.2022 19:13А как в таком апи определять, если функция вернула строку, надо-ли её освобождать или не очень. Особенно если оно разрастётся.
JordanCpp Автор
23.11.2022 19:18Пока оставлю сборку из исходников. В проекте использую cmake, проблем подключить или собрать на поддерживаемых системах нет.
JordanCpp Автор
23.11.2022 23:10Добавил поддержку компилятора Open Watcom v2. Должен работать и с более старыми версиями, но я не проверял. Будет основа для порта Dos.
skyblade
Я совершенно не эксперт в этой области, но, мне кажется, автор в процессе разработки этого проекта и решения уже поставленных и будущих вопросов таки сможет рассказать нам всем потом в виде большой итоговой статьи о том, почему всё так тормозит вместо релиза этого проекта :)
JordanCpp Автор
В этом году уже два раза порывался писать статью о тормозах. Но будет само повтор.