Ján Hanák

Akademický blog o .NET programovacích jazykoch a technológiách.

Štítky

Ešte neboli vytvorené žiadne štítky.

Aktualizácie emailom

Paralelné programovanie na platforme .NET
Problematika paralelného spracovania programových operácií počítačových aplikácií je spravidla predmetom bádania špecializovaných výskumných centier. Navrhnutie a korektné naprogramovanie aplikácie, ktorá dokáže využiť všetok dostupný výpočtový výkon počítačovej stanice, a pritom sa ešte môže pochváliť efektívnou časovou zložitosťou, je nezriedka ťažká úloha, ktorá si vyžaduje nielen solídne teoretické základy, ale tiež mnoho praktických skúseností a cit pre paralelizáciu. V tomto technickom dokumente sa zameriame na objasnenie základných pojmov a princípov z teórie programovania viacvláknových aplikácií pre vývojovo-exekučnú platformu Microsoft .NET Framework 3.5. Pritom si ukážeme, ako prebieha spracovanie aplikácií na procesoroch s jedným a viacerými jadrami.

 

 

Procesy, aplikačné domény a vlákna
 

 

Keď spustíme PE súbor riadenej aplikácie .NET, ktorá bola napísaná v niektorom z .NET-kompatibilných programovacích jazykov, odohrá sa nasledujúci reťazec udalostí: Operačný systém zistí, že má dôjsť k spusteniu riadenej aplikácie, a preto pre ňu v operačnej pamäti alokuje samostatný pamäťový priestor, ktorý sa nazýva fyzický proces aplikácie. Fyzický proces je izolačným primitívom operačného systému, čo znamená, že pomocou fyzických procesov smie operačný systém jednoznačne oddeliť jednu bežiacu aplikáciu od inej. Manipulácia s fyzickými procesy umožňuje, aby bolo v operačnom systéme spustených súčasne viac aplikácií. Všetky 32-bitové operačné systémy triedy Microsoft Windows pracujú v režime viacúlohového spracovania, a preto dovoľujú súbežnú exekúciu viacerých aplikácií súčasne.

 

Keďže operačný systém pri požiadavke na spustenie aplikácie .NET rozpozná, že spúšťaná aplikácia je riadeným programom, zabezpečí načítanie a inicializáciu virtuálneho exekučného systému (VES), ktorý bude riadiť exekúciu tejto aplikácie. Primárnou súčasťou VES je spoločné behové prostredie CLR, ktoré je zodpovedné za korektné vykonanie všetkých nízkoúrovňových služieb, ktoré si manažment riadených aplikácií vyžaduje. Len čo je prostredie CLR inicializované, dôjde k zostrojeniu primárnej aplikačnej domény. Aplikačná doména predstavuje logický proces, do ktorého bude načítaný a vzápätí podrobený exekúcii kód aplikácie .NET. Prostredie CLR vytvára pre každú aplikáciu .NET jednu hlavnú aplikačnú doménu, ktorú označujeme ako primárnu. Podobne, ako je fyzický proces jednotkou izolácie aplikácií bežiacich v operačnom systéme, tak je aplikačná doména čoby logický proces izolačným primitívom riadených aplikácií a modulov, ktorú smú byť situované v jednom fyzickom procese. Z uvedeného vyplýva, že v rámci alokačného priestoru jedného fyzického procesu sa môže vyskytovať viac procesov logických (aplikačných domén).  

 

Hlavná výhoda existencie aplikačných domén spočíva vo výrazne nižšom systémovom zaťažení, najmä čo sa týka spotreby výpočtových prostriedkov. Povedané priamočiaro, pre operačný systém je omnoho jednoduchšie preniesť sa na inú aplikačnú doménu než uskutočňovať transport medzi dvoma fyzickými procesmi.  

 

Vo chvíli, keď je primárna aplikačná doména na svete, prostredie CLR zostaví primárne aplikačné vlákno, na ktorom budú exekvované jednotlivé programové príkazy aplikácie .NET. Vlákno sa niekedy nazýva aj termínom podproces, pričom opísať ho môžeme ako exekučný kanál, na ktorom dochádza k spracovaniu programových príkazov.

 

 

