В интервюто талантливият словак ни разказа как успя да стартира приложение C # на стария Windows 3.11.

успех

Днес Windows 3.0 празнува точно 30 години от старта си. Както подсказва номерът на версията, това беше третото основно, но осмо издание на най-популярната днес настолна операционна система.

Всъщност в началото на 90-те все още не беше пълноценна операционна система - Windows беше „просто“ графична среда, която все още се основаваше на MS-DOS.

Това не се промени в непосредствено следващите версии. MS-DOS формира основата за Windows през следващите няколко години, включително версията на Windows 3.11 от края на 1993 г. Споменаваме това поради успеха на словашкия програмист Михал Стреховски.

На почти 30-годишния Windows 3.11 той успя да стартира приложение, написано на програмния език C #. Това е интересно, защото този език възниква около 7 години по-късно. По това време Microsoft вече се занимаваше с версията на Windows 2000 или легендарния Windows XP. И в двата случая те бяха отделни операционни системи без база на MS-DOS.

Освен това C # възниква по времето на 32-битовите операционни системи. Той никога не е проектиран да изпълнява своите програми на 16-битови операционни системи. И точно това е Windows 3.11.

В интервюто ще прочетете какво вдъхнови Михал Стреховски, каква мотивация имаше и какво трябваше да преодолее по време на работа.

Михал Стреховски Той работи в Microsoft от почти 10 години, но не успява по време на работата си, а като хоби в свободното си време. Учи компютърни науки в университета Масарик. В миналото той допринасяше за Živé.sk - той писа за нас уникална серия за защита от пукнатини - а преди това се занимаваше и с книги. В миналото той стоеше зад софтуера за промяна на скритите настройки на Windows, наречен SysTuner, който разработваше до 2004 г.

Нека започнем с това как сте придобили знанията си. Интересувате ли се от програмиране от детството?

Влязох в програмирането чрез баща си. Баща му е машинен инженер и по това време използва компютър Sinclair ZX Spectrum за работата си. Понякога го прибираше от работа и освен игри ми показваше и кратка програма, която написа, която ми даваше математически примери за решения. Въпреки това, повече от решаването на примерите, ме интересуваше как работи програмата.

Сама научих много неща в свободното си време. Просто бях любопитен и исках да разбера как работят компютрите. Никой не ми заповяда да ги изуча, нито той ми даде ресурси за тях.

Освен това достъпът до интернет е бил силно ограничен в миналото. Родителите ми ми позволиха максимум час на седмица. Така че по време на този урок намерих няколко източника по тази тема и след това ги изучавах цяла седмица заедно с училището.

Вдъхнових се и от две книги за крекинг, издадени по това време.

Точно?

Човек трябва да разгледа инструкциите на процесора и да разбере какво прави. Той също така трябва да възприема как програмата взаимодейства „със света“ и операционната система, на която работи. Наслаждавам се на това.

Прекарах последните 7 години в програмиране на виртуална машина, работеща с .NET. През това време човек също научава нещо. Така че комбинирах хобито си с това, което правя, или върших като работа в Microsoft.

Като свой собствен проект създадох библиотека, която разработчикът може да добави към приложението си и по този начин да получи известна степен на защита срещу напукване.

Така че се опитахте да се справите със самото напукване?

Опитах се да играя с него в гимназията. Като свой собствен проект създадох библиотека, която разработчикът може да добави към приложението си и по този начин да получи известна степен на защита срещу напукване.

Например някои от неговите функции успяха да бъдат открити, когато потребител се опита да стартира приложение в дебъгера. Дебъгерът е инструмент, който програмистите, както и бисквити и хакери, използват за анализ на приложения и проследяване на данни в паметта или инструкции, които изпращат на процесора. Крекерите могат да го използват, за да идентифицират уязвимости и да заобикалят защитите, приложени от разработчика на приложението.

Естествено, аз също се опитах да продам тази библиотека, предполагам, че определих цената на 20 долара. Няколко души дори го купиха, но аз не спечелих дори минимум от него за подаване на данъчна декларация.

Гимназист без контакти обаче ще се справи трудно. Но натрупах ценен опит от него. Също така съм почти сигурен, че това ми осигури настоящата ми работа. Когато работодателят видя какво съм правил в свободното си време, аз автоматично му бях по-интересен.

Нека разгледаме как стартирахте приложение C # на стария Windows 3.11. Това беше някак свързано с факта, че работите за Microsoft?

Това е едно от нещата, които играя през почивните дни. Това не беше свързано по никакъв начин с работата ми в Microsoft, това беше изключително моята развлекателна дейност. Мислил съм да получа C # до места, където не е стигал преди и никой не го използва. Имах няколко експеримента, най-известният от които вероятно беше този, когато успях да получа C # на Windows 3.11.

Тази интересна производителност е, защото Windows 3.11 беше пуснат през 1991 г. Първата версия на C # пристигна едва около 10 години по-късно. Дори разработчиците на C # не мислеха, че този език за програмиране един ден ще попадне във вече остарялата и неизползвана система.

Какво прави C # специфичен?

