Я часто повторяю, что .NET Core — это опенсорс и он работает «везде». MonoGame, Unity, Apple Watch, Raspberry Pi и микроконтроллеры, дюжина линуксов, Windows и так далее. Уже немало.
Но кому-то всё-таки мало. Михал Стреховски хочет запускать C# действительно везде.
![](https://habrastorage.org/r/w1560/getpro/habr/post_images/a16/bfc/532/a16bfc53266ec828bd93f6d8ec96ef3f.png)
C# в Windows 3.11
Он запустил код C# в двух «невозможных» системах, которые теперь дополнили наше определение «работает везде». Хотя это забавные эксперименты (не повторяйте их в продакшне), они подчёркивают как технические способности Михала, так и гибкость базовой платформы.
В семи твитах Михал рассказывает, как ему удалось запустить код C# под Windows 3.11. Приложение простое, здесь только вызов функции MessageBoxA с отображением соответствующего диалогового окна, которое в Windows с первых дней. Для вызова функции и получения результата используется DllImport/PInvoke.
Я сначала показал это приложение для Windows 3.11, потому что оно классное. Но в реальности автор начал с того места, где закончился его эксперимент с DOS. Он компилирует нативный код C#, и после этого правил больше не существует.
В этом примере он работает на платформе Win16, а не Win32. Однако в 1992 году (да, я тогда жил и программировал, и использовал это в проектах!) существовал определённый технологический мост под названием Win32s: подмножество API из Windows NT, которые были портированы обратно на Windows 3.11. Поэтому с учётом некоторых ограничений можно написать 32-битный код и обращаться из Win16 к Win32.
Михал понял, что объектные файлы, созданные AOT-компилятором CoreRT в 2020 году, можно собрать компоновщиком из Visual C++ 2.0 образца 1994 года. В результате получается машинный код, скомпонованный с интерфейсами Win32s, работающими в 16-разрядной Windows 3.11. Магия. Респект Михалу.
![](https://habrastorage.org/r/w1560/getpro/habr/post_images/342/543/565/3425435658b7a8ff087b7de75760560d.png)
Простое приложение Hello World C#
Я и раньше писал об автономных исполняемых файлах .NET Core 3.x, я большой фанат этого дела. Моё приложение ужалось до 28 мегабайт. Это совсем немного, учитывая, что оно включает в себя среду выполнения .NET и множество других ресурсов. Конечно, не следует судить о VM/рантайме по размеру минимально возможной программы, но Михал хотел посмотреть, до какого предела можно дойти — и поставил цель 8000 байт!
Программа работает в текстовом режиме, что, по-моему, здорово. Она также устраняет необходимость в сборщике мусора, поскольку здесь отсутствует выделение ресурсов. Это означает, что вы не можете нигде использовать new. Нет ссылочных типов.
Для объявления статических массивов он использует поля
Конечно, когда вы пытаетесь сделать какой-то автономный экзешник .NET, то изначально получаете файл 65 мегабайт, который включает приложение, среду выполнения и стандартные библиотеки.
Можно применить ILLinker и PublishedTrimmed для оптимизации Tree Trimming из .NET Core 3.х, но так вы уменьшите файл лишь до 25 мегабайт.
Он попытался использовать Mono и mkbundle, доведя размер до 18,2 мегабайт, но затем поймал ошибку. И среда выполнения по-прежнему никуда не делась.
Таким образом, единственным подходящим рантаймом остался CoreRT, который не включает в себя виртуальную машину, а только вспомогательные функции.
Так он получил 4,7 мегабайта, но это всё равно слишком много. С некоторыми настройками можно дойти до 3 мегабайт. Можно полностью вытянуть рефлексию и дойти до 1,2 мегабайта. Теперь она поместится на дискете!
Этот размер в один мегабайт кажется жёстким ограничением только для .NET SDK.
Вот где Михал уходит от стандартных инструментов. Он делает реимплементацию-заглушку для базовых типов System! Затем перекомпилирует с некоторыми волшебными переключателями, чтобы вышла только IL-версия экзешника.
Затем передаёт это в CoreRT, чтобы получить нативный код.
И вот мы здесь.
Ещё несколько хитростей — и на выходе 27 КБ! Затем он убирает из компоновщика несколько переключателей, чтобы отключить и удалить различные вещи, используя те же методы, которые используют разработчики на ассемблере, и в результате остаётся 8176 байт. Эпический триллер.
Подпишитесь на твиттер Михала и поаплодируйте ему.
Но кому-то всё-таки мало. Михал Стреховски хочет запускать C# действительно везде.
![](https://habrastorage.org/getpro/habr/post_images/a16/bfc/532/a16bfc53266ec828bd93f6d8ec96ef3f.png)
C# в Windows 3.11
Он запустил код C# в двух «невозможных» системах, которые теперь дополнили наше определение «работает везде». Хотя это забавные эксперименты (не повторяйте их в продакшне), они подчёркивают как технические способности Михала, так и гибкость базовой платформы.
Запуск C# под Windows 3.11
В семи твитах Михал рассказывает, как ему удалось запустить код C# под Windows 3.11. Приложение простое, здесь только вызов функции MessageBoxA с отображением соответствующего диалогового окна, которое в Windows с первых дней. Для вызова функции и получения результата используется DllImport/PInvoke.
Я сначала показал это приложение для Windows 3.11, потому что оно классное. Но в реальности автор начал с того места, где закончился его эксперимент с DOS. Он компилирует нативный код C#, и после этого правил больше не существует.
В этом примере он работает на платформе Win16, а не Win32. Однако в 1992 году (да, я тогда жил и программировал, и использовал это в проектах!) существовал определённый технологический мост под названием Win32s: подмножество API из Windows NT, которые были портированы обратно на Windows 3.11. Поэтому с учётом некоторых ограничений можно написать 32-битный код и обращаться из Win16 к Win32.
Михал понял, что объектные файлы, созданные AOT-компилятором CoreRT в 2020 году, можно собрать компоновщиком из Visual C++ 2.0 образца 1994 года. В результате получается машинный код, скомпонованный с интерфейсами Win32s, работающими в 16-разрядной Windows 3.11. Магия. Респект Михалу.
![](https://habrastorage.org/getpro/habr/post_images/342/543/565/3425435658b7a8ff087b7de75760560d.png)
Простое приложение Hello World C#
Запуск C# в 8 КБ под DOS
Я и раньше писал об автономных исполняемых файлах .NET Core 3.x, я большой фанат этого дела. Моё приложение ужалось до 28 мегабайт. Это совсем немного, учитывая, что оно включает в себя среду выполнения .NET и множество других ресурсов. Конечно, не следует судить о VM/рантайме по размеру минимально возможной программы, но Михал хотел посмотреть, до какого предела можно дойти — и поставил цель 8000 байт!
Программа работает в текстовом режиме, что, по-моему, здорово. Она также устраняет необходимость в сборщике мусора, поскольку здесь отсутствует выделение ресурсов. Это означает, что вы не можете нигде использовать new. Нет ссылочных типов.
Для объявления статических массивов он использует поля
fixed char []
: они должны жить в стеке, а стек у нас маленький.Конечно, когда вы пытаетесь сделать какой-то автономный экзешник .NET, то изначально получаете файл 65 мегабайт, который включает приложение, среду выполнения и стандартные библиотеки.
dotnet publish -r win-x64 -c Release
Можно применить ILLinker и PublishedTrimmed для оптимизации Tree Trimming из .NET Core 3.х, но так вы уменьшите файл лишь до 25 мегабайт.
Он попытался использовать Mono и mkbundle, доведя размер до 18,2 мегабайт, но затем поймал ошибку. И среда выполнения по-прежнему никуда не делась.
Таким образом, единственным подходящим рантаймом остался CoreRT, который не включает в себя виртуальную машину, а только вспомогательные функции.
dotnet publish -r win-x64 -c Release /p:Mode=CoreRT
Так он получил 4,7 мегабайта, но это всё равно слишком много. С некоторыми настройками можно дойти до 3 мегабайт. Можно полностью вытянуть рефлексию и дойти до 1,2 мегабайта. Теперь она поместится на дискете!
dotnet publish -r win-x64 -c Release /p:Mode=CoreRT-ReflectionFree
Этот размер в один мегабайт кажется жёстким ограничением только для .NET SDK.
Вот где Михал уходит от стандартных инструментов. Он делает реимплементацию-заглушку для базовых типов System! Затем перекомпилирует с некоторыми волшебными переключателями, чтобы вышла только IL-версия экзешника.
csc.exe /debug /O /noconfig /nostdlib /runtimemetadataversion:v4.0.30319 MiniBCL.cs Game\FrameBuffer.cs Game\Random.cs Game\Game.cs Game\Snake.cs Pal\Thread.Windows.cs Pal\Environment.Windows.cs Pal\Console.Windows.cs /out:zerosnake.ilexe /langversion:latest /unsafe
Затем передаёт это в CoreRT, чтобы получить нативный код.
ilc.exe zerosnake.ilexe -o zerosnake.obj --systemmodule zerosnake --Os -g
И вот мы здесь.
«Теперь у нас zerosnake.obj — стандартный объектный файл, ничем не отличающийся от объектных файлов, создаваемых другими нативными компиляторами, такими как C или C++. Последний шаг — скомпоновать его».
Ещё несколько хитростей — и на выходе 27 КБ! Затем он убирает из компоновщика несколько переключателей, чтобы отключить и удалить различные вещи, используя те же методы, которые используют разработчики на ассемблере, и в результате остаётся 8176 байт. Эпический триллер.
link.exe /debug:full /subsystem:console zerosnake.obj /entry:__managed__Main kernel32.lib ucrt.lib /merge:.modules=.rdata /merge:.pdata=.rdata /incremental:no /DYNAMICBASE:NO /filealign:16 /align:16
Подпишитесь на твиттер Михала и поаплодируйте ему.