Implicitne sú všetky riadené aplikácie (bez ohľadu na programovací jazyk, v ktorom boli napísané) vybavené len jedným (primárnym) vláknom, a preto sa nazývajú jednovláknové aplikácie. Keď sme dospeli až do tohto štádia, máme vytvorený jeden fyzický proces, jednu (primárnu) aplikačnú doménu a jedno (primárne) programové vlákno. Teraz dochádza k naštartovaniu Just-In-Time (JIT) kompilátora prostredia CLR, ktorý identifikuje MSIL kód vstupného bodu aplikácie .NET (tým je spravidla hlavná metóda Main) a preloží ho do natívneho, teda strojového kódu štandardu x86. Nutnosť prekladu inštrukcií medzi formami MSIL a x86 je odôvodnená nemožnosťou priamej exekúcie MSIL inštrukcií pomocou inštrukčných súprav dostupných procesorov (či už založených na modeli RISC, alebo CISC). Preklad na požiadanie má svoje silné i slabé stránky. Negatívom je postranná réžia, ktorá sa s prekladom MSIL kódu viaže – tá je citeľná predovšetkým pri prvých prekladoch fragmentov kódu (a teda pri spúšťaní aplikácie .NET). Našťastie, výhod je viac: JIT kompilátor dokáže verifikovať MSIL kód, poradí si s validáciou metadát a umožňuje takisto dočasné ukladanie raz preložených fragmentov MSIL kódu do operačnej pamäte (tieto teda pri nadchádzajúcom použití neprechádzajú rekompiláciou).

 

Naviac je JIT kompilátor schopný prekladaný MSIL kód optimalizovať podľa typu a schopností cieľového procesora, čo môže niekedy jemne a inokedy zase celkom zreteľne pozitívne ovplyvniť exekučné časy činností, ktoré riadená aplikácia realizuje. Keď sú inštrukcie MSIL kódu pretransformované do ich rýdzej natívnej podoby, dochádza k ich priamemu spracovaniu procesorom. V tomto momente vravíme, že aplikácia .NET beží. Ak si vysvetlený proces premietneme od začiatku až do konca, môžeme prehlásiť, že ide o riadenú exekúciu jednovláknovej aplikácie .NET.

 

 

Jedno- a viacvláknové aplikácie .NET    
 

 

Jednovláknová aplikácia vlastní iba jedno programové vlákno a existuje teda len jeden exekučný kanál, na ktorom môže byť spracúvaný kód programu. Aplikácia je v tomto prípade exekvovaná synchrónne. Synchrónny model sa vyznačuje sériovým spracovaním kódu, ktorý by sme mohli jednoducho charakterizovať slovami „kto skôr príde, ten skôr melie“. Pokiaľ budeme uvažovať dve metódy A a B, tak keď bude zavolaná prvá metóda (A), začne sa spracovávať jej programový kód. Druhá metóda (B) sa k slovu nebude môcť dostať skôr, ako vo chvíli, keď prvá metóda (A) ukončí svoju činnosť. Synchrónny model exekúcie príkazov je v podmienkach jednovláknovej aplikácie .NET poznačený viacerými reštrikciami. Ako najvypuklejšie sa javia problémy malej dátovej priepustnosti a nízkej citlivosti aplikácie na používateľské vstupy.

 

Predstavme si, že vyvíjame grafickú aplikáciu, ktorá bude uskutočňovať grafické transformácie s bitovými mapami. Jednou z populárnych transformácií je prevod bitmapy do sivotónu (odtieňov sivej farby). Ak bude naša aplikácia pracovať iba s jedným programovým vláknom, grafická transformácia bude realizovaná na ňom. Konverzia farebných vektorov obrazových bodov je však pomerne náročná manipulačná operácia, ktorá si najmä u obrázkov s vyšším rozlíšením a bitovou hĺbkou vyžiada nemalú porciu výpočtového času procesora. Keď bude naša aplikácia zaťažená prevodom obrazových bodov do odtieňov sivej, nebude už schopná reagovať na pokyny používateľa. To je nepríjemné hneď z niekoľkých dôvodov, avšak hlavný je ten, že počas výkonu grafickej transformácie nebude môcť používateľ s aplikáciou korektne pracovať. 
 

 

Úplne logickou úvahou dôjdeme k samozrejmému riešeniu: nakoľko jedno vlákno je pre nás nevyhovujúce, pridáme ešte jedno. Tak sa z jednovláknovej aplikácie stane aplikácia viacvláknová. Termín viacvláknová aplikácia determinuje aplikáciu, ktorá je zložená aspoň z dvoch programových vlákien. Beh ohľadu na počet vlákien, ktoré aplikácia vlastní, jedno zostáva i naďalej primárnym vláknom (pritom platí, že pozíciu primárneho vlákna zastáva vôbec prvé vytvorené vlákno, ktoré pre našu aplikáciu skonštruovalo prostredie CLR).

 

 
    

Ďalšie vlákna sú chápané ako vedľajšie, resp. pracovné. Ak sa vrátime k našej dvojvláknovej aplikácií, tak táto okrem primárneho vlákna obsahuje ešte jedno pracovné vlákno. Pracovné vlákno je prínosom, keď uvážime, že časovo náročnú grafickú transformáciu môžeme zveriť do jeho kompetencie. Tým signifikantne znížime zaťaženie primárneho vlákna, ktoré bude môcť realizovať ďalšie dôležité akcie, ako je spracovanie vstupov od používateľa. Zapojením viacerých vlákien sa naša aplikácia zbavila obmedzení daných synchrónnym exekučným modelom a začala pracovať v asynchrónnom režime. Asynchrónny model je paralelizácii naklonený oveľa viac než jeho náprotivok, pretože programové operácie môžu byť vykonávané súbežne. Aj keď je pravda, že v dnešných dňoch musíme rozlišovať medzi pseudoparalelizmom a skutočným paralelizmom.

 

 

