Приветствую, Хабравчане!
Сидел я значит за свои столом, читал Хабр, листал хаб старое железо и ничего не предвещало беды. В последнее время появилось много обзоров на старые ноутбуки, что даже захотелось найти и прикупить. Смотрю, где то дос стоит, windows 95.
Умилялся скриншотами, вчитывался в характеристики девайсов, pentium 1 c 100 mhz и 16 мб озу. Смешно конечно по нашим меркам. Естественно в голову приходили мысли, железо конечно абсурдно по производительности, но если применить современные фичи С++ то вполне можно и под них писать да хоть игры. Правда нет таких современных компиляторов поддерживающих такую дремучую древность.
Ещё раз окинул взглядом репозиторий по SimpleOS где получилось перевести хост версию на модули С++ 20. И так они мне понравились, что когда я загрузил сою библиотеку LDL, даже пропало желание программировать, так как проект написан на С++ 98 с серьезными ограничениями, нет namespace, свои минимальные шаблоны и т.д Вздохнул, и сказал про себя было бы здорово если бы получилось переписать к примеру тот же LDL на С++ 23 и сохранить совместимость со старым железом.
И тут сработал триггер в голове, подождите, что то подобное я где то видел. Начал искать гуглить и нашел.
Если в кратце
Михал понял, что объектные файлы, созданные AOT-компилятором CoreRT в 2020 году, можно собрать компоновщиком из Visual C++ 2.0 образца 1994 года. В результате получается машинный код, скомпонованный с интерфейсами Win32s, работающими в 16-разрядной Windows 3.11. Магия. Респект Михалу.
А, что так можно было?
И тут Бендер всё понял :-)
У меня был план и я его придерживался.
Дано, большое желание программировать под ретро железо используя современные фичи С++ 23. Windows 11 и Visual Studio Community 2026 (на днях обновился)
Что бы план сработал нужно.
-
Собирать код компилятором msvc 2026 c указанием что бы компилировал только в 32 битный x86, ещё отключить исключения и стандартную библиотеку, этим компилятор будет порождать только чистые и совместимые obj файлы.
Спасибо Microsoft что за больше чем 25 лет не стали кардинально менять формат obj. Ну хоть, что то они не сломали:)
Берем линкер от Visual C++ 6.0 и скармливаем эти obj файлы.
Получаем exe совместимый с Windows 95.
Теперь практика.
Создаем main.cpp
import std;
import Win32;
int main()
{
std::string title = "Hello Habr!";
std::string message = "Crazy programming!";
MessageBoxA(GetActiveWindow(), title.c_str(), message.c_str(), 0x00000000L);
ExitProcess(0);
return 0;
}
Просто выводим самым простым способом, текст.
Создаем модуль Win32.ixx
export module Win32;
extern "C"
{
__declspec(dllimport) void* __stdcall GetProcessHeap();
__declspec(dllimport) void* __stdcall HeapAlloc(void* hHeap, unsigned int dwFlags, unsigned int dwBytes);
__declspec(dllimport) int __stdcall HeapFree(void* hHeap, unsigned int dwFlags, void* lpMem);
__declspec(dllimport) void* __stdcall GetActiveWindow();
__declspec(dllimport) int __stdcall MessageBoxA(void* hWnd, const char* lpText, const char* lpCaption, unsigned int uType);
__declspec(dllimport) void __stdcall ExitProcess(unsigned int);
}
Экспортируем нужные нам функции.
Так как нет STL и libc то нужно для совместимости кода написать свой минимальный вариант.
void* malloc(size_t size)
{
return HeapAlloc(GetProcessHeap(), 0, size);
}
void free(void* ptr)
{
if (ptr)
{
HeapFree(GetProcessHeap(), 0, ptr);
}
}
Базовые вещи, поддержку динамической памяти.
export
{
void* operator new(size_t bytes)
{
return std::malloc(bytes);
}
void* operator new[](size_t bytes)
{
return std::malloc(bytes);
}
void* operator new(size_t bytes, void* ptr)
{
return ptr;
}
void* operator new[](size_t bytes, void* ptr)
{
return ptr;
}
}
Перегружаем аллокаторы, они нам нужны что бы написать минимальные std::string
typedef base_string<char> string;
Я не стал приводить внутренности строки, они не особо интересны, но в итоге все это экспортируется в пространство имен std;
Код main не меняется, это очень важно.
В чем суть?
Такой способ позволяет писать на С++ 23 под ретро железо с Windows 9x. Пишем в студии код, отлаживаем, тестируем, используем фичи, лямбды, constexpr, модули.
Но при сборке именно под старые ОС используем std реализацию, сейчас в ней не очень много кода, только базовые вещи, что бы запустилось и работало. Общий код остается единым не требуется его переписывать, нужно лишь ограничить проект фичами. Не использовать потоки, файловую систему, мьютексы и исключения. STL можно написать свою, добавить основные контейнеры vector, unordered_map, array.
Из коробки доступно string_view, span и т.д
Для поддержки сборки создал каталог make в нем батники для сборки и сам линкер от vc 6.0

