[RU]

[EN]
Alphazine - Only Successful People.
ГАРАНТ ПОПОЛНИТЬ ДЕПОЗИТ

Вернуться   Alphazine - Only Successful People. > Общий > Ботнеты. Связки. > Статьи.

Важная информация

[Определение часового пояса]
[ Регистрация ]
Имя:
Отделение людей от роботов


 
Опции темы
Полиглот-рантаймофоб
Старый   #1
Admin
Админ
 
Регистрация: 01.01.2020
Сообщений: 3,798
Спасибо: 172 раз(а)
Репутация:    
Депозит: 1.021 ?
Business Level: 25 ?
Отправить личное сообщение для Admin
Полиглот-рантаймофоб

Привет, кулхацкеры. Знаю, знаю… название звучит странно, но потерпите немного, сейчас я все объясню. В наших бесконечных холливарах о пользе и вреде разных языков программирования при разработке малвари я часто слышал один аргумент, который мне всегда казался странным. Мол Сишечка такая православная и честная, что на ней можно писать так, как делали мега-элитные деды во времена, когда динозавры были большими. А именно: без этих ваших рантаймов — на чистейшем (как слезы девственницы) WinAPI, получая прекраснейшую чистую малварь в 10-50кб, которая работает на всех возможных Вендах без всяческих фреймворков и дополнительных библиотек. А не как эти ваши новомодные Петухоны и Голанги, которые собираются в мегабайты хер пойми какого кода с этими вашими сборщиками мусора и интерпретаторами. Тогда в холливарах я часто отвечал мол, а что такого? Без рантайма можно и Плюсы собрать, да и другие нативные языки, было бы желание так изъёбываться. После последнего раза я все же задумался, а не напиздел ли я часом? И решил попробовать получить чистенькие хеллоуворлд экзешнички в 3-5кб с чистейшем WinAPI из разных языков программирования, а потом дать свои комментарии на тему, насколько это адекватно в каждом из языков по моему мнению, а также какие преимущества этот язык будет иметь над православным Цэ применительно к WinAPI-чистоте.

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

Содержание:
1. Введение
2. Цэ
3. Плюсы
4. Дэ
5. Ним
6. Васёк
7. Зиг
8. Заключение

1. Введение

Но давайте таки зайдем несколько издалека, чтобы всем было понятно, о чем речь. Если вы уже хорошо шарите в том, что такое рантайм в языках программирования, зачем он нужен или не нужен, чем код на чистейшем WinAPI такой элитный и тд, то можете смело пропустить весь раздел введения.

Современную компьютерную сферу можно рассматривать, как гигантских размеров луковицу, у который каждый слой — еще одна абстракция над кучей слоев под ним. Я, конечно, сильно упрощаю, но все же. Где-то глубоко внутри этих слоев у любого компа есть железо: процессор там со своими драгоценными ядрами, оперативная память и всякие такие вещи. Над железом стоит операционная система, которая по сути является абстракцией над железом. Над операционной системой находятся пользовательские и системные приложения/программы, которые зачастую общаются с железом и друг с другом не напрямую, а через операционную систему. Что позволяет не писать разные приложения для разных материнских плат, жестких дисков и так далее. Достаточно написать приложение для операционной системы. Понимаете аналогию? А если на слой пользовательских приложений вхерачить какую-нибудь тулзу для виртуализации (типа vmWare, Xen, VirtualBox и тд), то о количестве слоев становится уже больно думать.

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

Так исторически сложилось, что Сишечка заборола все другие языки и стала своего рода стандартом для всевозможного железа и архитектур процессоров. Обсуждение того, хорошо это или плохо, почему это произошло и так далее выходит далеко за пределы тематики статьи в сферу философии. Но сейчас, какую бы вы экзотическую железку не взяли, для нее будет как минимум Си-компилятор. Язык Си, насколько простым и близким к железу или ассемблерам он не был, все же является слоем абстракции. Поэтому для реализации на некотором железе некоторой логики, которую по стандарту язык должен обеспечивать, требуется дополнительный код. Этот код принято называть рантаймом языка. Код рантайма может быть написан на самом языке, используя, например, подмножество языка, которое в свою очередь не требует дополнительного кода. Или же на языке более низкого слоя абстракции. Так, например, код рантайма языка Си, ответственный за умножение и деление 64-битных чисел на 32-битных системах, обычно написан на Ассемблере.

История, не терпящая сослагательного наклонения, пошла еще дальше. Возьмем всеми любимый язык Петухон (Python). Большинство кода рантайма языка написано на языке более низкого слоя абстракции — Си (имеется ввиду основная его реализация — CPython), включая сборщик мусора, виртуальную машину, компилятор в байткод и хуеву тучу библиотек почти на все случаи жизни. При этом он зачастую позволяет писать код, не думая о том, на каком процессоре он будет исполняться, или на какой операционной системе, или кто там должен следить за выделенной памятью и не обосраться с указателями. Такие языки, как Петухон, делают программирование проще, ровно также, как сделали программирование проще другие слои абстракции: сначала ассемблеры, а потом и православные Сишечки. Конечно, у Петухонистов есть свои отдельные круги ада (например, как сделать цикл, который завершится раньше, чем когда пройдет 10 тысяч лет), но в целом такие языки, как Петухон, свои задачи слоя абстракции решают довольно хорошо.