Exekúcia jedno- a viacvláknových aplikácií .NET na procesoroch s jedným a viacerými jadrami

 

Hoci už vieme, aká je interná architektonická kompozícia jedno- a viacvláknovej aplikácie .NET, stále ešte nebolo povedané nič o ich exekúcii. Pre potreby našej ďalšej rozpravy prijmeme dohovor, že najskôr budeme skúmať beh jedno- a viacvláknovej aplikácie .NET na jednojadrovom CPU, a vzápätí sa pozrieme na to, aké zmeny nás čakajú pri prechode na procesory s viacerými jadrami.  

 

Operačný systém v súčinnosti s virtuálnym exekučným systémom vždy prideľuje programovým vláknam riadených aplikácií .NET určitú časovú dávku (tzv. časové kvantum), počas ktorej má dané vlákno k dispozícii kompletnú výpočtovú silu procesora. Jednovláknová aplikácia má len jedno vlákno, takže procesor obsluhuje iba toto vlákno. V skutočnosti je pri jednovláknovej aplikácii úplne nepodstatné, či beží na systéme s jedným alebo viacerými procesormi. Jedno vlákno môže v jeden okamih spracovávať len jeden procesor, resp. len jedno jadro procesora.

 

Teraz prejdime k modelu „viacvláknová aplikácia –> jednojadrový procesor“. Tu sa už veci začínajú trošičku komplikovať, pretože jednojadrový procesor môže v istý okamih uskutočňovať len jednu činnosť. Ak máme aplikáciu kooperujúcu s viacerými vláknami, tak je každému vláknu pridelené určité časové kvantum, počas ktorého je spracovávaný kód toho-ktorého vlákna. Keď uplynie časové kvantum pre jedno vlákno, procesor uchová aktuálny stav vlákna pre neskoršie použitie a presunie sa na ďalšie vlákno. Vzápätí začne procesor realizovať kód tohto vlákna, a to tak dlho, ako vymedzuje vytýčené časové kvantum. Po vypršaní časového kvanta prechádza procesor na ďalšie, doposiaľ ešte neobslúžené vlákno a pokračuje jeho spracovaním. Opísaný kolobeh sa opakuje pre všetky vlákna, ktoré aplikácia obsahuje. Procesor teda iteruje medzi jednotlivými vláknami a keďže fyzická dĺžka časového kvanta je veľmi malá, z vonkajšieho pohľadu sa spracovanie vlákien javí ako paralelné, aj keď také v skutočnosti nie je. Prečo? Nuž preto, že v jednom okamihu je vždy uskutočňovaný kód práve jedného vlákna (pripomíname, že aplikácia beží na jednojadrovom CPU). Po technickej stránke je exekúcia viacvláknovej aplikácie na jednojadrovom procesore učebnicovou formou pseudoparalelizmu.
 Poradie, v akom budú vlákna procesorom spracovávané, je dané ich prioritou. Každé vlákno disponuje relatívnou prioritou, čo je priorita vlákna voči všetkým ostatným vláknam bežiacej aplikácie. Softvéroví vývojári pracujúci v niektorom z .NET-kompatibilných programovacích jazykov môžu nastavovať päť základných stupňov priorít programových vlákien.  

 

Otázkou zostáva, ako si bude viacvláknová aplikácia počínať na počítači, ktorý je osadený viacjadrovým procesorom. Ak budeme predpokladať optimálnu distribúciu pracovného zaťaženia medzi jednotlivými vláknami, tak potom bude spracovanie aplikácie paralelné. Virtuálny exekučný systém nedisponuje vlastným plánovačom pre spracovanie vlákien, miesto toho sa v plnej miere spolieha na plánovací mechanizmus operačného systému. Je teda na operačnom systéme, aby zabezpečil delegovanie programových vlákien na zodpovedajúci počet exekučných jadier procesora. Uveďme príklad: Povedzme, že máme dvojvláknovú aplikáciu .NET, ktorá implementuje tzv. dátový paralelizmus. Dátový paralelizmus uplatňuje jednu z dekompozičných techník, pri ktorej viacero vlákien operuje s rozličnými (a teda na sebe nezávislými) blokmi istej dátovej štruktúry (v záujme zjednodušenia výkladu môžeme uvažovať o vektorovom poli).

 

