Vítejte na blog.vyvojar.cz Přihlásit | Registrovat | Pomoc
Titulní Blogy Fotky Soubory

Petr Lazecký

Chorobovýplody

C++\CLI – Kompilační režimy (2)

Musím upřímně přiznat, že mě ohlas na předchozí článek příjemně překvapil. Zdá se, že jsem se trefil do vkusu. Ono, jen tak pro představu, napsat takovýto článek „stojí“ zhruba 3 hodiny, takže jakákoliv reakce (zvláště pozitivní :-)) pak potěší. Odpovědi na dotazy najdete v dalším článku, takže prosím
o trpělivost :-).

Než se pokusím odpovědět na zajímavé dotazy, které se objevily v reakci na předchozí článek, mi dovolte popsat kompilační režimy které nový C++ kompilátor přináší. Zdá se, že některé detaily o jazyce C++\CLI jsem nepopsal dostatečně srozumitelně. Ale na všechno se dostane.

C++ kompilátor umožňuje, a to zatím jako jediný kompilátor na platformě .NET, vytvářet smíšené, „mixed“, assembly. Mimochodem, existuje nějaký rozumný překlad pro slovo „assembly“, který se mezi vývojáři v Čechách ujal? Smíšené assembly obsahují jak kód zkompilovaný do nativního kódu daného procesoru (x86 například), tak kód zkompilovaný do CIL (Common Runtime Intermediate Language). Tato vlastnost kompilátoru je velmi užitečná tehdy, pokud máte velké množství existujícího C++ kódu, který byste rádi využili při psaní kódu pro platformu .NET. Doporučovaná technika je napsání „wrapper“ kódu, který zabalí existující nativní kód do „managed“ třídy. Tento způsob programování se moc neliší od programování v klasickém C++.

Jelikož začínám používat terminologii, která nemusí být úplně srozumitelná pro každého a jelikož cílem tohoto seriálu je nabídnout okno do jazyka CLI\C++ i těm kdo se s C++ z různých důvodů nesetkali, nejdříve maličko odbočím.

Pokud zmíním v textu „klasická C++ třída“, „C++ třída“, „ISO C++ kód“, „nativní třída“, apod., tak mám na mysli něco podobného jako toto:

class classicClass
{
private:
    char* path;

public:
    classicClass()
    {
        path_ = malloc( _MAX_PATH );
    }

    ~classicClass()
    {
        if (path_)
        {
            free(path_);
        }
    }
}

Nesoustřeďte se vůbec na to, co tento kód provádí. Podstatné je pouze to, že jednou z typických sekvencí v klasickém C++ je zpráva haldy (heapu). Jazyk C++ dává programátorovi kontrolu nad tím, kdy a jak jsou data na haldě alokována a uvolňována. To je samozřejmě v příkrém rozporu s konceptem běhového prostředí (runtime), kdy je halda spravována samotným běhovým prostředím (runtime) a GC. Zkompilováním klasického C++ kódu získáme nativní (x86) kód.

Pokud zmíním v textu „MC třída“, nebo „Managed C++ třída“, tak mám na mysli syntaxi a pravidla pro C++ kód, která platila ve VS .NET 2002 a VS .NET 2003. Zde je malá ukázka:

public nativeClass
{
private:
    char* path;

public:
    classicClass()
    {
        path_ = malloc( _MAX_PATH );
    }

    ~classicClass()
    {
        if (path_)
        {
            free(path_);
        }
    }
};

public __gc class managedClass
{ 
public: 
    __property int  get_Size();
    __property void set_Size(int value); 
    __property int  get_Value(int i); 
    __property void set_Value(int i, int value);
 
    managedClass()
    {
        handle = GetDesktopWindow(); 
    }

private:
    int handle; 
    int size; 
    int values __nogc[10]; 
};

Na tomto kódu si všimněte, jak je možné míchat klasické, nativní třídy s “managed“ třídami. Také si všimněte, jak transparentně může C++ programátor použít interop v managed třídě, a to pouhým voláním Win32 API funkce. Deklarace API funkce, jako v jazycích C# a VB.NET, není nutná.