Еще один важный момент заключается в том, что есть компилируемые в нативный для процессора код языки. А есть интерпретируемые языки, к которым я для простоты буду относить еще и те, которые компилируются в байткод и запускаются на виртуальной машине (стековые и/или регистровые виртуальные машины, не путать с вмВарами и ВиртуалБоксами) или даже компилируются в нативный код JIT-компилятором (just in time). В этой статье мы будем говорить исключительно о языках программирования, которые компилируются в нативный для процессора код, то есть в те самые нули и единички, заключенные внутри исполняемых файлов (EXE, DLL и другие, если мы говорим о Венде) — это обычно называют AOT-компиляцией (ahead of time). Ведь, как мы можем обойтись без рантайма языка, если рантайм реализует виртуальную машину, на которой язык собственно и должен исполняться, правильно?

Итак, повторим, языки программирования, которые мы будем рассматривать в этой статье компилируются в нативный код, а рантайм языков помогает этим языкам работать на платформе, реализуя функционал, необходимый для всяческих высокоуровневых конструкций языка, которые на самой платформе не реализованы. Ну и дальше больше, рантайм также позволяет языку одинаково работать на разных платформах, например, на разных операционных системах Windows, Linux, Mac OSX, или на разных процессорах, абстрагируя вызов системных функций универсальным интерфейсом в рантайме.

Теперь в чем же такая прелесть чистого WinAPI-кода и кого это вообще должно волновать в 2022 году? Термином WinAPI объединен весь перечень API-функций, который предоставляют операционные системы семейства Windows, сохраняя при этом обратную совместимость. То есть чистый WinAPI-код зависит только от API и сервисов, предоставляемых операционной системой, и не требует дополнительно установленных библиотек или дополнительного кода в каком-либо виде (будь то в самом исполняемом файле при статической линковки или во внешних сторонних от операционной системы библиотеках при динамической линковке). В исполняемом файле будет находиться только тот код, который написал программист.

Многие языки типа Голанга или чуть в меньшей степени тех же Ним и Дэ часто вхерачивают весь необходимый им код рантайма прямиком в исполняемый файл. Это позволяет иметь один жирненький экзешничек и не таскать с собой или не устанавливать отдельно набор динамических библиотек с рантаймом языка. Это удобно для программиста и пользователей программы, но делает исполняемый файлы довольно большими. Например, хеллоу ворлд на Голанге будет в районе 1мб. В современном мире на это всем похер, тк у всех (даже в мухосрансках) уже давно мегабитные интернеты. Но во времена малвари старой школы это имело большое значение. Поэтому, наверное по-инерции, многие умудрённые опытом кулхацкеры считают маленькую компактную малварь без лишнего кода и зависимостей - хорошей, а малварь на Голангах и Петухонах в несколько мегабайт веса — плохой.

Таким образом, мы с вами рассмотрели общие случаи. Теперь давайте посмотрим на частные случаи, когда умение создавать независимый от рантайма код было бы полезно. Во-первых, если мы можем заставить нативный язык программирования работать на чистом WinAPI (и у его компилятора есть ассемблерные вставки или интринсики), то это автоматически означает, что с помощью этого языка мы можем писать шелл-коды. Да-да, времена, когда нужно было писать шелл-коды на ассемблере, давно прошли, в чем мы уже убедились в моей статье про donut. Я и сам уже лет 10 не прикасался к ассемблеру даже при написании шелл-кодов. Во-вторых, если исполняемый файл состоит только из написанного нами кода, то и весь этот код можно прогнать через обфускатор до или во время его сборки. Согласитесь, работать с уже скомпилированным нативным кодом куда сложнее, чем с абстрактным синтаксическим деревом или промежуточным представлением сборки, типа LLVM IR (Clang, Rust, D и тд) или GIMPLE (GCC, D, Ada и тд). Кроме того наиболее тупые аверы не брезгуют поставить сигнатуру на рантайм языка программирования. Сцукены, никогда им не прощу совершенно негодный троллинг языка программирования Nim, когда из-за уебанских сигнатур абсолютно все экзешники на Ниме (включая компилятор и другие вспомогательные тулзы) начали палиться.

Возможно, я еще какое-то не очевидное преимущество кода на чистом WinAPI не вспомнил, но не переживайте, какой-нибудь представитель старой школы обязательно напишет об этом в комментариях к этой статье. Теперь давайте рассматривать непосредственно языки программирования.

2. Цэ

