În interviu, slovacul talentat ne-a spus cum a reușit să ruleze o aplicație C # pe vechiul Windows 3.11.

succes

Astăzi, Windows 3.0 sărbătorește exact 30 de ani de la lansare. După cum sugerează numărul versiunii, a fost a treia ediție majoră, dar a opta, a celui mai popular sistem de operare desktop de astăzi.

De fapt, la începutul anilor 1990 nu era încă un sistem de operare cu drepturi depline - Windows era „doar” un mediu grafic care se baza încă pe MS-DOS.

Acest lucru nu sa schimbat în versiunile imediat următoare. MS-DOS a constituit baza pentru Windows în următorii câțiva ani, inclusiv versiunea Windows 3.11 de la sfârșitul anului 1993. Menționăm acest lucru datorită succesului programatorului slovac Michal Strehovský.

Pe Windows 3.11, vechi de aproape 30 de ani, a reușit să ruleze o aplicație scrisă în limbajul de programare C #. Acest lucru este interesant, deoarece acest limbaj a apărut aproximativ 7 ani mai târziu. La acea vreme, Microsoft se ocupa deja de versiunea Windows 2000 sau legendarul Windows XP. În ambele cazuri, acestea erau sisteme de operare separate, fără o bază pe MS-DOS.

În plus, C # își are originea în zilele sistemelor de operare pe 32 de biți. Nu a fost niciodată conceput pentru a-și rula programele pe sisteme de operare pe 16 biți. Și exact asta este Windows 3.11.

În interviu, veți citi ce l-a inspirat pe Michal Strehovský, ce motivație a avut și ce a trebuit să depășească la locul de muncă.

Michal Strehovský Lucrează la Microsoft de aproape 10 ani, dar nu a reușit în timpul muncii sale, ci ca hobby în timpul liber. A studiat informatica la Universitatea Masaryk. În trecut a contribuit la Živé.sk - a scris pentru noi o serie unică de protecție împotriva fisurilor - și înainte s-a ocupat și de cărți. În trecut, el a fost în spatele software-ului pentru schimbarea setărilor ascunse Windows, numit SysTuner, pe care l-a dezvoltat până în 2004.

Să începem cu modul în care ți-ai dobândit cunoștințele. Te-a interesat programarea încă din copilărie?

Am început să programez prin tatăl meu. Tatăl meu era inginer mecanic și la acea vreme folosea un computer Sinclair ZX Spectrum pentru munca sa. Uneori îl aducea acasă de la serviciu și, pe lângă jocuri, îmi arăta și un scurt program pe care l-a scris, care îmi dădea exemple matematice pentru soluții. Cu toate acestea, mai mult decât rezolvarea exemplelor, m-a interesat cum funcționează programul.

Am învățat eu multe lucruri în timpul liber. Eram doar curios și voiam să știu cum funcționează computerele. Nimeni nu mi-a ordonat să le studiez și nici nu mi-a dat resurse pentru ele.

În plus, accesul la internet a fost sever limitat în trecut. Părinții mei mi-au permis maximum o oră pe săptămână. Așa că am găsit câteva resurse pe această temă în timpul acelei lecții și apoi le-am studiat toată săptămâna alături de școală.

De asemenea, am fost inspirat de două cărți despre cracking care au fost publicate la acea vreme.

Exact?

Trebuie să te uiți la instrucțiunile procesorului și să înțelegi ce fac. De asemenea, trebuie să perceapă modul în care programul interacționează „cu lumea” și sistemul de operare pe care rulează. Îmi place asta.

Am petrecut ultimii 7 ani programând o mașină virtuală care rulează .NET. În acel timp, se învață și ceva. Așa că mi-am combinat hobby-ul cu ceea ce fac, sau am făcut-o ca slujbă la Microsoft.

Ca proiect propriu, am creat o bibliotecă pe care dezvoltatorul ar putea să o adauge la aplicația sa și astfel să obțină un anumit grad de protecție împotriva crăparii.

Așa că ai încercat să te descurci cu crăparea?

Am încercat să mă joc cu el în liceu. Ca proiect propriu, am creat o bibliotecă pe care dezvoltatorul ar putea să o adauge la aplicația sa și astfel să obțină un anumit grad de protecție împotriva crăparii.

De exemplu, unele dintre caracteristicile sale au putut fi detectate atunci când un utilizator a încercat să ruleze o aplicație în depanator. Un depanator este un instrument pe care programatorii, precum și crackerii și hackerii, îl folosesc pentru a analiza aplicațiile și a urmări datele din memorie sau instrucțiunile pe care le trimit procesorului. Crackerii îl pot folosi pentru a identifica vulnerabilitățile și a ocoli protecțiile implementate de dezvoltatorul aplicației.