Pokračujme ďalej a dodajme, že našu aplikáciu spustíme na počítači s dvojjadrovým procesorom. Podľa teoretických princípov je najlepšie riešenie také, kedy sa počet programových vlákien zhoduje s počtom exekučných jadier procesora, resp. s počtom procesorov nainštalovaných v počítači. To je presne náš prípad, pretože 2 vlákna môžu byť paralelne spracúvané na 2 jadrách procesora. Ak budeme predpokladať, že je splnená požiadavka rovnomerného pracovného zaťaženia vlákien, tak potom každé vlákno vykoná polovicu práce. Pri výkonnostných testoch by sme mali zaznamenať takmer lineárny nárast výkonu dvojvláknovej aplikácie oproti aplikácii jednovláknovej. Ak teda sekvenčne pracujúca aplikácia splní úlohu za 12 sekúnd, paralelná aplikácia by mala byť hotová za približne 6 sekúnd.

 

Nemenej dôležitý je fakt, či je aplikácia .NET škálovateľná, teda, či sa dokáže flexibilne prispôsobiť hocako výkonnej hardvérovej infraštruktúre. Povedané inak, ak zoberieme našu dvojvláknovú aplikáciu a spustíme ju na počítači so 4-jadrovým procesorom, tak dostupná výpočtová kapacita bude využitá len na 50 %. Hoci každé z dvoch vlákien bude vykonávané na samostatnom jadre procesora, obsadíme len dve zo štyroch exekučných jadier. To nie je, samozrejme, optimálny stav. V kontexte štyroch exekučných jadier je potrebné vytvoriť 4 vlákna, pričom im musíme delegovať právomoci tak, aby každé z nich vykonalo 1/4 celkovej práce (pripomeňme, že stále uvažujeme o dátovom paralelizme). Vo všeobecnosti, ak máme n exekučných jadier, potrebujeme n programových vlákien, z ktorých každé bude realizovať 1/n pracovného objemu.    
 

 

Ak je viacvláknová aplikácia optimalizovaná pre exekúciu na viacjadrových procesoroch, hovoríme o skutočnom paralelizme, v rámci ktorého je väčší počet programových vlákien spracovávaný paralelne na viacerých procesorových jadrách. Snahou softvérových vývojárov je algoritmicky navrhnúť aplikáciu tak, aby mohlo byť dosiahnuté čo možno najlepšie využitie dostupných výpočtových zdrojov počítačového systému. Povedané inými slovami, aplikácia bude alokovať všetky prítomné jadrá procesora a vyťaží z nich maximum výkonnostnej kapacity. Potom môžu byť zbierané sladké plody vývojárskej práce, keď program dosahuje výborné exekučné časy a dokáže vo svoj prospech absorbovať všetku disponibilnú hardvérovú silu.

 

Ján Hanák
C++ pre .NET
Zatiaľ čo programovacie jazyky Visual Basic a C# sa tešia večne neutíchajúcemu záujmu zo strany vývojárov pracujúcich na platforme Microsoft .NET Framework 2.0, 3.0 a 3.5, len zlomok programátorov vie, že okrem „bejziku“ a „síšárpu“ existuje taktiež niečo, čomu sa vraví C++/CLI. 

C++/CLI alebo riadené C++ je .NET-kompatibilný programovací jazyk, ktorý sa objavil spoločne s uvedením Visual Studia 2005, teda pred viac ako dvoma rokmi. Na technických seminároch a konferenciách zameraných na vývoj počítačového softvéru a demonštráciu programátorských techník však s nevôľou zisťujem, že formulka C++/CLI je takmer neznáma. V skutočnosti o existencii a použití tohto programovacieho jazyka vie len maličký segment vývojárov. Z objektívneho pohľadu vznikol tento neutešený stav ako dôsledok mnohých rôznych faktorov. Keď sa spýtate programátorov píšucich svoj softvér pre platformu .NET, čo ich ako prvé napadne po vyslovení zaklínadla „dotnet“, tak drvivá väčšina vám povie, že v ich mysliach panuje silná asociácia s jazykmi C# a Visual Basic. Nech už sa pozriete kamkoľvek, spomenuté programovacie jazyky sú bez výnimky propagované ako prostriedky „prvej voľby“, ktoré sú k dispozícii vývojárom plánujúcim stavať svoje riadené aplikácie v štýle .NET.  

Áno, máte pravdu, C# a Visual Basic sú bez ošemetného glorifikovania skvelé jazyky. Sám ich už mnoho rokov používam, pričom tu a tam medzi nimi prepínam, čo mi umožňuje využívať pozitíva jedného alebo druhého jazyka. Na riešenie istej problémovej oblasti sa lepšie hodí C#, zatiaľ čo v iných partiách zase sebavedomo boduje Visual Basic. A prosím, pustite z hlavy slová nekvalifikovaných programátorov a zlomyseľníkov, ktorí hlásajú, že Visual Basic je iba hračkou a nie skutočným programovacím jazykom na budovanie počítačového softvéru. Visual Basic sa vo verzii 2008 predvádza vo vskutku skvelej forme – veď len miera zapracovaných syntakticko-sémantických inovácií svedčí o životaschopnosti tohto programovacieho prostriedku. Nechajme však Basic a C# na moment stranou, pretože mojou snahou, ktorá sa bude preplietať celým týmto článkom, je vyzdvihnutie pozície jazyka C++/CLI.

 

 