Файл компиляции compiler.bat
call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars32.bat"
cl /O2 /std:c++20 /arch:IA32 /GS- /Zc:threadSafeInit- /GR- /EHa- /c ../Win32.ixx
cl /O2 /std:c++20 /arch:IA32 /GS- /Zc:threadSafeInit- /GR- /EHa- /c ../std.ixx
cl /O2 /std:c++20 /arch:IA32 /GS- /Zc:threadSafeInit- /GR- /EHa- /c ../main.cpp
pause
Файл линковки linker.bat
link /SUBSYSTEM:CONSOLE,4.0 /NODEFAULTLIB /ENTRY:EntryPoint /OUT:main.exe ^
main.obj ^
std.obj ^
kernel32.lib ^
user32.lib
pause
Не самый удобный способ, но позже добавлю вариант сборки в cmake, пока cmake собирает только под современные системы.
cmake_minimum_required(VERSION 3.28)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_SCAN_FOR_MODULES ON)
set(SOURCES
Win32.ixx)
add_executable(main "main.cpp")
target_sources(main PUBLIC FILE_SET CXX_MODULES FILES ${SOURCES})
Теперь все ради чего были все наши мытарства!

Оно живое и даже работает:)
Слышу первый же вопрос, зачем?
Отвечаю.
Мне по фану. Я хочу портировать свои проекты на С++ 23 и оставить совместимость со старыми системами. Только они могут показать, красоту кода через производительность. На моём основном ПК i5 11400 и 48 гб озу (купил до полного пи.. подорожания) На нём ничего не тормозит, ни мой проект LDL, не проект по воссозданию движка Arcanum по которому я пишу цикл статей. А именно на старом железе, где мало памяти у процессора смешные 50-150 mhz и вдруг на них работает графика, проигрывается звук, какой никакой геймплей, 2D графика, бывает даже 3D. И это работает приемлемо, твой код продуман, уже не получится не приходя в сознание писать код чик чик и в прод. Мне это нравится, а если ещё и доступен современный С++ 23 это прекрасно и расширяет вообще сферу ретро программирования, не нужно искать старые компиляторы, пиши на современных собирай и проверяй на Windows 9x.
Это лишь пока проверка идей, ещё предстоит разделить модуль std на контейнеры и реализации определенных фич типа expected. Заменить winapi на реализацию std cout в небольшом объеме. Главное, что бы код который пишут на современном ПК, мог без изменений собираться таким способом с подменой всего std, но с доступам к основным фичам С++ 23. Я думаю это клёво.
Теперь я могу продолжать свой цикл статей по разработке движка Arcanum на С++ 23 и не отказываться от поддержки Windows 98.
В ближайшее время обновлю, реализацию std добавлю поддержку linux. Основные контейнеры STL. Думаю, что в Linux с такими пируэтами даже проще.
Есть конечно вариант портировать последний gcc для сборки наитивно под Windows 98, как это сделал пользователь fsb4000, для gcc 11. Но пока я к такой древней и темной магии не готов:)
Обязательно напишу статью о более серьезных успехах, когда они будут в портировании LDL на С++ 23 код с работой на Linux, Windows. Сейчас же это описание возможно безумного, странного, но все же рабочего способа, запустить современный С++ код, на старых системах.
Отвечу на вопрос кому оно вообще нужно то?
Отвечаю. Нам.
К примеру в русскоязычной сфере это конечно Old-Games.ru
Это центр всех любителей и энтузиастов старых, игр, программ, ПК. И я рад, сказать, что тоже являюсь частью. Но мы не только играем в старые игры, но так же создаем новые проекты для старых систем. Новые переводы для старых игры, новые моды, новые улучшения. Все это делается ради фана. Потому, что нравится, у каждого есть своя работа и свои заботы. Но все жё находится время и на такое хобби. Здесь нет KPI или менеджеров, чем хочешь тем и занимайся.
Все же есть в старом железе, свое очарование!
Буду раз, предложениям, советам, критике и рад пообщаться в комментариях.
Ссылка на весь представленный код
Комментарии (44)