Това беше предшествано от няколко стъпки. По принцип C # работи по различен начин от езиците за програмиране C или C ++. Тези езици за програмиране са "традиционни" в смисъл, че написаният в тях код се превежда директно в машинен код за архитектурата на целевия процесор и операционната система по време на обработката. Следователно, ако създадете програма за x86 Windows от код в C ++, няма да я стартирате на ARM64 телефон с Android.

C # е по-скоро като Java или Kotlina в това отношение. Когато компилирате кода в C #, не получавате инструкции за „разбиране“ на някакъв вид физически процесор. За да ги стартирате, ви е необходим виртуален процесор. Не можете да си купите такъв в магазина така. Виртуалният процесор е среда за изпълнение, чрез която вашият конкретен процесор и операционна система могат да изпълняват програмна семантика в C #.

Кои операционни системи C # поддържат?

Ако имате Windows Vista или по-нова версия, вече имате този виртуален процесор. Той е част от операционната система.

През 2014 г. Microsoft обяви наследник с отворен код на .NET Framework, наречен .NET Core. През 2016 г. той разшири „официалния“ .NET с други поддържани платформи: Linux и macOS. В същото време отварянето на изходния код позволи на всеки да добави поддръжка за други операционни системи и процесори.

Microsoft също основава .NET Foundation. Не се контролира от Microsoft. Проектите, свързани с .NET Core, бяха преместени под него, така че общността вече да не възприема платформата като затворена и принадлежаща на една компания. .NET искаше да се качи на възможно най-много устройства и платформи.

Що се отнася до моя експеримент с C # и Windows 3.11, използвах съществуваща .NET Core технология с отворен код. Но трябваше да ги модифицирам във форма, която се поддържа и в Windows 3.11. Това не е нещо, което има голямо приложение на практика. Направих много преки пътища в прототипа, така че определено не е всичко .NET на Windows 3.11.

Целта ми беше да разбера наистина минималните изисквания за стартиране на C # код.

Как стигнахте конкретно до Windows 3.11? Приехте го като предизвикателство?

Windows 3.11 не беше целта в началото. Започнах, като се опитах да определя минималните изисквания за стартиране на C # код. Веднага стана ясно, че минималните изисквания се определят от средата на изпълнение. Дори най-простите и примитивни приложения, които отпечатват само число или низ, се нуждаят от тази виртуална машина, за да разберат техните инструкции.

На този етап си помогнах с експериментален проект на Microsoft, публикуван също под лиценз с отворен код. Това е алтернативна среда за изпълнение. Не с какво .NET Core официално работи.

Предимството му е, че освен виртуалната машина, тя съдържа и друг компилатор. Той може да преведе C # кода директно в машинен код, „разбираем“ за избраната хардуерна платформа и операционна система. Подобно на често превеждания изходен код на C или C езици++.

След това кодът, написан на C #, се превежда два пъти. Времетраенето изпълнява инструкции за виртуалния процесор от програмните редове в C #, написани от програмиста. Друг компилатор превръща тези виртуални инструкции в машинен код за определена избрана платформа, като x86 Windows. Казано по-неспециалистично, той го превежда на „език“, разбираем от физическия процесор в компютъра.

И самият .NET Core не знае това?

Също така е възможно да се създаде „самостоятелно приложение“ в .NET Core. Той реализира внедряването на виртуален процесор за конкретна операционна система и "носи процесора". Следователно той вече не изисква специална поддръжка за .NET в операционната система. Внедряването на този виртуален процесор обаче е относително широко.

Експерименталният проект, за който говоря, от друга страна пропуска тази среда при компилиране. Както казах, вместо това той превежда написаното приложение директно в естествен код за избрания процесор.

Като пропуска изпълнението на .NET, езикът губи едно от предимствата си пред статично преведените езици като C и C ++. Възможно е да се генерира нов програмен код по време на изпълнението на програмата. Е, видях го като много добра начална стъпка. Успях да елиминирам първия "ненужен" компонент от целия процес, т.е. условието за използване на виртуална машина и среда за изпълнение.

Това обаче не беше достатъчно?

Не. За да сведем до минимум изискванията, се фокусирах върху отделните функции на езика C # и неговата среда на изпълнение. Предимството на споменатия експериментален проект е именно в това, че отделните компоненти на средата на изпълнение не са задължителни. Програмистът може да "щракне" върху това, което иска да изключи. Това не се поддържа много, но е налице и работи.

Затова направих една напълно проста игра с такава змия. Проектирах го така, че да зависи от възможно най-малко компоненти на средата на изпълнение и бих могъл да заменя всички ненужни.

Първо започнах да се занимавам със „събирача на боклук“, т.е. автоматизирано управление на паметта. Отново, това е област, в която C # е по-близо до Java от традиционния C.

В обикновения C, преди да използва променлива, програмистът трябва да поиска място в паметта за нея, като я разпредели. Напротив, когато вече не се нуждае от него, той не трябва да забравя да освободи паметта. За по-големи проекти това е огромен проблем и разработчикът може лесно да се загуби в управлението на паметта. След това приложението използва ненужно много ресурси, срива или по друг начин не работи правилно.

И така, как работи C #?