Pro Managed C++ kód platí několik omezení. Hlavním cílem Managed C++ bylo umožnit využití existující masy C++ kódu. Návrháři Managed C++ se tedy zaměřili na tuto oblast. V Managed C++ je možné míchat jak klasické C++ přístupy, jako správu haldy pomocí C++ funkcí malloc(), tak managed přístupy. Ve výše uvedeném kódu můžete například vidět, že se třídy managedClass a nativeClass nacházejí ve stejném zdrojovém kódu. Díky černé magii kompilátoru je v tomto případě vygenerovaná smíšená (mixed) assembly. Navíc je třída managedClass označena pomocí klíčového slova jako __gc třída spravována GC. Programátor se tedy nemusí starat o uvolnění této třídy, stejně, jak jsme zvyklí z C#.

Když se nad tím chvilku zamyslíte, tak mi možná dáte za pravdu, že smíšené assembly nabízí zajímavé možnosti. C++ programátor neztrácí prostředí, na které je zvyklý, a pomocí asi deseti nových klíčových slov má možnost využít všech výhod GC. Vše ve stejném zdrojovém kódu. Microsoft označil tuto funkci jako IJW (It Just Works).

Jak jsem naznačil v předchozím příspěvku, tak časem objevily v návrhu technologie IJW závažné chyby, které vedly až k samotnému zpochybnění využitelnosti C++ v managed prostředí.

Mixed assembly mají i své nevýhody. Ta největší je v tom že tyto assembly nejsou „verifiable“. Tyto assembly také nejsou přenositelné. Jistě, o přenositelnosti .NET assembly mezi operačními systémy se dá zatím, buďme realisty, diskutovat. Ale přenositelnost mezi procesory (Intel, AMD, Itanium, AMD64), zvláště přechod na 64 bitovou platformu, je jistě praktická výhoda.

Pokud zmíním v textu „CLI\C++ třída“, „CLI\C++ kód“ apod., tak mám na mysli kód, který je možné zkompilovat pomocí volby /cli:safe (popsáno níže). Něco jako je toto:

public ref class cliClass
{
private:
    [DllImport("User32.dll", CharSet=CharSet::Auto)]
    [PreserveSig]
    static IntPtr GetDesktopWindow();
 
    // disable value semantics for this class
    cliClass(const A% lhs);
    A% operator=(const A% lhs);

public:
    void cliClass()
    {}

    // Destructor. Mapped to Dispose(bool) behind the
    // scenes
    ~cliClass()
    {}

protected:
    // Finalizer
    !cliClass() 
    {}

private:
    String^ reference;   
    String  stackLikeMember; // behaves in the same way as stack
                             // variable, no surprise for C++ programmer
};

Opět není podstatné co tento kód dělá, či spíše nedělá :-), ale co si můžeme v kódu dovolit a co ne. Všimněte si, že v CLI\C++ je nutné deklarovat Win32 API, stejně jako v C# či VB. Konec konců, CLI\C++ je plnohodnotný managed jazyk, „first class citizen“. C++ programátor si jistě všimne deklarace kopírovacího konstruktoru a přiřazovacího operátoru. C++ programátor si také všimne, že člen „reference“ je nedeklarován jako reference, kdežto člen stackLikeMember je nedeklarován stejně jako zásobníková proměnná, jejíž konstruktor je automaticky zavolán při vytvoření třídy a jejíž destruktor je automaticky zavolán při úklidu třídy.

Kód napsaný v CLI\C++ je možné zkompilovat jako „verifiable“ kód. Z toho také automaticky vyplývá, že kód napsaný v C++\CLI neopsahuje žádný nativní kód, stějně jako kód zkompilovaný C# kompilátorem.

Abych to vyjádřil ještě jinak, pamatujete si tyto věty z minulosti?

“JavaScript nemá nic společného s Javou, ale jde o nový skriptovací jazyk.”
“VB.NET nemá nic společného s Visual Basicem, ale jde o nový jazyk.”