JBFW
24.12.2025 12:21А нет такого ощущения, что переизбыточное усложение C++, с добавлением всё новых и новых абстракций, приведет примерно к тому, что C++ перестанут пользоваться?
Типа, "писать на C с плюсами (по книжке K&R 90-х) - это примитивно и устарело, а понять C++ 30 без 30-летнего опыта разработки на нём - нереально, ну его нафиг"

JordanCpp Автор
24.12.2025 12:21У меня нет 30-ти летнего опыта программирования. Но чуть знаю. Я бы посмотрел с другой стороны, к примеру java, python библиотек в стандарте намного больше но никто не говорит, что это языки монстры которые не возможно изучить. Но так же правда, что устраиваясь на мидл java или python нужно знать не только язык, но и его экосистему которая в разы больше С++.
Нет у меня, нет ощущения, что он не нужен и вот вот от него в е откажутся.

SilverTrouse
24.12.2025 12:21Современные стандарты добавляют очень много приятных фичей, упрощающие жизнь. К примеру писать шаблоны до с++20 и после две разные вещи. Да даже до с++17 много вещей упрощающие визуал и понимание ( if constexpr, концепты, templete for в грядущем 26 стандарте)

JBFW
24.12.2025 12:21Проще было писать в 1999: никаких концептов, шаблонов и прочего: обычный С + некоторые удобные вольности (типа инкремента и переопределения функций) + классы.
MFC например в таком стиле был написан примерно весьУвидеть сейчас это можно в библиотеках Ардуино, "тот самый С++".
А вот потом пошло-поехало...

JordanCpp Автор
24.12.2025 12:21Ага помним.
// helloapp.cpp : Minimal MFC Windows app. // // This is a part of the Microsoft Foundation Classes C++ library. // Copyright (C) 1992 Microsoft Corporation // All rights reserved. // // This source code is only intended as a supplement to the // Microsoft Foundation Classes Reference and Microsoft // WinHelp documentation provided with the library. // See these sources for detailed information regarding the // Microsoft Foundation Classes product. #include <afxwin.h> // Define a window class derived from CFrameWnd class CHelloWindow : public CFrameWnd { public: CHelloWindow() { Create(NULL, "Hello World!", WS_OVERLAPPEDWINDOW, rectDefault); } }; // Define an application class derived from CWinApp class CHelloApp : public CWinApp { public: virtual BOOL InitInstance(); }; // Construct the CHelloApp's m_pMainWnd data member BOOL CHelloApp::InitInstance() { m_pMainWnd = new CHelloWindow(); m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return TRUE; } CHelloApp HelloApp; // HelloApp's constructor initializes and runs the app
JBFW
24.12.2025 12:21Я про синтаксис )
MFC - отдельная тема. Так-то можно ещё на чистом winapi писать, со всеми этими объявлениями класса окна и какими-то костылями из Паскаля

Siemargl
24.12.2025 12:21А Паскаль то тут при чем?
MFC это тоненькая обвязка WinAPI + hwnd.

JBFW
24.12.2025 12:21А вы погуглите.
int PASCAL MessageBox(HWND, LPCSTR,...)
Это влияло на работу со стеком. И где-то там ещё были строки паскалевского типа, где первый байт длина, и строка не завершается х0, как в С. Потому что в самом начале Винду вообще планировали на Паскале делать.
И MFC это совсем не тоненькая обвязка, а такой "классный монстр", построенный на наследовании всех классов от единого предка, CObject. CWnd в том числе.
В глубинах которой как раз и шло обращение к winapi через hwnd.
А вот hwnd - это всего лишь указатель на экземпляр класса окна в ОС, handle window, это как раз составная часть winapi. Все объекты в ней - windows, у каждого свой handle, и через него идёт передача events в окно.
Где-то на дисках в каталогах .../1/old/old/win/doc.... до сих пор валяется документация, наверное...

Siemargl
24.12.2025 12:21Мне не надо гуглить, а на нем писал =)
От Паскаля там Pascal calling convention, в Win16 API. Паскаль-строк не припоминаю. Байку такую слышал про написание на паскале, но достоверность не знаю

Siemargl
24.12.2025 12:21Upd. Кстати на Win11 прекрасно работает MS Visual C++ 1.1 32-bit с MFC 2.1 (всего 1.5 МБ исходников)

JBFW
24.12.2025 12:21По нынешним временам - да, можно сказать "тонкая обвязка".
Но тогда...Делал как-то программку типа "Нортона" (только графическую, с иконками каталогов и файлов) - на голом winapi она заняла что-то около 50 килобайт (всего !), на MFC подобное - от 300 и больше.
Когда хард - 240Мб весь - это существенно.
Потому что класс на классе и классом погоняет, где надо и не надо. И кстати, дало дурной заразительный пример - тоже начал так делать, пока не дошло, что это как-то неправильно...Но в целом, С++ тогда был простым - я об этом говорю.