Firește, am încercat și eu să vând biblioteca respectivă, cred că am stabilit prețul la 20 de dolari. Câțiva oameni chiar l-au cumpărat, dar nu am câștigat nici măcar un minim din acesta pentru depunerea unei declarații fiscale.

Cu toate acestea, unui elev de liceu fără contacte îi va fi greu să facă față. Dar am câștigat o experiență valoroasă din aceasta. De asemenea, sunt aproape sigur că acest lucru mi-a oferit slujba mea actuală. Când angajatorul a văzut ce am făcut în timpul liber, am fost automat mai interesant pentru el.

Să vedem cum ați rulat o aplicație C # pe vechiul Windows 3.11. A fost cumva legat de faptul că lucrați pentru Microsoft?

Acesta este unul dintre lucrurile pe care le joc în weekend. Nu era legat în niciun fel de munca mea la Microsoft, era exclusiv activitatea mea de agrement. M-am gândit să duc C # în locuri unde nu a ajuns până acum și nimeni nu o folosește. Am avut mai multe experimente, dintre care cel mai faimos a fost probabil cel în care am reușit să obțin C # pe Windows 3.11.

Această performanță interesantă se datorează faptului că Windows 3.11 a fost lansat în 1991. Cu toate acestea, prima versiune a C # nu a sosit decât după aproximativ 10 ani. Chiar și dezvoltatorii C # nu au crezut că acest limbaj de programare va intra într-o zi în sistemul deja învechit și neutilizat.

Ce face specific C #?

Aceasta a fost precedată de mai mulți pași. C # funcționează, în principiu, diferit de limbajele de programare C sau C ++. Aceste limbaje de programare sunt „tradiționale” în sensul că codul scris în ele este tradus direct în codul mașinii pentru arhitectura țintă a procesorului și sistemul de operare în timpul procesării. Prin urmare, dacă creați un program pentru Windows x86 din cod în C ++, nu îl veți rula pe un telefon Android ARM64.

C # seamănă mai mult cu Java sau Kotlina în această privință. Când compilați codul în C #, nu primiți instrucțiuni pentru a „înțelege” un fel de procesor fizic. Ai nevoie de un procesor virtual pentru a le rula. Nu poți cumpăra una în magazin așa. Un procesor virtual este un mediu de rulare prin care procesorul și sistemul dvs. de operare pot efectua semantica programului în C #.

Ce sisteme de operare C # acceptă?

Dacă aveți Windows Vista sau o versiune ulterioară, aveți deja acest procesor virtual. Face parte din sistemul de operare.

În 2014, Microsoft a anunțat succesorul open-source al .NET Framework numit .NET Core. În 2016, a extins .NET „oficial” cu alte platforme acceptate: Linux și macOS. În același timp, deschiderea codului sursă a permis oricui să adauge suport pentru alte sisteme de operare și procesoare.

Microsoft a fondat, de asemenea, Fundația .NET. Nu este controlat de Microsoft. Proiectele legate de .NET Core au fost mutate sub aceasta, astfel încât comunitatea să nu mai perceapă platforma ca fiind închisă și aparținând unei singure companii. .NET dorea să ajungă pe cât mai multe dispozitive și platforme posibil.

În ceea ce privește experimentul meu cu C # și Windows 3.11, am folosit tehnologia open source existentă .NET Core. Dar a trebuit să le modific într-un formular acceptat și în Windows 3.11. Nu este ceva care are o aplicație excelentă în practică. Am făcut o mulțime de comenzi rapide în prototip, deci cu siguranță nu este totul .NET pe Windows 3.11.

Scopul meu a fost să aflu cu adevărat cerințele minime pentru rularea codului C #.

Cum ați ajuns în mod specific la Windows 3.11? Ai luat-o ca pe o provocare?

Windows 3.11 nu a fost scopul la început. Am început încercând să stabilesc cerințele minime pentru rularea codului C #. A fost imediat clar că cerințele minime sunt determinate de mediul de rulare. Chiar și cele mai simple și primitive aplicații, care imprimă doar un număr sau un șir, au nevoie de această mașină virtuală pentru a le înțelege instrucțiunile.

În această etapă, m-am ajutat la un proiect experimental Microsoft, publicat și sub licență open-source. Acesta este un mediu alternativ de execuție. Nu cu ce .NET Core funcționează oficial.