Nu, tak bych možná mohl říci, že “CLI\C++ nemá nic společného s C++, ale jedná se o nový jazyk”. Tato paralela úplně nesedí, protože CLI\C++ ve skutečnosti má mnohem více společného s C++ než VB s VB.NET. Co se ale snažím říci je to, že CLI\C++ je nový jazyk, určen pouze pro platformu.NET. C++ programátor se bude v tomto jazyce cítit jako ryba ve vodě. Protože ale jde o opravdu nový programovací jazyk, tak vše co platí v klasickém C++ automaticky neplatí v CLI\C++. Klasické C++ konstrukce, jako například alokování paměti na haldě pomocí funkce malloc() a spol. nejsou v CLI\C++ samozřejmě dostupné, protože v managed neexistuje nic takového jako unmanaged halda. CLI\C++, na rozdíl od ISO C++, nepodporuje vícenásobnou dědičnost, protože CLI nepodporuje vícenásobnou dědičnost. Trošku předbíhám sled svých myšlenek pro tento seriál. Snažím se ale říci, že pokud se na jazyk CLI\C++ díváte zatíženým pohledem ze své předchozí skušenosti či povědomí o jazyce C++, tak je to podobné, jako byste srovnávali VB a VB.NET (opět, tato paralela trošku pokulhává, protože CLI\C++ implementuje semantiku jazyka C++ velmi důsledně na rozdíl od VB.NET, jež přináší pro VB programátora zcela nové koncepty jako je například samotné OOP).

Kompilační režimy

Poté co jsem vysvětlil termíny, které budu používat v tomto seriálu, se konečně dostávám ke kompilačním režimům CLI\C++.

VS.NET 2005 C++ kompilátor obsahuje přepínač /cli, pomocí něhož můžete kompilovat váš kód pro platformu .NET.

/cli – použití této volby vám umožňuje vytvářet smíšené, „managed“ assembly, které obsahují jak nativní kód, tak CIL kód. Tento režim podporuje i současný kompilátor ve VS.NET 2002 a VS.NET 2003.

/cli:oldSyntax – tento přepínač slouží ke kompilaci existujícího Managed C++ kódu. Jak jsem popsal výše, takovýto kód je „unverifiable“, může obsahovat jak managed tak nativní kód (smíšená assembly). Tento přepínač existuje pro zpětnou kompatibilitu s Managed C++.

/cli:pure – tento přepínač vytváří assembly obsahující pouze CIL kód. Jakýkoliv C++ kód, který nelze zkompilovat do CIL, bude při použití tohoto přepínače odmítnut kompilátorem. Proto Microsoft napsal managed verze CRT, STL (STL.NET) a MFC. Tento přepínač sice vytváří čistě managed assembly, ale tato assembly je stále „unverifiable“. Přeneseně si tuto volbu můžete představit jako ekvivalent volby /unsafe v C#.

/cli:safe – tento přepínač povoluje pouze konstrukce, které umožní vytvořit „verifiable“ assembly. K API funkcím je nutné přistupovat přes PInvoke, nelze používat aritmetiku pro ukazatele apod. Pouhý pokus vložit hlavičkový soubor windows.h vyvolá dlouhý seznam kompilačních chyb. CRT funkce také nejsou dostupné. Inu, jednak nejsou v "managed" třeba a jednak jsou s "managed" neslučitelné. Výsledná assembly je svou strukturou a použitelnosti v .NET identická assembly zkompilované C# kompilítorem.

Tento seriál je pouze o kódu, který lze zkompilovat s volbou /cli:safe.

Zveřejněno Wednesday, April 20, 2005 11:05 AM by lazo
Vedeno pod: , ,

Upozornění na nové komentáře

Pokud chčeš dostávat upozornění emailem na změny u toho příspěvku,tak se zaregistruj zde.zde

Odebírat komentáře k tomuto příspěvku pomocí RSS

Komentář

 

Zbyšek Hlinka napsal:

: Smíšené assembly obsahují jak kód zkompilovaný do nativního kódu daného procesoru (x86 například), tak kód zkompilovaný do CIL