Zrodenie riadeného C++ 

Aby sme sa nedopustili omylu, rád by som uviedol, že s riadeným C++ sa mohli programátori po prvýkrát stretnúť už v roku 2002, presne vtedy, keď bola s veľkou pompou uvedená nová generácia programovacích nástrojov v súprave .NET. V týchto časoch sa v produkte Visual C++ .NET objavil jazyk v origináli pomenovaný ako Managed Extensions for C++. Syntaktické rozšírenia predstavovali doplnenie natívneho C++ (navrhnutého podľa štandardu ISO/IEC 14882:1998) o programové rysy, ktoré by dovoľovali použiť C++ čoby prostriedok na písanie aplikácií bežiacich v prostredí .NET. Pravdu povediac, začlenené rozšírenia mali tak hlboký dosah na jazyk C++, že sa o projekte Managed Extensions for C++ začalo hovoriť ako o zbrusu novom programovacom jazyku. Nuž, a tak sme získali prvé riadené C++, kde adjektívum „riadené“ bolo s dôvtipom zvolené tak, aby ostro kontrastovalo s existujúcim „natívnym“ C++. Skvelé je, že tento terminologický dodatok nenecháva žiadny priestor na nedorozumenie, lebo všetko, čo sa javí ako riadené, je určené pre virtuálny exekučný systém platformy .NET Framework.

Rozšírenia uvedené v Managed Extensions for C++ sa dotýkali lexikálnej, syntaktickej aj sémantickej stránky programovacieho jazyka, a je preto jasné, že tieto modifikácie museli byť reflektované aj v kompilátore produktu Visual C++ .NET. Microsoft pre riadené C++ pripravil nový prekladač, ktorý dokázal generovať zostavenia (assembly) s MSIL kódom, typovými metadátami a ďalšími potrebnými súčasťami.
 

Tým pádom získal produkt Visual C++ .NET citeľne na sile, pretože sa jednou ranou stal široko ďaleko jediným nástrojom, ktorý umožňoval vytvárať softvér podľa nasledujúcich scenárov:
 
  1. Písanie programov v jazyku C s pomocou knižnice CRT.
  2. Písanie programov v jazyku C v spolupráci s Win32 API.
  3. Písanie programov v jazyku C++ s podporou štandardnej knižnice a STL.
  4. Písanie programov v jazyku C++ spoločne s knižnicami MFC a ATL.
  5. Písanie riadených (.NET) programov pre systém CLR s využitím bázovej knižnice tried (BCL) v jazyku Managed Extensions for C++. 
Pre úplnosť dodajme, že Visual C++ .NET združoval projektové šablóny, vďaka ktorým mohli vývojári zakladať funkčné skelety svojich budúcich riadených aplikácií. K dispozícii bola dokonca tiež šablóna na stavbu formulárových aplikácií, ktorých základy boli položené na obľúbenej objektovej knižnici Windows Forms (alebo WinForms skrátene, ak chcete). Nanešťastie, dobrý nápad nebol dotiahnutý do konca, pretože Visual C++ .NET chýbal prívetivý vizuálny návrhár formulárov, čo bol pomerne tvrdý oriešok, s ktorým si nemálo vývojárov nevedelo rady. Zmienený oriešok sa nakoniec pekne rozluskol sám, keď v prvej polovici roka 2003 uzrel svetlo sveta Visual C++ .NET 2003 s plnou podporou vizuálneho programovania, čím sa v tejto disciplíne vyrovnal tandemu tvorenému Visual Basicom .NET 2003 a Visual C# .NET 2003. Avšak, pokiaľ by sme zvažovali celkový počet prítomných projektových šablón na vývoj aplikácií .NET, tak Visual C++ .NET nebol ani vo verzii 2003 rovnocenným partnerom spomenutej „veľkej dvojky“.   

V odborných kruhoch sa názory na jazyk C++ s Managed Extensions dosť často rozchádzali. Ak si dovolíme to zjednodušenie, a rozdelíme všetkých vývojárov na „komerčných“ a „akademických“, tak najmä z prvej skupiny sa ozývali námietky voči snáď až príliš prekombinovanej syntaxi a ťažkopádnej sémantike riadeného C++. Čo dodať, potreby komerčných programátorov sú iné ako požiadavky teoretických informatikov, počítačových vedcov a ďalších odborných persón pôsobiacich na vysokých školách informatického zamerania. Bežný vývojár chce jazyk, pomocou ktorého bude môcť dosiahnuť maximálnu možnú pracovnú produktivitu. Jeho cieľom je vytvorenie softvéru presne podľa prianí zákazníka, za ktorý dostane patričnú odmenu (pragmaticky zmýšľajúci programátor nevidí v použití jazyka žiadnu mágiu). Na druhej strane, ľudia pôsobiaci v akademickej sfére majú tendenciu dlho-predlho skúmať produkt, pričom to ozajstné vzrušenie prežívajú, keď sa môžu dosýta pohrať so všetkými programovými rysmi, voľbami prekladača a ďalšími, rýdzo špecializovanými a výsostne jemnými atribútmi analyzovaného produktu.

 