Avantajul său este că, pe lângă mașina virtuală, conține și un alt compilator. Poate traduce codul C # direct în codul mașinii „de înțeles” pentru platforma hardware selectată și sistemul de operare. Similar cu codul sursă tradus în mod curent în limbile C sau C++.

Codul scris în C # este apoi tradus de două ori. Runtime execută instrucțiuni pentru procesorul virtual din liniile de program în C # scrise de programator. Un alt compilator transformă aceste instrucțiuni virtuale în cod de mașină pentru o anumită platformă selectată, cum ar fi Windows x86. În termeni laici, el îl traduce în „limbaj” înțeles de procesorul fizic din computer.

Și .NET Core în sine nu știe asta?

De asemenea, este posibil să creați o „aplicație independentă” în .NET Core. Implementează implementarea unui procesor virtual pentru un anumit sistem de operare și „transportă procesorul”. Prin urmare, nu mai necesită suport dedicat pentru .NET în sistemul de operare. Cu toate acestea, implementarea acestui procesor virtual este relativ extinsă.

Proiectul experimental despre care vorbesc, pe de altă parte, omite acest mediu la compilare. După cum am spus, în schimb, traduce aplicația scrisă direct în cod nativ pentru procesorul selectat.

Prin omiterea timpului de execuție .NET, limba își pierde unul dintre avantajele față de limbile traduse static, cum ar fi C și C ++. Este posibil să generați un nou cod de program în timpul rulării programului. Ei bine, l-am văzut ca un pas de pornire foarte bun. Am reușit să elimin prima componentă „inutilă” din întregul proces, adică condiția utilizării unei mașini virtuale și a unui mediu de rulare.

Totuși, nu a fost suficient?

Nu. Pentru a minimiza cerințele, m-am concentrat în continuare pe funcțiile individuale ale limbajului C # și mediul său de rulare. Avantajul proiectului experimental menționat este tocmai faptul că componentele individuale ale mediului de rulare nu sunt obligatorii. Programatorul poate „face clic” pe ceea ce vrea să excludă. Acest lucru nu este foarte susținut, dar este acolo și funcționează.

Așa că am făcut un joc complet simplu cu un astfel de șarpe. L-am proiectat pentru a depinde de cât mai puține componente ale mediului de execuție posibil și aș putea înlocui cele inutile.

Mai întâi am început să mă ocup de „colectorul de gunoi”, adică gestionarea automată a memoriei. Din nou, aceasta este o zonă în care C # este mai aproape de Java decât C tradițional.

În C obișnuit, înainte de a utiliza o variabilă, programatorul trebuie să solicite spațiu de memorie pentru aceasta, alocând-o. Dimpotrivă, atunci când nu mai are nevoie de el, nu trebuie să uite să elibereze memoria. Pentru proiecte mai mari, aceasta este o problemă uriașă, iar dezvoltatorul se poate pierde cu ușurință în gestionarea memoriei. Aplicația folosește apoi inutil multe resurse, se blochează sau altfel nu funcționează corect.

Deci, cum funcționează C #?

În C #, programatorul nu trebuie să solicite manual și să elibereze memorie. Mediul de execuție (procesor virtual) face aceste sarcini în mod inteligent și după cum este necesar. Cu toate acestea, dezvoltatorul are mai puțin control asupra resurselor sistemului. Cu toate acestea, codul în sine este mult mai puțin complicat și mai ușor de diagnosticat în caz de probleme.

Un „colector de gunoi” este o bucată de cod relativ complicată. Urmărește ultima dată când un program a accesat memoria și așa mai departe. Dar am fost fascinat pentru că teoretic putea fi „aruncat”. Va trebui doar să gestionez din nou memoria.

Am făcut-o și cu sistemul de intrare și ieșire din program, pe care l-am înlocuit cu propria mea implementare mică. Treptat, am înlocuit totul cu reimplementări mai simple, care au mai puține dependențe de sistemul de operare.

Drept urmare, am primit o aplicație cu un șarpe scris în C #, care avea o dimensiune de doar 8 kilobyți și era complet independent de mediul de rulare.

Care a fost rezultatul?

Așa am „jucat” cu el câteva săptămâni. Drept urmare, am primit un joc cu șarpele scris în C #, care avea doar 8 kilobyți în mărime și era complet independent de mediul de rulare. Întreaga mașină virtuală a devenit inutilă. Fiecare comandă și octet din codul meu sursă au fost traduse și urmărite direct în fișierul .EXE rezultat.