В качестве некоторого бейз-лайна, и чтобы было с чем сравнивать, нам для начала понадобится собрать исполняемый файл с чистым WinAPI из православной Сишечки. Для этого я не буду использовать пресловутую Visual Studio, так как сам уже давно ей не пользуюсь. В этом вопросе нам поможет GCC и MinGW, заботливо собранные ребятами из https://winlibs.com/ - там помимо GCC есть еще и сборка Clang/LLVM, что тоже удобно. При желании вы можете попробовать сделать тоже самое с помощью Clang, разница с GCC будет небольшая. Теперь давайте таки напишем программу на Цэ, которая с помощью чистого WinAPI выведет нам хеллоу ворлд.

Код:
C:

#include <windows.h>

void EntryPoint() {
    MessageBoxA(NULL, "Hello World!", NULL, MB_OK);
    ExitProcess(0);
}
Выглядит почти, как обычный хеллоу ворлд на Цэ, но есть несколько отличий. Во-первых, точка входа программы, которую мы обозвали «EntryPoint», имеет другой формат. На самом деле точки входа исполняемых файлов под Вендой именно такие: не принимающие никаких параметров и не возвращающие значений. Точка входа «main(int argc, char** argv)» типичная для Цэ на самом деле ложь. Эту точку входа вызывает рантайм языка Цэ. В рантайме есть своя точка входа, которую обычно называют «_start», она имеет именно такой же формат, как и наша «EntryPoint». В ней цэшный рантайм получает аргументы командной строки (обычно) через WinAPI функцию «GetCommandLine», парсит ее по определенным правилам (или с помощью WinAPI функции «CommandLineToArgv») и передает в точку входа «main», или «WinMain». Вместе с этим происходит еще несколько мелочей, суть которых нам сейчас не особо важна.

Во-вторых, в конце нашей программы мы принудительно вызываем WinAPI функцию «ExitProcess». В стандартной сборке за нас это бы сделал цэшный рантайм. После того, как мы вернули бы код завершения программы из «main», цэшный рантайм произвел бы ряд деинициализаций всяких мелочей, а затем вызвал бы с этим кодом функцию «ExitProcess», чтобы завершить текущий процесс. «Почему же нельзя выйти с помощью простого возврата управления из точки входа?» спросите вы. Дело в том, что для работы процесса операционная система может создавать в нем, например, дополнительные потоки, поэтому выхода основного потока не достаточно, чтобы гарантировать то, что процесс завершился. Теперь давайте соберем наш код в маленький чистейший экзешничек.

Код:
Bash:

i686-w64-mingw32-gcc   -o .\test32.exe -fno-ident -flto -O3 -Os .\test.c -nostdlib -e_EntryPoint -s -lkernel32 -luser32
x86_64-w64-mingw32-gcc -o .\test64.exe -fno-ident -flto -O3 -Os .\test.c -nostdlib -eEntryPoint  -s -lkernel32 -luser32
Мы используем компиляторы gcc для x86 и x64, чтобы сгенерировать 32-битный и 64-битный исполняемый файл соответственно. Параметр «-fno-ident» запрещает компиляторам и линкерам вставлять ident-строки (версия компилятора, которой был создан исполняемый файл), к WinAPI-чистоте эти параметры не относятся. Как по сути не относятся и параметры оптимизации «-O3» и «-Os». Параметр «-flto» включает оптимизации времени линковки, в частности этот параметр убирает из исполняемого файла тот код, который никогда в программе не использовался (часто это называют «DCE» — dead code elimination). Параметр «-nostdlib» указывает линкеру, что ему не нужно линковать исполняемый файл со стандартными библиотеками (в том числе с рантаймом), а «-e_EntryPoint» говорит ему, что точка входа нашей программы будет называться «EntryPoint» («_» в имени функции в 32-битной сборке нужно потому, что какому-то идиоту в далекие времена пришло в голову, что это будет отличной идеей манглить все названия цэшных функций, дописывая им подчеркивание впереди имени). Параметр «-s» удаляет отладочную информацию с исполняемого файла (незачем так упрощать жизнь аверам, так ведь). А параметры «-l» добавляются в линковку библиотеки «libkernel32.a» (для импорта функции «ExitProcess») и «libuser32.a» (для импорта функции «MessageBox»). Посмотрим, что же у нас получилось:




Вот они — наши драгоценные чистенькие WinAPI исполняемые файлики. 3-4кб чистейшей красоты и удовольствия от осознания собственного слегка упоротого превосходства над компиляторами и рантаймами. Есть что-то прекрасное в этом, в этих исполняемых файликах из одной функции, в которых есть только тот код, который мы написали сами. Хотя, может быть, во мне просто говорят отголоски старой школы, нужно не забывать пить таблетки...