JordanCpp Автор
24.12.2025 12:21Точно помню информацию, что старые mac os как раз использовали pascal для разработки и довольно широко. После чего со временем перешли на objective C.

JordanCpp Автор
24.12.2025 12:21В чертогах памяти, вспоминается что использование PASCAL как пример
long FAR PASCAL WndProc ( HWND hwnd, UINT msg, UINT wParam, LONG lParam )использовали, не потому, что первые версии были на pascal, а потому, что это ускоряло код. Откуда эта информация, не помню.

Siemargl
24.12.2025 12:21Нет, все проще.
Для cdecl очищает стек вызывающая функция и на каждый вызов приходится на одну ассемблерную инструкцию больше, зато умеет в переменное число параметров.
pascal и stdcall отличаются только порядком передачи параметров, очистка стека вызываемой ф-цией.

RH215
24.12.2025 12:21Скорее, наоборот, то, что было с 11 по 23 упростило разработку до какого-то вменяемого уровня. Сложность языка всё ещё запредельная, но писать стало приятнее.

Jijiki
24.12.2025 12:21тут надо затронуть 1 очень важную деталь, железный конь на фон Неймане который уже не тот когда был придуман С - и новое будущее, которое стучится к нам с Растом, и почему-то верификация, если на неё посмотреть архитектурно смотрится уже не так как тогда когда пытались доказать что Раст имба, тоесть мы буквально шагнули в будущее когда первые байты Раста тронули центральный процессор, так что 86-64 будем посмотреть, но эволюция двинулась вперед в мире процессоров однозначно

windsurfer69
24.12.2025 12:21Наоборот, начиная с С++11 наконец на С++ стало писать проще, благодаря новым функциям языка и новым шаблонам разработки. И проявился такой неожиданный эффект - код на новых стандартах просто работает на всех системах, где этот стандарт поддерживается - фактически - везде. С++ повезло, что у него есть Бьёрн с его энергией и авторитетом.

blackstrip
24.12.2025 12:21Скоро майкрософт закроет C++ вообще, откажется от его использования и забудет о нем)
Microsoft к 2030 году планирует отказаться от языков программирования C и C++ и переписать кодовые базы на Rust с помощью «ИИ-инфраструктуры»
Об этом рассказал главный инженер компании в посте на LinkedIn.
Модернизация крупных кодовых баз и переход на Rust — не официальное заявление компании, а планы ведущего инженера Microsoft Галена Ханта. Он выложил пост на LinkedIn, в котором рассказал, что ищет инженера-программиста, чтобы убрать «каждую строку C и C++ из Microsoft к 2030 году». Для решения этой «ранее немыслимой задачи» команда собирается «объединить ИИ и алгоритмы для переписывания крупнейших кодовых баз».

RH215
24.12.2025 12:21Это что-то уровня отказа от Кобола. Они, конечно, могут отказаться от новых проектов на С++, но переписать весь старый код до 2030 -- это абсурд.
(Вообще, этот проект больше исследование, чем план)

Ulrih
24.12.2025 12:21у ИИ есть "наработки" в миллионы строк кода на Rust'е? или оно будет учиться на этом?

Jijiki
24.12.2025 12:21просто напишут в первом диалоге чтобы ждал файл по приходу файла транслировал этот код в равный Расту, недели 3(около 4 дней трансляции, далее около 1к вопросов на закрепление и правки нейрослопом) и далее допил командой около 3 лет что-то такое я думаю
суть еще в том что они могут диалог распечатать, соотв вопросы будет задавать их начальник типо ну мне так кажется
тоесть если это гпт 5.5 это именно так но тут момент готовы ли они кидать в сетку код
ну в любом случае наверняка уже знают лучше как это сделать я только предположил
а может не так, может просто будут брать куски и с асистентом перепиливать на Раст в обычном режиме
оба варианта на 3 года уйдёт если там много связного кода

JordanCpp Автор
24.12.2025 12:21Возможно в 2030 году выйдет директор Microsoft и скажет, очевидно эксперимент не удался и т.д Но мы приобрели опыт, знания и бла, бла, бла

Jijiki
24.12.2025 12:21может и так но мат доказательство Раста на фриз версии удобнее чем ловить ошибки на С с goto

Okeu
24.12.2025 12:21в 2030 году выйдет директор Microsoft и скажет,
приглашаем присоединиться вас к программе Windows Insider =)

