Рабочий стол в духе Win9x, на котором живут настоящие Win32-приложения на C — и компилируются прямо во вкладке браузера, без сервера и без Emscripten.
- Демо — рабочий стол с Сапёром, Блокнотом, командной строкой и компилятором; всё на настоящем Win32 C, скомпилированном в WebAssembly
- Репозиторий
Открываю Сапёр, потом — исходник программы, компилирую hello-world и запускаю. Всё в браузере.
Что это
winweb — маленький рабочий стол в стиле Windows 9x, живущий в браузере. Окна, панель задач, меню «Пуск», Проводник, иконки. Но суть не в оформлении: приложения здесь — настоящие программы на Win32 C, написанные с WinMain, RegisterClass, циклом сообщений и рисованием через GDI. Они скомпилированы в WebAssembly и реально выполняются.
В комплекте — Сапёр (тот самый, на RegisterClass/WM_PAINT/BitBlt), Блокнот, командная строка cmd, демка с иконками, консольный hello-world. И — компиляторы cc и msbuild, потому что C-исходники можно собирать прямо здесь.
Сначала был Emscripten
Идея была очевидная: взять Win32-приложение на C, скомпилировать в wasm и подсунуть ему JS-фасад вместо настоящего user32/gdi32. Первая мысль — Emscripten: он отлично компилирует C в wasm, у него есть всё.
И поначалу так и было. Но мне хотелось большего — чтобы приложения можно было собирать изнутри: открыть .c в Блокноте, поправить, нажать сборку и тут же запустить. Маленькая IDE внутри самого рабочего стола.
Вот тут Emscripten и закончился. Чтобы компилировать C в браузере, нужен компилятор в браузере. А Emscripten — это clang плюс LLVM, сотни мегабайт нативного кода; в браузер его не затащить. Плюс мелочь, но неприятная: модули Emscripten делят общий сегмент данных, и запустить два экземпляра одного приложения независимо (два Сапёра рядом) без плясок не выходит.
Значит, нужен изначально маленький компилятор.
Маленький lcc вместо Emscripten
Компилятор у меня уже был — про него прошлая статья: я взял учебный ретаргетируемый компилятор C LCC (~16 тысяч строк), написал ему бэкенд в WebAssembly, а потом скомпилировал его же самого в wasm. Получился rcc.wasm — компилятор C размером ~280 КБ, целиком работающий как WebAssembly.
С ним всё встало на места:
- Компиляция в браузере стала реальной.
cc demo.cв командной строке — этоrcc.wasmплюс крошечный препроцессор на TypeScript. Тот же компилятор, что собирает приложения под node, работает и во вкладке — байт-в-байт. - Каждый модуль самодостаточный. lcc-приложение экспортирует свою память и таблицу функций — никакого общего сегмента. Два Сапёра рядом — это два независимых экземпляра, каждый со своей памятью. То, что не получалось с Emscripten, тут вышло само.
- Размер. hello-world — пара килобайт wasm, а не «рантайм плюс программа». Всё, что приложению нужно снаружи, оно импортирует у хоста.
Полное избавление от Emscripten оказалось не жертвой, а апгрейдом.
GDI, user32, System32
Дальше — самое интересное: чем приложение «думает», что оно общается с Windows.
GDI. Когда приложение зовёт Rectangle, TextOut, CreateFontW, BitBlt — за этим стоит фасад, рисующий на <canvas>. Сапёр рисует поле, цифры, объёмные рамки DrawEdge, мину и улыбку — всё это настоящие GDI-вызовы, просто исполняет их JS поверх canvas.
Окна. Менеджер окон — на DOM: каждое окно это <div> с заголовком, кнопкой закрытия, меню и иконкой. CreateWindowEx создаёт окно, RegisterClass запоминает оконную процедуру, а цикл GetMessage/DispatchMessage гоняет настоящие сообщения — WM_PAINT, WM_COMMAND, WM_TIMER, WM_SIZE.
DLL — по-настоящему. Самое неожиданное: user32, gdi32 и kernel32 — это реальные файлы C:\Windows\System32\user32.wasm и так далее. Приложение импортирует функции оттуда, а уже эти wasm-«библиотеки» тонкими переходниками зовут JS-фасад. То есть цепочка честная: app.wasm → user32.wasm → JS. Не потому что иначе нельзя — а потому что так аккуратнее и красивее.
System32. В C:\Windows\System32 лежат и инструменты: cmd.wasm, cc.wasm, msbuild.wasm, notepad.wasm. Это обычные исполняемые wasm-файлы. cmd ищет их по имени (как по PATH), а Проводник умеет отличить консольную программу от оконной и при двойном клике открыть консоль.
Это работает по-настоящему
Лучшая проверка — собрать приложение изнутри. Открываешь в Блокноте iconsdemo.c, что-то меняешь, в командной строке набираешь msbuild IconsDemo — компилятор в браузере собирает .wasm и кладёт в C:\Program Files\.... Запускаешь иконку на рабочем столе — открывается твой изменённый вариант. Сервера во всём этом нет нигде: компилятор, линковка, файловая система (на IndexedDB), запуск — всё во вкладке.
И мелочи, из которых складывается ощущение настоящего: командная строка с cd/dir/type и регистронезависимыми путями; иконки приложений, зашитые в сам wasm и подхватываемые в заголовке окна и на панели задач; ресайз окна Блокнота; кириллица в UTF-8; консольные программы с вводом и exit.
Получилась штука, которую приятно открыть в браузере и потыкать: не имитация Win9x, а маленький, но настоящий Win32-рантайм — со своим компилятором, своими DLL и реальными C-приложениями. И всё это — меньше мегабайта, грузится мгновенно.