Napriek tomu, že jazyk C++ s Managed Extensions bol z pohľadu akademikov skvostným dielom umožňujúcim písať programy pre platformu .NET, jeho praktické rozšírenie a obľuba medzi vývojármi zostali na tristnej úrovni. Bola to skutočne škoda, pretože riadené C++ malo na viac, než sa v tej dobe javilo. Popri vývoji aplikácií .NET spomeňme trebárs možnosť „miešať“ natívny a riadený zdrojový kód, alebo veľmi rýchlo portovať existujúce natívne programy do riadeného prostredia. Nezriedka stačilo len vziať aplikačný kód a skompilovať ho s prepínačom /clr kompilátora. 

 

 

C++/CLI: Riadené C++ v novom vydaní     

Hoci sa jazyku C++ s Managed Extensions nepodarilo naplno preraziť, bolo jasné, že myšlienka urobiť z jazyka C++ komfortný nástroj na písanie aplikácií .NET vytyčovala cestu správnym smerom. Zlomom v ďalšej evolúcii sa stal november roku 2005, keď sa medzi vývojárov dostalo nové riadené C++. Vari zo všetkých strán inovovaný jazyk bol pomenovaný ako C++/CLI, pričom CLI predstavuje akronym pre spoločnú jazykovú infraštruktúru platformy .NET (Common Language Infrastructure). V C++/CLI sa odohralo skutočné zemetrasenie, ktoré nenechalo kameň na kameni, našťastie v pozitívnom zmysle slova. S najväčším potešením bolo posudzované razantné prečistenie syntaxe, vypustenie krkolomných kľúčových slov a zavedenie moderných syntaktických konštrukcií, ktoré konečne dávali na známosť, že nové riadené C++ sa stáva prvotriednym obyvateľom univerza .NET.

 

Stále platí, že základom na projektovanie jazyka C++/CLI je natívne C++, ktoré odpovedá štandardu ISO/IEC 14882:2003 (tento štandard z roku 2003 bol minoritnou aktualizáciou predchádzajúceho štandardu z roku 1998). Jazyk C++/CLI podstúpil štandardizačný proces organizácie ECMA, výsledkom ktorého bolo prijatie štandardu ECMA – 372 C++/CLI Language Specification. Zatiaľ nie je pre jazyk C++/CLI k dispozícii štandard ISO, a ako sa tak zdá, je vo hviezdach, či takýto štandard bude v dohľadnej dobe vôbec prijatý (technickej komisii nie je akosi pochuti už samotný názov jazyka, ktorý je vraj veľmi ľahko zameniteľný s pôvodným, a teda natívnym C++).

Pri projektovaní jazyka C++/CLI boli stanovené tieto ciele: 
  1. Vytvoriť elegantnú syntaktickú a sémantickú bázu, ktorá bude prirodzená a ľahko pochopiteľná pre programátorov v C++. 
  2. Začlenenie prvotriednej podpory pre programové rysy CLI (ide napríklad o prácu s hodnotovými a odkazovými dátovými typmi, podporu generického programovania a automatickú správu pamäte zrodených objektov).
  3. Priniesť všetko dobré z natívneho C++ (napríklad možnosť pre deterministické uvoľnenie zdrojov asociovaných s objektmi).
  4. Eliminovať výskyt zložitých a spletitých syntakticko-sémantických konštrukcií tak neslávne známych z jazyka C++ s Managed Extensions. 
Dobrou správou je, že všetky predostreté ciele sa tvorcom jazyka podarilo naplniť. Niekedy to síce bolo za cenu hlbokých zásahov do internej štruktúry jazyka, avšak vynaložené úsilie stálo bezpochyby za to. Jazyk C++/CLI kompletne nahrádza staršie riadené rozšírenia zhmotnené v C++ s Managed Extensions. C++/CLI je implementovaný v produktoch Visual C++ 2005, Visual C++ 2005 Express, Visual C++ 2008 a Visual C++ 2008 Express. Verzia 2005 je teda prvou edíciou Visual C++, v ktorej sa s jazykom C++/CLI stretávame. Ďalšia potešujúca správa je, že jazyk C++/CLI je v identickej podobe prítomný aj v najnovšej verzii 2008 produktu Visual C++. Pre vývojárov to znamená, že nadobudnuté znalosti a praktické skúsenosti môžu okamžite uplatniť aj v najnovšom prostredí. 

 