Теперь давайте посмотрим, чего мы с вами лишились, отключая цэшный рантайм. Во-первых, всего того функционала, который выполняется до и после вызова классической цэшной точки входа. В частности статических конструкторов для сложных типов данных и парсинга командной строки. Ну чтож, не очень то и хотелось. Во-вторых, некоторых специфических для платформы вещей. Как я уже говорил, например, деление 64-битных чисел на 32-битной платформе. В-третьих, всех удивительно всратых функций цэшного и посиксного стандартов: от всяких malloc’ов до всяких strlen’ов. Если уж прям так хочется их использовать, придется написать свою реализацию, либо слинковать с libmsvcrt.a (msvcrt.dll), которая есть на всех операционных системах семейства Windows. Но в последнем случае нужно быть аккуратнее, тк эта библиотека сохраняет обратную совместимость с CRT библиотекой Visual Studio 6, поэтому многих современных вещей в ней найти даже не пытайтесь (как, например, «%ld» во всяческих printf’ах, если я правильно помню). Если готовы заплатить такую цену, милости просим в наш элитный «WinAPI-pure» клубешник.

3. Плюсы

Плюсы часто идут в единой пачке с Цэ компиляторами, как минимум под более менее популярные архитектуры и платформы (да, даже в 2022ые годы вы сможете где-то найти железку, для которой в природе существует только компилятор Цэ, а условный LLVM про ее поддержку даже не думал еще, что очень печально). Так и для нашей цели мы с вами воспользуемся теми же компиляторами, что и в примере для языка Цэ. Да и код менять нам сильно не придется (все-таки языки родственные). И да, по-умолчанию, Плюсы зависят от стандартной библиотеки языка Цэ, своей собственной стандартной библиотеки (libc++), в которой реализована в частности поддержка исключений, а так же в коде на Плюсах часто используется библиотека STL и (прости Хоспаде) Boost, хоть последний и не присутствует в стандарте языка (но в мире сложно встретить адепта Плюсов, который ни разу на Буст не мастурбировал, будьте осторожнее с ними, они страшные люди).

Код:
C++:

#include <windows.h>

extern "C" void EntryPoint() {
    MessageBoxA(NULL, "Hello World!", NULL, MB_OK);
    ExitProcess(0);
}
Чем-то напоминает те самые картинки, которые вроде бы одинаковые, но на них нужно найти N-отличий. На самом деле в нашем случае в плюсовом коде только одно отличие. Точку входа мы определили как «extern C». Поскольку, Плюсы в отличии от Цэ поддерживают перегрузку функций (function overloading, то есть две функции могут иметь одно и тоже имя, если имеют разные аргументы), появилось такое понятие как манглинг (name mangling). Оно заключается в том, что каждое имя в исходнике декорируется специальной кодировкой, которая может вписывать прямиком в имя информацию о конвенции вызовов, типах аргументов и возвращаемого значения, неймспейсах и классах, к которым функция принадлежит, и потенциально другую информацию. Поэтому, чтобы имя точки входа в объектном файле не заменилось на мангленное, мы помечаем эту функцию, как «extern C» (но если вы особо упоротый, то можете в качестве точки входа в параметры компиляции вписать и мангленное имя).

Код:
Bash:

i686-w64-mingw32-gcc   -o .\test32.exe -fno-ident -fno-exceptions -fno-rtti -flto -O3 -Os .\test.cpp -nostdlib -e_EntryPoint -s -lkernel32 -luser32
x86_64-w64-mingw32-gcc -o .\test64.exe -fno-ident -fno-exceptions -fno-rtti -flto -O3 -Os .\test.cpp -nostdlib -eEntryPoint  -s -lkernel32 -luser32
При сборке у нас тоже достаточно мало изменений. Добавились флаг «-fno-exceptions», отключающий поддержку исключений в языке, а так же «-fno-rtti» отключающий так называемые RTTI-таблицы (runtime type information — таблицы, которые содержат информацию о типах данных в рантайме). Посмотрим, что получилось из этого:




Таким образом, мы получили такие же замечательные бинарнички, что и с помощью цэшного компилятора. Но давайте хотя бы частично помянем все те возможности Плюсов, которых мы лишились за счет нашего стремления к WinAPI-чистоте. Во-первых, все возможности, которые зависят от рантайма языка Цэ, приказали долго жить, это должно быть сразу понятно. Во-вторых, обработка исключений, которую в Плюсах не очень то и жалуют. Дело в том, что обработка исключений — очень сложная тема в Плюсах, многие компании, которые постоянно используют Плюсы, на полном серьезе запрещают своим программистам писать код с использованием исключений (как тот же Гуголь). Я сам пару раз напарывался на интересные «особенности», из-за которых просиживал ночами в обнимку с отладчиком, если интересно, как-нибудь расскажу. В-третьих, практически полностью отпадают STL и какие-то условные Boost’ы, поскольку они часто сильно завязаны на исключения и цэшный рантайм. Нет ну мы же стремимся к чистоте, чего хотели — того и получили. Может быть, что-то еще важного забыл, напишите в комментариях, если это так.