Am făcut un joc de șarpe pentru modul text („aplicație consolă”). Acest lucru nu a fost complicat, deoarece majoritatea sistemelor de operare, inclusiv Windows, au funcții de citire și scriere pe consolă. Ele pot fi apelate și din C #. Cu toate acestea, problema a fost că aceste caracteristici au fost adăugate la Windows mult timp după Windows 3.11.

Așa că a trebuit să-mi ajustez obiectivele pentru Windows 3.11. În loc să mă joc cu un șarpe, am încercat să afișez doar un simplu mesaj text cu două butoane. Cu toate acestea, am folosit câteva blocuri de construcție șarpe pentru acest lucru. Pe cel mai recent Windows 10, o astfel de aplicație a funcționat fără probleme imediat, dar cu vechiul Windows 3.11 am avut încă ghinion.

Nu au existat probleme la traducerea aplicației în cod nativ?

A trebuit să mă confrunt cu limitările proiectului experimental cu care am lucrat de la început. Funcționează doar pe sisteme de operare pe 64 de biți. Nu va funcționa pe un sistem pe 32 sau chiar pe 16 biți.

Aici am dat peste o curiozitate istorică. Windows 3.11 a fost un sistem de operare pe 16 biți, dar odată cu introducerea următorului Windows 95 pe 32 de biți, Microsoft a pus la dispoziție un mic add-on numit Win32 pentru acest sistem mai vechi.

Win32-urile au permis unor aplicații ușoare și mici de 32 de biți să ruleze pe Windows 3.11 pe 16 biți. Prin urmare, nu a trebuit să mă ocup de tranziția de la 64 de biți la 16, ci „doar” 32. Win32-urile mi-au servit ca o bază foarte bună, deoarece în acel moment a fost susținută și integrată în diverse medii de dezvoltare.

Înainte de a trece la ultimul pas, este bine să menționăm cum funcționează traducerea programelor în cod nativ. Compilatorul de coduri native generează așa-numitele „fișiere obiect”. Aceste fișiere conțin codul nativ al programului, dar nu sunt executabile deoarece nu sunt complete. Acestea conțin referințe la alte simboluri definite în alte fișiere obiect.

Pentru a crea un fișier executabil, se folosește un instrument numit linker, care combină mai multe fișiere obiect într-o singură unitate executabilă.

Mi-am compilat codul folosind un compilator 2020 în fișiere obiect. Le-am conectat apoi cu un linker din 1994.

Cum ați folosit acest „pas intermediar”?

Mi-am tradus aplicația în fișiere obiect folosind proiectul experimental menționat. Obținerea ieșirii pe 32 de biți de la un compilator experimental nu a fost o astfel de problemă, deoarece .NET Core acceptă sisteme de operare pe 32 de biți. Proiectul experimental folosit se bazează pe .NET Core. Am programat câteva detalii mici rămase într-un compilator experimental. De asemenea, le-am integrat înapoi în proiect pentru a le pune la dispoziția altora.

Ulterior, am folosit link.exe din 1994 ca linker.Programul meu era primitiv și fără dependențe, dar am fost totuși surprins că era compatibil cu vechiul linker. Cu toate acestea, formatul de fișier nu s-a schimbat prea mult de atunci, astfel încât linkerul nu a avut nici cea mai mică problemă cu acesta. Și, deoarece a venit din același timp cu Windows 3.11, a legat codul meu de alte fișiere obiect care au venit din momentul potrivit.

Programul rezultat funcționează în continuare și poate fi rulat pe Windows 10. Dar funcționează și pe Windows 3.11, care are acum peste 26 de ani.

Mai târziu, am reușit chiar să rulez jocul pe MS-DOS 5.

Nu ai încercat să mergi „mai departe”?

Mai târziu, am reușit chiar să rulez jocul pe MS-DOS 5. Dar acum nu am timp pentru petreceri similare. Atunci eram în concediu parental, așa că am mai avut puțin timp. Acum, pe lângă copil, trebuie să gestionez și munca.

Alte sisteme pe 16 biți sau platforme mai vechi decât Windows 3.11 ar necesita mai multă muncă decât sunt dispus să investesc. În Windows 3.11, avantajul era instrumentul Win32s. În caz contrar, ar trebui să încep să mă ocup de generatorul de cod nativ în sine, de exemplu, și ar fi mult mai mult de lucru. Nu ar fi imposibil, ci doar consumatoare de timp.

Între timp, unul dintre contribuitorii la proiectul experimental .NET a luat jocul meu și l-a reproiectat pentru a funcționa fără niciun sistem de operare. Echipa a demonstrat de fapt că C # poate fi folosit și pentru a crea sistemele de operare.