Tak teď mě polil studený pot. Proč jsem si hned po přečtení této věty vzpomněl na Borland VCL.NET? Už si představuju, že si koupím nějakou komponentu, která bude hezky běhat s mým programem. Pak si uživatel mého programu vymění procesor, a celý program buchne. Jen proto, že programátor-frajer použil pro zvýšení výkonu nativní kód. A já si pak budu dlouho lámat hlavu nad tím, proč .NET komponenta neběží v .NET. Do pr....! :(

Takže teď budu muset zkoumat nejen to, zda je assembly psaná ve VCL.NET (což poznám docela snadno), ale i to, zda náhodou není mixed. Hmmm, krása... :(
April 20, 2005 1:03 PM
 

Petr Lazecky napsal:

Ano, ale ja se pokousim vyzdvihnout to, ze CLI\C++ je plne managed jazyk, kde muzete mit *i*, ne jenom, smisene assembly.
V cem vidim zasadni prulom je to, ze CLI\C++ umoznuje vytvaret stejne assembly, co se tyce struktury a pouzitelnosti, jako assembly zkompilovane v C#. Tedy plne managed a verifikovatelny kod. Pokud budete kompilovat s volbou /clr:safe, tak budete vytvaret plne managed kod bez jakychkoliv zakernosti. Vse ostatni je v CLI\C++ kvuli zpetne kompatibilite a pouzitelnosti obrovskeho mnozstvi existujiciho C++ kodu. Realisticky, to jako vyrobce C++ kompilatoru nemuzete tak jednoduse ignorovat.
C# za sebou tak dlouhou historii a proto je tezke srovnavat...
April 20, 2005 1:51 PM
 

Zbyšek Hlinka napsal:

Že lze plodit čistý managed kód, je v naprostém pořádku. Já jsem reagoval na to, že *lze* plodit i smíšený kód, a to v oblastech, kde to nemusím "standardně" očekávat. Tedy u nevizuálních komponent, například. Jinými slovy, mohu zcela reálně očekávat podrazy u kryptovacích, pakovacích a dalších podobných komponent, které jsou náročnější na výpočet a práci s bitovými strukturami. Tyto komponenty budou sice rychlejší, ale použitelné jen někde. A pokud si nebudu dávat pozor, mohu na to dojet. Což poněkud devalvuje původní ideu .NETu...

Proto jsem si vzpomněl na VCL.NET, kde mi hrozí podobné nebezpečí.
April 20, 2005 2:22 PM
 

Name napsal:

Myslím, že jsem někde viděl název "složení".
April 20, 2005 5:24 PM
 

vlko napsal:

No ja tak nejak predpokladam, ze to ze je assembly verifiable sa da jednoduchym sposobom zistit a nastavit kontrolu tak, aby sa Vami predpokladane podrazy neobjavili.
Inac skoda, ze cli/c++ prislo az teraz, byt to tak pred 2 rokmi, mozno ani c# neprerazi, takto uz mnoho firiem pre .Net pozaduje c#. Mozno ale na druhu stranu je lepsie pouzivat iny jazyk pre managed kod a iny pre unmanaged, uz len zo zvyku, zabudat uvolnovat pamat by bolo dost smutne:)
April 20, 2005 7:31 PM
 

Marcus napsal:

Já bych pro "assembly" spíš použil "sestavení".
September 4, 2006 1:03 PM
 

Tomášův blog .. napsal:

Na úvod O jazyce C++/CLI tu už psal trojici článků Petr Lazecký, takže pokud jste je ještě nečetli tak

January 13, 2007 11:37 PM
 

Tomášův blog .. napsal:

Na úvod O jazyce C++/CLI tu už psal trojici článků Petr Lazecký, takže pokud jste je ještě nečetli tak

January 14, 2007 3:43 AM

Vytvoření nového komentáře

(povinný) 
(nepovinný)
(povinný) 
Opiš čísla, která vidíš na obrázku:
Odeslat
Powered by Community Server (Personal Edition), by Telligent Systems