Osobitnú pozornosť si zaslúži kompilátor jazyka C++/CLI, ktorý prichádza s novými prepínačmi. Tieto precíznejšie upravujú štruktúru a štýl správania sa vygenerovaných zostavení aplikácií .NET. Bližšiu charakteristiku prepínačov kompilátora môžete nájsť v tab. 1.

 

Tab. 1: Prehľad prepínačov kompilátora jazyka C++/CLI
Prepínač Textový opis prepínača Charakteristika
/clr Common Language Runtime Support

Podpora jazyka C++/CLI

so zmiešaným kódom.
/clr:pure Pure MSIL Common Language Runtime Support Podpora jazyka C++/CLI s riadeným kódom.
/clr:safe Safe MSIL Common Language Runtime Support Podpora jazyka C++/CLI s riadeným a verifikovateľným kódom.
/clr:oldSyntax Common Language Runtime Support, Old Syntax Podpora jazyka C++ s Managed Extensions.

 

Základným prepínačom aj naďalej zostáva /clr, ktorého aktivácia spôsobí preklad zdrojového kódu do inštrukcií jazyka MSIL. Prepínač /clr sa kamaráti so zmiešaným kódom, čo znamená, že v kóde sa môžu bez akýchkoľvek problémov vyskytovať fragmenty riadeného C++/CLI i natívneho C++. Tento prepínač je vhodnou voľbou vtedy, keď pracujeme na aplikácii, ktorej jadro spočíva na rozhraní oboch svetov.

 

Vyšší stupeň konformity so zásadami CLI reprezentuje prepínač /clr:pure – jeho pričinením môžu vznikať rýdze zostavenia s riadeným kódom (natívny kód nie je povolený). Ešte viac, a pravdu povediac, úplne najviac ako to len ide, sa k princípom spoločnej jazykovej infraštruktúry hlási prepínač /clr:safe, ktorý dokáže vyprodukovať bezpečné zostavenia. Pre bezpečné zostavenie platia dve zásady: Po prvé, bezpečné zostavenie obsahuje verifikovateľný MSIL kód – to je kód, ktorý spĺňa kritéria dané verifikačným algoritmom JIT kompilátora. No a po druhé, bezpečné zostavenie obsahuje validné typové metadáta. Nemôže sa preto stať, že by bezpečné zostavenie obsahovalo poškodené metadáta, alebo že by v ňom bol uložený potenciálne nebezpečný (neverifikovateľný) MSIL kód. Bezpečné zostavenia zostrojené pomocou prepínača /clr:safe kompilátora sú úplne zhodné so zostaveniami, ktoré vytvárajú prekladače jazykov Visual Basic a C#.

 

Na samý koniec sme si nechali prepínač /clr:oldSyntax, ktorý odsúva jazyk C++/CLI do ústrania a na jeho pozíciu umiestňuje staršiu verziu riadeného C++ s Managed Extensions. Nie je nutné dodávať, že užitie tohto prepínača nie je pre nové projekty vôbec vhodné a uplatňuje sa len pri potrebe úprav existujúcich programov napísaných ešte v jazyku C++ s Managed Extensions.

 

 

Čo je nové v jazyku C++/CLI  