Теперь рассмотрим, какие дополнительные в сравнении с Цэ возможности мы получим, если будем писать на чистом WinAPI на Плюсах. Во-первых, концепции RAII и ООП полностью доступны (включая наследование и полиморфизм). Вы можете не использовать или даже бояться таких вещей, но для больших проектов эти концепции очень помогают в разработке. По идее, единственная вещь, которая не будет работать в ООП — операторы «new» и «delete», но, если они вам необходимы, то их можно реализовать засчет WinAPI функций «HeapAlloc» и «HeapFree» (если не понял, спроси меня как). Во-вторых, осталось, хоть и всрато-плюсовое, но метапрограммирование (включая генерики, constexpr и тд), с его помощью можно, например, реализовать хеширование или шифрование строк на этапе компиляции. В-третьих, Плюсы таки чуток строже, чем Цэ, поэтому, например, они могут отловить некоторые баги, связанные с константными и неконстантными указателями, на этапе компиляции. В четвертых, неймспейсы, ссылки, перегрузка операторов и другие приятные вещи.

4. Дэ

Дэ — своего рода удивительный язык, как можно было давным-давно сделать такой приятный на первый взгляд язык программирования, но при этом полностью просрать в популярности всем самым ужасным языкам современности? Но этот вопрос, наверное, опять из серии философии. Конечно, в Дэ тоже есть свои примеры плохого (на мой взгляд) дизайна, но для выходцев из долгих и проблемных отношений с Цэ или Плюсами этот язык программирования должен показаться очень хорошим. У языка Дэ есть три компилятора на выбор: DMD — основной компилятор, который поддерживают и развивают авторы языка, GDC — компилятор на базе GCC и LDC — компилятор на базе LLVM (последние два поддерживаются и развиваются за счет комьюнити языка). Ради интереса для наших с вами тестов я выбрал компилятор LDC, но при желании можно портировать эту тему и на другой компилятор, берем компилятор отсюда: https://github.com/ldc-developers/ldc/releases

В базовой «пачке» язык Дэ идёт со сборкой мусора, исключениями, RTTI-таблицами и кучей всего вредного для нашей WinAPI-чистоты. Однако, в определенный момент своей истории язык Дэ получил так называемый режим «betterC», в котором отсутствует весь этот высокоуровневый бред, но присутствует ряд приятных и полезных улучшений языка в сравнении с Цэ. В основном режиме язык Дэ зависит от стандартной библиотеки языка Цэ, собственной рантайм библиотеки, которая называется «druntime», и собственной стандартной библиотеки «phobos». В режиме «betterC» язык Дэ зависит только от стандартной библиотеки языка Цэ, которую мы с успехом отключим. Давайте посмотрим код:

Код:
C-подобный:

import core.sys.windows.winuser;
import core.sys.windows.winbase;

extern (C) void EntryPoint() {
    MessageBoxA(null, "Hello World", null, MB_OK);
    ExitProcess(0);
}

В языке Дэ уже есть готовые модули с определениями многих WinAPI-функций и типов данных, мы безусловно должны ими воспользоваться (многие из этих модулей повторяют иерархию заголовочных файлов MinGW, где и что находится довольно просто гуглиться). Точку входа, как и в случае с Плюсам, нам необходимо объявить как «extern C», чтобы избежать манглинга. Далее мы просто вызывает наши WinAPI функции ровно также, как мы делали в Цэ и в Плюсах. Очень просто.

Код:
Bash:

ldc2 -of=.\test32.exe -betterC -m32 -O3 --boundscheck=off --flto=full -L=/nodefaultlib -L=/entry:EntryPoint -L=/subsystem:console -L=kernel32.lib -L=user32.lib .\test.d
ldc2 -of=.\test64.exe -betterC -m64 -O3 --boundscheck=off --flto=full -L=/nodefaultlib -L=/entry:EntryPoint -L=/subsystem:console -L=kernel32.lib -L=user32.lib .\test.d
del .\test32.obj
del .\test64.obj
По-умолчанию, LDC попытается либо использовать линкер из состава установленной Visual Studio, либо будет использовать линкер из состава LLVM в режиме параметров, совместимом с мелкомягким линкером, так что не удивляйтесь структуре дополнительных параметров «-L», они прозрачно передаются линкеру. Параметр «/nodefaultlib» отключает стандартные библиотеки, «/entry» указывает точку входа, «/subsystem» указывает подсистему для исполняемого файла (для простоты мы все исполняемый файлы собираем с консольной подсистемой), «kernel32.lib» и «user32.lib» получают WinAPI-функции в нашу таблицу импорта. Параметры компилятора LDC тоже сравнительно похожи на параметры, которые мы передавали сишному и плюсовому компилятору. Параметр «--flto» включает оптимизации времени линковки, «-O3» указывает настройки оптимизации, «-betterC» включает режим «betterC», «--boundscheck» отключает проверки выхода за границы массива (таки Дэ побезопаснее язык чем Цэ и Плюсы, но многие такие проверки времени выполнения требуют наличия рантайм библиотеки, так как в случае ошибок будет вызван цэшный «assert»). Посмотрим, чего мы там насобирали:




Не, ну выглядит неплохо, согласитесь. В сравнении с обычным режимом языка Дэ от WinAPI-чистоты мы потеряли довольно много: сборщик мусора, классы (зависят от RTTI-таблиц и иерархии классов в рантайм библиотеки: все классы наследуются от одного базового класса object), исключения, конструкторы и деструкторы модулей и многое другое. Но при этом, осталась очень добрая и приятная часть фичей языка Дэ, которой нет в языке Цэ.

Самое для меня вкусное - во времени компиляции доступен весь язык Дэ, то есть метапрограммирование, хоть и не топовое (на базе AST-макросов), но можно делать многие очень интересные вещи. Есть возможность использовать вложенные функции и не переживать о порядке расположения функций в модуле. Есть делегаты и замыкания. Для структур доступны конструкторы, деструкторы, функции члены, перегрузка операторов - считайте это полноценным ООП и RAII (включая defer, который у них называется «scope(exit)»). Модули и более быстрая компиляция. Сравнительно легкое взаимодействие с COM (легче, чем в Цэ). Забавная фича юнит-тестов, которую разработчики малвари (включая меня) никогда не используют. Ну и так далее. Звучит не плохо, почему бы не попробовать, а?

5. Ним

Язык Ним при всех своих прелестях также, как и Дэ, по неизвестной мне причине давным давно посётся в середине между топом и анусом рейтингов популярности языков программирования. Думайте о нем, как о слегка странном Петухоне, компилируемым в нативный код, и не будете далеки от правды. Язык Ним хороший пример концепции абстракций над абстракциями, поскольку он компилируется в нативный код не напрямую. Сначала компилятор языка Ним генерирует Цэ-код, а затем кормит этим кодом цэшный компилятор. С одной стороны процесс компиляции от этого занимает больше времени, но с другой стороны код на языке Ним будет работать практически везде, где работает код на языке Цэ. Ставить компилятор само собой нужно с официального сайта языка: https://nim-lang.org/

На первый взгяд манера языка компилироваться в Цэ может сыграть нам на пользу, поскольку компилировать Цэ без стандартной библиотеки мы уже умеем, но тут не все так просто. Как и Дэ, язык Ним предлагает много функционала поверх языка Цэ. Это и несколько возможных моделей управления памяти (сборщик мусора или разные счетчики ссылок со сборкой циклов или без нее, ну или вообще без автоматического управления памятью), и большая стандартная библиотека с неочевидными новичку зависимостями между модулями и так далее и тому подобное. Я таки нашел способ отключить практически весь рантайм и стандартные библиотеки и языка Ним, и языка Цэ, но насколько далеко мы с вами сможем на этом ехать, пока мне не понятно. Попробуйте и напишите в комментариях, что у вас вышло.

Код:
Код:

proc rawoutput*(s: string) =
    discard

proc panic*(s: string) {. noreturn .} =
    while true: discard
Для начала нам нужно определить специальный файл с заглушками «panicoverride.nim». Эти функции будут вызываться в том случае, если в рантайме случится какая-то непоправимая ошибка, я просто забил хер на эту ситуацию, вы можете попробовать реализовать в нем обработку критических ошибок, если вам будет это интересно.

Код:
Код:

import winim

proc MessageBoxA(hwnd: int, txt: LPCSTR,  cap: LPCSTR,  but: int32) {. importc, header: "windows.h" .}
proc ExitProcess(code: int) {. importc, header: "windows.h" .}

proc entry_point() =
    MessageBoxA(0, "Hello World", nil, 0)
    ExitProcess(0)

entry_point()
В языке Nim есть специальная библиотека для работы с WinAPI-функциями и COM-объектами, которая ставится с помощью утилиты «nimble» и называется «winin». В ней есть определения почти всех WinAPI-функций на все случаи жизни, но функции эти зачастую определены через динамический импорт, требуют наличия соответствующих функций рантайма, а они в свою очередь требуют использовать сборщик мусора или счетчики ссылок (которые тоже реализованы в рантайме). Поэтом из библиотеки winim я взял только определения типов WinAPI, а статический импорт необходимых нам WinAPI-функций прописал руками (в языке Ним для этого нужно указать прототип функции, и из какого хедера языка Цэ она взята). Далее мы объявляем функцию точки входа и вызываем ее на топ уровне модуля.

Код:
Bash:

nim c --out:test32.exe --gcc.exe:i686-w64-mingw32-gcc   --gcc.linkerexe:i686-w64-mingw32-gcc   --cpu:i386  --define:danger --gc:arc --os:standalone --passL:-nostdlib --passL:-e_NimMain --noMain --forceBuild --passL:-s --passL:-lkernel32 --passL:-luser32 .\test.nim
nim c --out:test64.exe --gcc.exe:x86_64-w64-mingw32-gcc --gcc.linkerexe:x86_64-w64-mingw32-gcc --cpu:amd64 --define:danger --gc:arc --os:standalone --passL:-nostdlib --passL:-eNimMain  --noMain --forceBuild --passL:-s --passL:-lkernel32 --passL:-luser32 .\test.nim
Уф, а тут уже мы видим куда больше параметров. Параметр «--out» указывает имя выходного файла. Параметры «--gcc» говорят компилятору, какие компиляторы языка Цэ использовать (чтобы не морочиться, мы используем те же самые компиляторы языка Цэ, что мы уже поставили себе ранее). Параметр «--cpu» выбирает архитектуру процессора, а «--os» посылает нахер большую часть стандартной библиотеки (типа мы собираем baremetal-код без засисимости от сервисов операционной системы, такая наебочка получается). В качестве модели управления памятью я выбрал «--gcrc», так как библиотека winim не хочет работать с «--gc:none», возможно, в не настолько простой программе это бы мне аукнулось, но для этого примера подходит. Параметры «--passL» напрямую передают вложенные в них аргументы цэшному линкеру (их мы уже рассматривали ранее в разделе про Цэ). Я согласен, что выглядит сложно и работает на честном слове, но все же давайте посмотрим, что у нас получилось.

Хотя погодите, кода получилось слишком много, чтобы впихнуть скрины в статью, можете сами посмотреть в любом дизассемблере. Не то, чтобы слишком много, все-таки 4-5кб, но это уже не выглядит так красиво, как могло бы быть. Мы значительно перекрутили себе и языку Ним яйки, стремясь к WinAPI-чистоте, а результат не так радует. Ну да ладно, не всем же экспериментам быть удачными, так ведь?

Но давайте посмотрим, какие плюшки мы могли бы получить в сравнении с языком Цэ, если бы у нас получилось привести язык Ним к WinAPI-чистоте в более удобоваримом виде (можете попробовать сами, если получится, то напишите об этом в комментариях). У языка Ним очень мощная система метапрограммирования (на базе AST-макросов), с помощью которой можно делать чумовые вещи, например, как я показывал тут: https://xss.is/threads/64508/ - то, что такое возможно, удивило даже меня самого. В языке даже без автоматического менеджмента памяти есть defer и своебразный ООП и RAII, что сразу делает программирование проще. Можно перегружать операторы и многое многое другое.

6. Васёк

Не просто Васёк, а халявный Васек (он же FreeBasic). Если Дэ и Ним в рейтингах языков программирования живут где-то в серединах, то Васяк сидит очень глубоко в анусе этих рейтингов. Я со времен Спектрумов на Васьках не программировал, но мне просто нужен был какой-то язык, чтобы вас удивить. Я не говорю, что это чем-то плохой язык, но, к сожалению, в современном мире смотреть в его сторону будут только дикие нонконформисты или фанаты Васьков со времен каких-то старых железок (ну или малварщики, которые хотят очень удивить аверов своим нестандартным выбором языка). Компилятор мы возьмем с официального сайта: https://www.freebasic.net/

У всех Васьков традиционно большой набор команд, ну или ключевых слов, как это принято называть в их мирке. Многие из них зависят от стандартной библиотеки, отключая которую мы их лишаемся. В случае с халавным Васьком большая часть этих вещей сделана в стандартной библиотеке кодом на языке Цэ. Халявный Вася может компилировать свой код в Цэ или в LLVM IR, который (как Ним) в последствии компилирует уже либо GCC, либо инфраструктурой LLVM. Потыкать в LLVM, конечно, интересно, но мы будем продолжать пользоваться нашими GCC компиляторами для простоты. Давайте посмотрим на исходный код:

Код:
Код:

#include once "windows.bi"

declare sub EntryPoint()

sub EntryPoint()
    MessageBox(0, "Hello World", 0, MB_OK)
    ExitProcess(0)
end sub
Мы подключаем заголовочный файл «windows.bi», который является по сути местным аналогом «windows.h» из мира Цэ и Плюсов. Далее мы определяем точку входа и просто в ней вызываем наши WinAPI-функции. Просто и лаконично, как и в Цэ, Плюсах и Дэ.

Код:
Bash:

fbc32 -gen gcc -c -o .\test32.o .\test.bas
fbc64 -gen gcc -c -o .\test64.o .\test.bas
i686-w64-mingw32-gcc   -o .\test32.exe -nostdlib -s .\test32.o -lkernel32 -luser32
x86_64-w64-mingw32-gcc -o .\test64.exe -nostdlib -s .\test64.o -lkernel32 -luser32
del .\test32.o
del .\test64.o
Сначала мы компилируем исходники на Ваське в объектные файлы с помощью кодогенератора GCC (параметр «-gen»), а затем линкуем объектные файлы аналогично тому, как мы делали с Цэ и Плюсами, тут должно быть уже все понятно. И вот, что у нас вышло:




Похоже на Цэ, ведь так? Давать комментарии по поводу того, чем халявный Васек был бы лучше или хуже, чем Цэ, наверное, я не смогу, так как практически ничего про него не знаю, а времена, когда я хоть что-то писал на Васьках, давно прошли. Но замечу, что большое число команд Васька будут недоступны в режиме чистого WinAPI и об этом нужно помнить.

7. Зиг

Или Заг? Или Захуй я вообще за него взялся? Нет все-таки Зиг. Я не большой фанат Зига, однако он (наряду с Одином) считается чуть ли не единственной современной альтернативой православной Цэшке (поскольку всякие Расты, Нимы, Дэ — это все же языки, призванные заменить именно Плюсы в их сфере). Зиг по идее может не зависеть от рантайма языка Цэ, но при этом он еще далек от версии 1.0, и регулярно что-то меняет в себе в процессе разработки. Пока что, я бы не рекомендовал его пробовать, хотя бы до того момента, когда ядро языка в достаточной степени не стабилизируется. Но для этой обзорной статьи я все-таки решил его потыкать и вот, что у меня получилось.

Код:
Код:

const std = @import("std");

extern "user32" fn MessageBoxA(hwnd: ?std.os.windows.HANDLE, txt: ?std.os.windows.LPCSTR, cap: ?std.os.windows.LPCSTR, but: std.os.windows.UINT) c_int;

pub fn main() void {
    _ = MessageBoxA(null, "Hello World!", null, 0);
    std.os.windows.kernel32.ExitProcess(0);
}
Ох, божечки, сколько непонятных букаф. Именно поэтому я Зиг и не очень люблю. В самом верху мы подключаем стандартную библиотеку, но только для того, чтобы достать оттуда определения WinAPI-типов. Потом мы объявляем внешнюю функцию «MessageBoxA», определения которой в стандартной библиотеки у меня достать почему-то не вышло. Затем объявляем точку входа в которой вызываем и мессаджбокс и экситпроцесс, аналогично, как мы делали с другими языками.

Код:
Bash:

zig build-exe -target i386-windows-gnu   --name test32 -O ReleaseSmall --library user32 .\test.zig
zig build-exe -target x86_64-windows-gnu --name test64 -O ReleaseSmall .\test.zig
del .\test32.exe.obj
del .\test64.exe.obj
А вот тут у меня началась боль. По неведомой мне причине мне не удалось заставить Зиг собрать мне такой исполняемый файл под x86 (возможно, это баг в компиляторе, он не хочет находить libuser32.a, даже если ему подпихивать ее в параметрах). Если у вас это выйдет, напишите в комментариях, как, я почитаю. Поскольку Зиг не зависит от стандартных цэшных библиотек, особо много параметров указывать нам не понадобилось. Параметр «-target» указывает под какую операционную систему и процессор нужно собирать, «--name» определяет имя исполняемого файла, а «ReleaseSmall» включает оптимизации по размеру. Теперь посмотрим 64-битный исполняемый файл, который мы получили:



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

Язык Зиг очень близок по концепции к православной Цэшке. Преимуществ не так то и много. Например, можно не переживать о расположении функций в модуле, или посчитать что-то на этапе компиляции (но метапрограммирования, как и в Цэ, по сути нет). В общем, хотите использовать — пожалуйста, но я бы не рекомендовал, как минимум на сегодняшний день.

8. Заключение

Ну чтож, друзья, на мой взгляд у нас с вами получилось занимательное приключение. Я хотел написать небольшую и интересную статью, но опять вышло овер 30к букав. Надеюсь, что кому-то из вас хватит сил дочитать мою статью до конца. Если так, спасибо, за ваше внимание. Если вас интересует мое мнение, то в плане конкретно WinAPI-чистоты мне больше всего понравилось работать с Дэ. Более того, я считаю, что Дэ будет наиболее простым и приятным выбором, если вы много писали на Цэ и Плюсах раньше, но хотите попробовать что-то новое.

Я намерено опустил язык Раст в пользу более незаметных языков. Конечно, концепции в языке Раст интересные, но в плане WinAPI-чистоты необходимость оборачивать все WinAPI-вызовы в unsafe-блоки не вызывает энтузиазма. К тому же, о том, как использовать Раст, без стандартных библиотек было написано и до меня. Если у вас появился интерес к этой теме, попробуйте проделать тоже самое с другими языками (хорошими кандидатами для этого, возможно, станут FreePascal, Odin, Beef и другие нативные языки). Если у вас что-то дельное из этого выйдет, не забудьте поделиться своим опытом в комментариях к этой статье. Спасибо! Если хотите что-то спросить по теме, не стесняйтесь.

Автор: DildoFagins

Скрытый текст, необходимо выполнение следующих условий: [hide=15]
Иметь 56 сообщений(ие) (56 осталось)
Иметь 38 спасиб(о) (38 осталось)
Иметь 2 очко(а,ов) репутации (2 осталось)
Давность регистрации на форуме не менее75 дней назад
  Ответить с цитированием
Опции темы
Опции просмотра