В C # програмистът не трябва да иска ръчно и да освобождава памет. Средата на изпълнение (виртуален процесор) изпълнява тези задачи интелигентно и според нуждите. Разработчикът обаче има по-малък контрол върху системните ресурси. Самият код обаче е много по-малко сложен и по-лесен за диагностициране в случай на проблеми.

„Събирачът на боклук“ е относително сложен код. Проследява кога дадена програма е осъществила последен достъп до паметта и т.н. Но бях очарован, защото теоретично можеше да бъде "изхвърлен". Просто ще трябва сам да управлявам паметта отново.

Направих го и със системата за вход и изход от програмата, която замених със собствената си малка реализация. Постепенно замених всичко с по-опростени реимплементации, които имат по-малко зависимости от операционната система.

В резултат получих приложение със змия, написана на C #, което беше само 8 килобайта по размер и беше напълно независимо от средата на изпълнение.

Какъв беше резултатът?

Ето как „играх“ с него няколко уикенда. В резултат на това получих игра с тази змия, написана на C #, която беше с размер само 8 килобайта и беше напълно независима от работещата среда. Цялата виртуална машина стана ненужна. Всяка команда и байт от моя изходен код бяха директно преведени и проследени в получения файл .EXE.

Направих игра на змия за текстов режим („конзолно приложение“). Това не беше сложно, тъй като повечето операционни системи, включително Windows, имат функции за четене и запис в конзолата. Те могат да бъдат извикани и от C #. Проблемът обаче беше, че тези функции бяха добавени към Windows дълго след Windows 3.11.

Затова трябваше да коригирам целите си за Windows 3.11. Вместо да си играя със змия, се опитах да покажа само обикновено текстово съобщение с два бутона. Въпреки това, аз използвах някои змийски градивни елементи за това. На най-новия Windows 10 подобно приложение работеше без проблеми веднага, но със стария Windows 3.11 все още нямах късмет.

Нямаше проблеми с превода на приложението в естествен код?

Трябваше да се справя с ограниченията на експерименталния проект, с който съм работил от самото начало. Работи само на 64-битови операционни системи. Няма да работи на 32- или дори 16-битова система.

Тук попаднах на историческо любопитство. Windows 3.11 беше 16-битова операционна система, но с въвеждането на следващия 32-битов Windows 95, Microsoft направи малка добавка, наречена Win32s за тази по-стара система.

Win32s позволи някои леки и малки 32-битови приложения да работят на 16-битов Windows 3.11. Следователно нямаше нужда да се занимавам с прехода от 64 бита към 16, а „само“ 32. Win32s ми послужи като много добра основа, тъй като по това време той беше поддържан и интегриран в различни среди за разработка.

Преди да преминем към последната стъпка, добре е да споменем как работи преводът на програми в роден код. Родният компилатор на код генерира така наречените "обектни файлове". Тези файлове съдържат родния код на програмата, но не са изпълними, тъй като не са пълни. Те съдържат препратки към други символи, дефинирани в други обектни файлове.

За създаване на изпълним файл се използва инструмент, наречен линкер, който комбинира няколко обектни файла в една изпълнима единица.

Компилирах кода си с помощта на компилатор 2020 в обектни файлове. След това ги свързах с линкер от 1994г.

Как използвахте тази "междинна стъпка"?

Преведох приложението си в обектни файлове, използвайки споменатия експериментален проект. Получаването на 32-битова продукция от експериментален компилатор не беше такъв проблем, защото .NET Core поддържа 32-битови операционни системи. Използваният експериментален проект се основава на .NET Core. Програмирах останалите няколко малки детайла в експериментален компилатор. Също така ги интегрирах обратно в проекта, за да ги направя достъпни за другите.

Впоследствие използвах link.exe от 1994 г. като линкер.Програмата ми беше примитивна и без никакви зависимости, но все пак бях изненадана, че е съвместима със стария линкер. Форматът на файла обаче не се е променил много оттогава, така че линкерът не е имал и най-малък проблем с него. И тъй като той дойде по същото време като Windows 3.11, той свърза кода ми с други обектни файлове, които дойдоха от точното време.

Получената програма все още работи и може да се изпълнява на Windows 10. Но тя работи и на Windows 3.11, който вече е на повече от 26 години.

По-късно дори успях да пусна играта на MS-DOS 5.

Не сте се опитали да отидете "по-нататък"?

По-късно дори успях да пусна играта на MS-DOS 5. Но сега нямам време за подобни партита. Тогава бях в родителски отпуск, така че имах малко повече време. Сега, освен детето, трябва да управлявам и работата.

Други 16-битови системи или платформи, по-стари от Windows 3.11, ще изискват повече работа, отколкото съм готов да инвестирам. В Windows 3.11 предимството беше инструментът Win32s. В противен случай би трябвало да започна да се занимавам например със самия генератор на роден код и това би било много повече работа. Не би било невъзможно, просто много време.

Междувременно един от сътрудниците на експерименталния .NET проект взе моята игра и я преработи да работи без операционна система. Екипът всъщност доказа, че C # може да се използва и за създаване на самите операционни системи.