rbdr
24.12.2025 12:21Их последний вин11 уже прекрасный антипример. Есть сомнения, что раст какой-нибудь им поможет.

JordanCpp Автор
24.12.2025 12:21Допустим даже если поможет, а толку то. Безопасная, но тормозная, неудобная, сливающая тоннами информацию о пользователе, за его же деньги. Проблемы с обновлением, оптимизацией, купите ПК с tpm 2.0 и бла, бла, бла.

Siemargl
24.12.2025 12:21Без исключений печально, std будет неполной.
Сторонний линкер с полной поддержкой COFF с MS совместимыми исключениями не вариант подобрать?

JordanCpp Автор
24.12.2025 12:21Исключения добавил в бэклог. Я о них знаю, помню и буду искать варианты. Но для быстрого старта и такой вариант приемлем.

CrashLogger
24.12.2025 12:21А что такого они сломали в Visual Studio, что экзешник не может запускаться под Win98 ? Стандарт PE файлов вроде не менялся, что там может быть несовместимо ?

JordanCpp Автор
24.12.2025 12:21Версия файла, но это не проблема. Проблема в новых API которые используются sв реализации стандартной библиотеки, исключения которые полагаются на новый windows api. Тоже самое с рантайм. Еще сам формат новые версии добавляют дополнительные секции, которые может обрабатывать только новый линкер, который знает только о новой винде.
Я думаю, в linux проблем будет меньше в этом плане. Пока мне не до конца понятная вещь это именно исключения, есть понимание что у каждого компилятора опора на конкретное api и если его подменить, есть шанс что заведется.

Siemargl
24.12.2025 12:21Кстати, тоже вариант. Несовместимость обычно в startup коде. Может быть, простая его подмена и решит большинство вопросов.
Проблема в новых API которые используются sв реализации стандартной библиотеки, исключения которые полагаются на новый windows api
Это очень сомнительно.
А вот рантайм в целом, наоборот, может тоже взять постарше.

JordanCpp Автор
24.12.2025 12:21Ещё поэкспериментирую с поддержкой исключений. Уверен, что решение есть.

DrMefistO
24.12.2025 12:21Сразу предвещаю дополнительные проблемы, не описанные в статье:
иногда происходит добавление обращений к WinAPI, которые недоступны в старых ОС
Необходимо выключать также генерацию опкодов новомодных инструкций типа SSE, иначе для каких-то оптимизаций студия задействует их, и код в итоге крэшнется, и понять почему будет крайне сложно

JordanCpp Автор
24.12.2025 12:21Я это учел, использую winapi которые есть начиная с windows 95.
Для того, что бы не было sse применяю ключи компиляции /arch:IA32
Данные проблемы как раз легко обходятся.

JordanCpp Автор
24.12.2025 12:21Я ожидал проблем с манглингом имен, но их нет. Протестировал на дополнительных модулях, классы, доп using'и и компилятор применяет совместимые названия, линкер от vc 6.0 корректно все линкует.

JordanCpp Автор
24.12.2025 12:21Данный подход расширяет программирование под ретро системы. С++ 23 его фичи и использование современных компиляторов хорошо оптимизируют код. Это дополнительная производительность, за которую по сути я не плачу это из коробки. Очень приятно, мне в библиотеке LDL приходилось для софт рендера дублировать работу с разными форматами изображений, с помощью С++ я смогу это упростить и быть уверенным, что все заинлайниться так как буду использовать шаблоны, constexpr.

begin_end
24.12.2025 12:21О, аналогично играюсь, компилируя на современной ОС минимально возможные exe, работоспособные как сейчас, так и в Windows 95. Правда, я фанат Делфи (в данной форме применения - деградировавшего почти до Паскаля + Win32 api). Также использую специфичный подход, беря .lib файлы и старый майкрософтский линкер v.5.12.
Сборка

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

JordanCpp Автор
24.12.2025 12:21Не сказал бы, что игрушка. Можно писать не многопоточные программы, игры разной сложности, можно использовать динамическую линковку к dll,so. Это будет работать.
Вторым шагом можно сделать модули для SDL 1.2 и SDL3 для новых и старых систем. В модулях загружать через GetProcAddress.
Создать модуль с единым api который бы вызывал данные модули в зависимости от системы. Доделать STL.
И в итоге можно писать один код сразу для всех новых и старых систем на современном С++ 23. Пока без исключений, так как пока для меня их добавление, намного сложнее, чем написать основные библиотечные фичи и контейнеры.

TitovVN1974
24.12.2025 12:21А как использование всех этих возможностей влияет на производительность?
Получается ли эффективный код?
BorisU
Тут примчались санитары и зафиксировали нас (с) :)