V tejto podkapitole by som sa rád zameral na niektoré z najvýznamnejších inovácií, s ktorými jazyk C++/CLI prichádza. Začneme pozvoľna, a síce preťaženým aritmetickým operátorom +, ktorý nám dovoľuje zreťazovať Unicode textové reťazce (konečne tak môžeme zabudnúť na neustále volanie statickej metódy Concat triedy System::String). Ďalej je na rade mechanizmus zjednotenia typov a jeho spätný chod, čiže programové operácie boxing a unboxing. Základnou myšlienkou uvedeného mechanizmu je schopnosť manipulovať s inštanciou hodnotového typu ako s objektom. Hoci v C++ s Managed Extensions bol boxing realizovaný explicitne príkazom __box, v jazyku C++/CLI je tento proces uskutočňovaný implicitne, teda kedykoľvek kompilátor usúdi, že je to potrebné (v tomto smere sa jazyk C++/CLI správa rovnako ako C# a Visual Basic). Spätný chod mechanizmu zjednotenia typov bol skôr zahájený spravidla operátorom dynamic_cast<>, čo je možnosť, ktorú máme aj v C++/CLI. Okrem toho však môžeme hodnotu z objektu získať tiež pomocou operátora safe_cast<>, či explicitnou typovou konverziou v štýle jazyka C. 
  

Prejdime na hodnotové triedy a štruktúry a enumeračné (vymenované) typy. Tieto sa deklarujú ako value class, value structenum class. Odkazové triedy (skôr s prívlastkom __gc class) sú teraz deklarované ako ref class a rozhrania sa vytvárajú pomocou kontextovo spojených kľúčových slov interface class. Rozhrania môžu implementovať nielen odkazové triedy, ale tiež ich hodnotové náprotivky. Objekty odkazových tried sú zakladané operátorom gcnew, ktorý garantuje ich alokáciu na riadenej halde. Kedykoľvek je objekt odkazovej triedy skonštruovaný, operátor gcnew vráti riadený manipulátor, prostredníctvom ktorého je objekt situovaný na riadenej halde dosiahnuteľný. Riadený manipulátor je syntakticky stvárnený symbolom striešky (^). Manipulátor ^ je nástupcom riadeného smerníka (__gc*) z jazyka C++ s Managed Extensions. Je dobré mať na pamäti, že uvedený manipulátor smeruje vždy na „celý“ objekt. Ak budete potrebovať pristupovať k podobjektom, môžete s výhodou využiť schopnosti interných smerníkov interior_ptr. Manipulátor ^ smie byť podrobený dereferencii rovnako ako natívny smerník. 

 

V súvislosti s kreáciou objektov odkazových tried má jazyk C++/CLI v rukách ešte jedno eso. Tým je zásobníková sémantika, vďaka ktorej možno ref triedu inštanciovať syntakticky takisto ako hodnotovú (value) triedu. Uveďme malú ukážku. Povedzme, že máme odkazovú triedu T. Potom príkaz zakladajúci objekt tejto triedy pomocou zásobníkovej sémantiky vyzerá takto: T obj; kde obj je názov premennej, prostredníctvom ktorej je zostrojený objekt dosiahnuteľný. Ak máte skúsenosti s jazykom C++, mohli by ste dospieť k pozoruhodnému záveru: dobre, no objekt bude alokovaný na zásobníku programového vlákna a nie na riadenej halde, všakže? Nie, vážení priatelia, nie je to tak – to sú len čary a troška mágie jazyka C++/CLI. Vždy totiž platí jednoduché pravidlo: Nech už je objekt založený akýmkoľvek syntaktickým spôsobom, ak ide o objekt odkazovej triedy, je vždy uložený na riadenú haldu (presnejšie do nultej objektovej generácie riadenej haldy).   

Veľmi rozsiahle zmeny poznačili finalizáciu objektov odkazových tried. Bohužiaľ na podrobnú rozpravu nie je v tejto chvíli priestor, no najdôležitejšou zmenou je skutočnosť, že deštruktory sú interne prevádzané na metódy Dispose rozhrania System::IDisposable. Na objekt, resp. riadený manipulátor smie byť aplikovaný operátor delete, ktorý iniciuje aktiváciu deštruktora (teda metódy Dispose). Deštruktor uskutoční explicitné uvoľnenie nepotrebných zdrojov, ktoré sú s objektom riadenej triedy spojené (ale pozor, tým sa nekončí životný cyklus samotného objektu). Novým adeptom na scéne je finalizér, čo je metóda s názvom !T (T je identifikátor odkazovej triedy). Finalizér je spúšťaný automatickým správcom pamäte v procese implicitnej finalizácie objektu odkazovej triedy. Je samozrejme na programátorovi, či bude chcieť triedu navrhnúť s finalizérom – potom budú objekty zrodené z tejto triedy vyžadovať svoju finalizáciu (a preto bude k ich likvidácii potrebných viac cyklov kolekcie správcu pamäte).

 

Odkazové triedy deklarované v jazyku C++/CLI môžu mať práve jedného priameho predka, nakoľko viacnásobná dedičnosť nie je v prostredí .NET povolená. Aj v tomto smere ale poteší prívetivosť jazyka C++/CLI, ktorý uvádza implicitnú verejnú jednoduchú dedičnosť. Programátori sú tak zbavení povinnosti vypisovať kľúčové slovo public pri každom odvodzovaní podtried. Kompletne prepracovanou syntaxou sa môžu pochváliť skalárne vlastnosti, ktorých vetvy na čítanie a modifikáciu privátnych dátových členov tried sú odteraz zoskupené do jedného syntaktického bloku. Skalárne vlastnosti tak vyzerajú podobne ako tie, ktoré poznáte z jazyka C#. Minoritnými zmenami prešli aj abstraktné a zapečatené triedy, v deklaráciách ktorých sa modifikátory abstractsealed nachádzajú až za identifikátormi týchto tried.  

 

 

C++/CLI má budúcnosť  

Programovací jazyk C++/CLI je moderným prostriedkom na vytváranie výkonných aplikácií bežiacich na vývojovo-exekučnej platforme Microsoft .NET Framework 2.0, 3.0 a 3.5. Pôvabná syntax, logickejšie programové konštrukcie a možnosť tesnej spolupráce s BCL z neho robia v očiach C++ vývojárov ideálneho kandidáta na tvorbu riadených aplikácií. Ak máte chuť sa s jazykom C++/CLI bližšie zoznámiť, prevezmite si z webu bezplatne
inštalačný balíček produktu Visual C++ 2008 Express a vydajte sa za novými dobrodružstvami. Som si istý, že objavíte mnohé malebné zákutia jazyka, s ktorým je konečne radosť pracovať.
  

 

 

Ján Hanák

Viac príspevkov