1 Assembly a jazyková interoperabilita Cílem následující ukázky je poskytnout p ř íklad naznač ující jen ně co málo více, než je hello, world. Bude se jednat o malou knihovnu pro ř ešení kvadratické rovnice. Téma opravdu elementární, pro demonstrač ní úč ely však bohatě stač í. Tuto knihovnu spat ř íte v ně kolika programovacích jazycích, což nám p ř edstaví.net platformu jako vícejazykovou, provázanou s mnoha svě ty 1. Navíc si také p ř iblížíme pojem rodina jazyků C, kam C# pat ř í. Na platformě.net jsou assembly soubory s p ř íponou *.exe nebo *.dll, obsahující jednu nebo více t ř íd p ř eložených do mezijazyka CIL. Kromě tohoto ř ízeného kódu obsahují v tzv. manifestu rovně ž metadata, t.j. sebepopisné informace o tě chto t ř ídách a mohou obsahovat i další atributy. Pokud chceme budovat knihovnu, je žádoucí její t ř ídy zkompilovat do samostatného assembly s p ř íponou *.dll. Takové assembly neobsahuje vstupní bod, metodu Main(). Knihovnu mů žeme potom znovu použít v jiné aplikaci, bez nutnosti kompilovat opakovaně zdrojový kód. Mů žeme zač ít návrhem t ř ídy v nadjazykové podobě, pro tento úč el byl vytvo ř en Unified Modelling Language UML jazyk. Na obrázku je vidě t UML diagram popisující č lenské promě nné (složky, fields) a č lenské funkce (metody) které chceme implementovat. Rovně ž vidíme, že každá t ř ída.netu je potomkem t ř ídy System.Object. 1 Ř íká se tom Cross Language Interoperability
2 Vhodným nástrojem (nap ř. MS Visio, které je souč ástí Visual Studia.Net Architect) lze z tohoto diagramu vygenerovat kostru zdrojového kódu. Ta vypadá v C# takto using System; namespace KvadrRovLib public class Solver private double a,b,c; public Solver() public void Read() public void Solve() private double Read(string caption) private double discriminant(double a,double b,double c)
3 a na cvič ení mů že být nabídnuta k doplně ní kódu. A takhle vypadá kostra ve Visual Basicu.Net (VB). VB nepat ř í do rodiny C a je to vidě t. Imports System Namespace KvadrRovLib Public Class Solver Inherits Object Private a As Double Private b As Double Private c As Double Public Sub Solver () End Sub Public Sub Read () End Sub Public Sub Solve () End Sub Private Function Read (ByVal caption As String) As Double End Function Private Function discriminant (ByVal a As Double, _ ByVal b As Double, _ ByVal c As Double) _ As Double End Function End Class End Namespace Visio umí vygenerovat i kostru pro ANSI/ISO C++. Jazyk C++ je následník jazyka C, který jej uvedl do svě ta objektového programování a byl šlágrem 90tých let minulého století. V C++ 2 musíme mít hlavič kový (*.h) soubor, který je pot ř eba ší ř it společ ně s binárními soubory. Ta totiž v C++ na rozdíl od assembly neobsahují metadata ( v p ř ípadě data o funkcích, t ř ídách, metodách...). #ifndef SOLVER #define SOLVER #include <string> using namespace std; namespace KvadrRovLib class Solver private: double a,b,c; public: Solver(); void Read(); void Solve(); private: double Read(string caption); 2 Takhle vypadá neř ízená varianta C++, Visual C++ umí generovat i ř ízený kód. Zdrojový kód pro ř ízené C++ je v dodatku.
4 double discriminant(double a,double b,double c); ; #endif // SOLVER A zde je soubor *.cpp s kostrou definic metod #include ".\solver.h" namespace KvadrRovLib Solver::Solver() void Solver::Read() void Solver::Solve() double Solver::Read(string caption) double Solver::discriminant(double a,double b,double c) Protože jsou definice oddě leny od deklarací, je nutno u každé metody znovu dodat vazbu na metadata. Tím myslím, že p ř ed identifikátorem metody stojí oddě leno č ty ř teč kou :: jméno t ř ídy, ke které metoda náleží. Rovně ž je p ř ítomen p ř íkaz preprocesoru #include, kterým umožníme kompilátoru C++ provést kontrolu metadat. V.Netu, jak již víme, jsou metadata integrální souč ástí assembly a proto ani č ty ř teč ku ani hlavič kové soubory s deklaracemi neuvidíme. Metadata si vygenerují v procesu kompilace a jsou neodluč ně spojena s ř ízeným kódem v assembly. Vrať me se proto rychle k našemu C#. Po doplně ní tě l metod zdrojový kód vypadá takto: Matematická knihovna v C# (Solver.cs) 1 using System; 2 namespace KvadrRovLib 3 4 public class Solver 5 6 public Solver() 7 8 Random rand = new Random(); 9 a = rand.nextdouble(); 10 b = rand.nextdouble(); 11 c = rand.nextdouble(); 12 13 14 private double a, b, c; 15 16 private double Read(String caption) 17 18 Console.WriteLine(caption); 19 return double.parse(console.readline()); 20 21 22 public void Read() 23 24 a = Read("a:");
Ř 5 25 b = Read("b:"); 26 c = Read("c:"); 27 28 29 private double discriminant(double a, double b, double c) 30 31 return b*b-4*a*c; 32 33 34 public void Solve() 35 36 double d = discriminant(a,b,c); 37 if (d == 0) 38 39 Console.WriteLine(- b / (2 * a)); 40 return ; 41 42 if (d > 0) 43 44 Console.WriteLine((- b + Math.Sqrt(d))/(2*a)); 45 Console.WriteLine((- b - Math.Sqrt(d))/(2*a)); 46 47 else 48 49 double r = Math.Sqrt(- d) / (2 * a); 50 Console.WriteLine("0+1i",- b/ (2 * a),r); 51 Console.WriteLine("0-1i",- b/ (2 * a),r); 52 53 54 55 Na ř ádku 6 je deklarován implicitní konstruktor t ř ídy Solver. Implicitní znamená, že nemá parametry. Pokud bychom ho nenapsali, doplní si jej p ř itom p ř ekladač automaticky sám (prázdný). My jsme implicitní konstruktor napsali. Funkcí konstruktorů je inicializovat data a uvést instance t ř ídy do provozuschopného stavu. Proto se konstruktory volají automaticky p ř i vytvá ř ení instancí (objektů ). To se právě dě je na ř ádku 8, kde je vytvá ř ena instance rand t ř ídy System.Random. T ř ída Solver má t ř i č lenské privátní promě nné typu double pro koeficienty kvadratické rovnice. Na ř ádcích 9 11 jsou tyto promě nné inicializovány pomocí volání metody NextDouble() objektu rand. ádkem 16 zač íná privátní metoda Read s parametrem caption typu string. Jejím úč elem je p ř eč íst promě nnou typu double a p ř edat ji jako návratovou hodnotu. Parametr caption slouží jako návě ští pro uživatele. Ř ádek 19 return double.parse(console.readline()); volá statickou metodu Parse t ř ídy double, která se pokusí zkonvertovat ř etě zec p ř eč tený z klávesnice na reálné č íslo s dvojitou p ř esností, v opač ném p ř ípadě se vyhodí výjímka, tu však pro jednoduchost neodchytáváme.
Ř 6 ádek 37 if (d == 0) obsahuje dvojité == což je logický test rovnosti (v C rodině tradič ní) jednoduché = je p ř i ř azovací p ř íkaz (v Pascalu = a :=, VB si pochopí sám). Koneč ně následující ř ádek 50 Console.WriteLine("0+1i",- b/ (2 * a),r); obsahuje zajímavou variantu statické metody WriteLine. Prvním parametrem je formátovací ř etě zec, který obsahuje znač ky 0,1,,n. Za formátovacím ř etě zcem následují parametry par0, par1,...,parn. Metoda WriteLine zkonvertuje parx na ř etě zec a substituuje jím všechny výskyty znač ek x ve formátovacím ř etě zci. Výsledný ř etě zec se objeví jako nový ř ádek textu na konzole. Zbytek kódu je snad sdostatek sebepopisný. Program v C# používající tuto knihovnu (KvadrRov1.cs) 1 using System; 2 using KvadrRovLib; 3 4 namespace kursycs 5 6 class TridniObal 7 8 static void Main() 9 10 Solver sol = new Solver(); 11 sol.solve(); 12 sol.read(); 13 Console.WriteLine("Reseni:"); 14 sol.solve(); 15 Console.ReadLine(); 16 17 18 Na ř ádku 2 vidíme p ř íkaz using KvadrRovLib, který nám umožní zkrátit identifikátory. Soubor deklaruje t ř ídu TridniObal která bude souč ástí jmenného prostoru kursycs. Metoda Main() obsahuje na ř ádku 10 p ř íkaz new, který vytvo ř í instanci t ř ídy KvadrRovLib.Solver a odkaz (adresu) na tuto instanci uloží do promě nné sol typu KvadrRovLib.Solver. Na ř ádcích 11 15 se sekvenč ně volají ve ř ejné (public) metody naší t ř ídy Solver. Nejd ř íve je voláno Solve() nad stavem instance sol, do kterého ji uvedl implicitní konstruktor. Vy ř eší se kvadratická rovnice s koeficienty vygenerovanými náhodným generátorem. Poté mů že uživatel zadávat svoje koeficienty a odeč íst ř ešení z
7 konzoly. Na 15. ř ádku se č eká na Enter, což v jistých situacích zamezí neč ekanému zmizení konzolového okna. P eklad a spušt ní programu Knihovnu a program ji používající p ř eložíme do ř ízeného kódu (CIL) p ř íkazy csc /target:library solver.cs csc kvadrrov1.cs /reference:solver.dll Nezapomeň te si nejd ř íve nastavit prost ř edí. V pracovním adresá ř i vznikly dvě assembly solver.dll a kvadrrov1.exe. Parametr /target:library za ř ídí, že vzniklé assembly bude mít p ř íponu *.dll. Pokud tento parametr neuvedeme, p ř eklad skonč í chybou, protože pro *.exe je vyžadována p ř ítomnost metody Main(). Parametr /reference zjistí na základě metadat v assembly solver.dll zda jsou p ř ítomny všechny t ř ídy a metody volané z kvadrrov1.cs a uloží vazbu na solver.dll do assembly kvadrrov1.exe. Spustíme kvadrov1.exe Samoz ř ejmě, že náš program mohl vzniknout jako jediné assembly, nap ř. tak, že bychom obě t ř ídy umístili do jediného souboru a rovně ž st tak zjednodušili p ř íkazy pro kompilaci. Takto ale mů žeme použít kdykoliv t ř ídu KvadrRovLib.Solver aniž bychom manipulovali se zdrojovým kódem. Dodatky pro zvlášť odolné jedince Platforma.Net podporuje mnoho jazyk S Visual Studiem 2003 dostanete 5.Netových jazyků C#, J#, Jscript, VB, C++ (to umí i nativní kód a je dodáno s nativními knihovnami) 3. Dalších více než 20 jazyků mů žete získat z jiných, č asto univerzitních zdrojů. 3 C++, C#, J#, Jscript patř í do rodiny C jazyků
8 MS.Net je navržen tak, aby si každý vývojář mohl vybrat jazyk podle toho, který např. nejvíce umí, nebo spíše ten který, se pro danou problematiku nejvíce hodí. Č asto se jedná o tradici. Intenzivní výpoč ty se např. programují nejč astě ji ve Fortranu, nově ji pak v C++, Javě a C#. Samozř ejmě se rozhodují vě tšinou týmy a jednotlivci se musí př izpů sobit. Nejdů ležitě jší je v moderním vývoji znovupoužitelnost kódu a tehdy se musejí př ekonávat jazykové bariéry. Program ve finanč nictví psaný ve Visual Basicu např. potř ebuje používat knihovnu psanou v C++. K tomu dř íve sloužil a slouží COM a CORBA, což jsou složité a tě žkopádné technologie. V.Netu to prostě díky společ ným typů m (CTS Common Type Specification) a stavbě jazyků (Common Language Specification) není žádný problém. Samozř ejmě musí jít o programy psané pro.net verze tě chto jazyků, používající FCL. V následující ukázce pů jde ještě o více. Sun Java má mnoho společ ných rysů s.netem, je ovšem v podstatě jednojazyková. Existuje velké množství kódu v javě. Př edpokládejme, že máme př ístup ke zdrojovému kódu. Mů žeme si vybrat.netový jazyk který je Sun Javě velmi blízko, je to J#. Pomocí kompilátoru J# (vjc.exe) mů žeme vyrobit ř ízený modul, který potom budeme modifikovat např. ve Visual Basicu a používat nakonec v C#. Mů žeme ovšem rovně ž použít Java Conversion Assistant pro (polo)automatickou konverzi zdrojového kódu Javy na zdrojový kód C#. Java má v.netu trochu vě tší podporu, než by se č ekalo, do jisté míry je podporován nejen jazyk, ale i runtime (Java platforma). V př edchozím jsme napsali knihovnu na ř ešení kvadratické rovnice, mohli jsme si to ale odpustit, kdybychom mě li k dispozici zdrojový kód v Sun javě. Matematická knihovna v isté Sun Jav (Solver.java) package KvadrRovLib; public class Solver public Solver() a=math.random(); b=math.random(); c=math.random(); private double a,b,c; private double Read(String caption) byte[] buf = new byte[100]; System.out.println(caption); double r=0; try System.in.read(buf); catch(java.io.ioexception exc) System.out.println(exc.toString());
9 String str = new String(buf).trim(); r = Double.parseDouble(str); return r; public void Read() a = Read("a:"); b = Read("b:"); c = Read("c:"); private double discriminant(double a, double b, double c) return b*b-4*a*c; public void Solve() double d = discriminant(a,b,c); if(d==0) System.out.println(-b/(2*a)); return; if(d>0) System.out.println((-b+Math.sqrt(d))/(2*a)); System.out.println((-b-Math.sqrt(d))/(2*a)); else double r = Math.sqrt(-d)/(2*a); System.out.print(-b/(2*a)); System.out.print("+"); System.out.print(r); System.out.print("i"); System.out.println(); System.out.print(-b/(2*a)); System.out.print("-"); System.out.print(r); System.out.print("i"); System.out.println(); Program v Jav používající tuto knihovnu (KvadrRov.java) import KvadrRovLib.*; public class KvadrRov public static void main(string[] args) Solver sol = new Solver();
10 sol.solve(); sol.read(); System.out.println("Reseni:"); sol.solve(); Jak to sestavíme a spustíme pod Sun JDK (Java Development Kit)? JDK je obdobou.net SDK, obsahuje základní nástroje nutné pro vývoj aplikací. Nejdř íve si ově ř íme zda máme JDK nainstalováno. Uložíme-li soubor KvadrRov.java do adresář e ř ekně me Kvadratice, je nutno uložit soubor Solver.java do podadresář e Kvadratice\KvadrRovLib. KvadrRovLib je název balíč ku - package, java alternativě prostoru jmen a jak se zdá Java pro prostory jmen př ímo používá adresář ový strom. 4 Zjistíme si, v kterém adresář i leží JDK adresář bin a napíšeme si následující dávku (buildjava.bat). set jdkbin=c:\jbuilder9\jdk1.4\bin cd kvadrrovlib %jdkbin%\javac.exe solver.java cd.. %jdkbin%\javac.exe -classpath. kvadrrov.java Pokud dávku spustíme, vytvoř í se v odpovídajících adresář ích ke každému souboru *.java soubor *.class obsahující tzv. byte kód (obdoba CIL). Pro spuště ní programu si napíšeme následující dávku (run.bat) set jdkbin=c:\jbuilder9\jdk1.4\bin %jdkbin%\java.exe -classpath. KvadrRov Probě hne JIT př eklad a JVM (Java Virtual Machine) spustí kód: Uvě domíme si, že v Javě velké množství tř íd znamená velké množství *.class souborů uložených ve stromové adresář ové struktuř e 5 simulující prostory jmen. 4 Zdrojový soubor *.java nesmí rovně ž obsahovat více než jednu veř ejnou tř ídu. 5 To je dů vod, proč se dále balí do *.jar archívů.
11 Knihovnu použijeme pod.net frameworkem Nad adresář em Kvadratice nastavíme prostř edí SDK (máme zavě šeno kafe). Zadáním př íkazu vjc /target:library KvadrRovLib/solver.java vyrobíme př ekladač em J# v adresář i KvadrRov assembly solver.dll obsahující ř ízený kód se tř ídou Solver. Toto assembly mů že rozvinout tým VB programátorů, který v každé tř ídě implementuje metodu about(). Tady je př íslušný kód Imports KvadrRovLib, System Public Class VBSolver Inherits Solver Public Function About() As String Console.WriteLine("Kvadraticka rovnice:") End Function End Class uložme jej do souboru VBSolver.vb. Př íslušné assembly z potomkem VBSolver př eložíme př ekladač em Visual Basicu vbc vbsolver.vb /reference:solver.dll /t:library kde referencujeme zdě dě ný kód ze solver.dll. Koneč ně vše použijeme v C# programu using System; using KvadrRovLib; namespace kursycs class TridniObal static void Main(string[] args) VBSolver sol = new VBSolver(); sol.about(); sol.solve(); sol.read(); Console.WriteLine("Reseni:"); sol.solve(); Console.ReadLine(); Vše př eložíme a sestavíme př íkazem csc kvadrrov.cs /r:solver.dll /r:vbsolver.dll
12 Vzniklé assembly kvadrrov.exe spustíme obvyklým způ sobem, tady je výsledek Kromě př íspě vku VB týmu (Kvadratická rovnice) vidíme rovně ž, jak.net na rozdíl od Javy podporuje automaticky č eskou kulturu (desetinná č árka). Implementace knihovny v MC (Managed C++) Ano C++ je jedním z jazyků.netu, dosahuje se toho pomocí tzv. ř ízených rozšíř ení (managed extensions). V našem př ípadě je to klíč ové slovo gc př ed slovem class, které instruuje př ekladač k vytvoř ení ř ízené tř ídy, jejíž životnost spravuje sbě rač odpadků Garbage Collector. // SolverMC.h #pragma once using namespace System; namespace KvadrRovLib public gc class Solver private: double a,b,c; public: Solver() Random* rand = new Random(); a = rand->nextdouble(); b = rand->nextdouble(); c = rand->nextdouble(); void Read()
13 a=read("a:"); b=read("b:"); c=read("c:"); void Solve() double d = discriminant(a,b,c); if (d == 0) Console::WriteLine(- b / (2 * a)); return ; if (d > 0) Console::WriteLine((-b+ Math::Sqrt(d))/(2*a)); Console::WriteLine((-b-Math::Sqrt(d))/(2*a)); else double x = - b/ (2 * a); double y = Math::Sqrt(- d) / (2 * a); Console::WriteLine("0+1i", box(x), box(y)); Console::WriteLine("0-1i", box(x), box(y)); private: double Read(String* caption) Console::WriteLine(caption); return Double::Parse(Console::ReadLine()); double discriminant(double a,double b,double c) return b*b-4*a*c; ; Implementace knihovny v Ansi/ISO C++ Zde je implementace v C++ podle normy ANSI/ISO, jazyce, který zů stal ve Visual Studiu a s.netem komunikuje prostř ednictvím MC (viz výše). Hlavič kový soubor solver.h je ten co už byl uveden. Obsahuje př íkaz using namespace std naznač ující použití standardní knihovny STL, která je souč ástí normy jazyka. #include ".\solver.h" #include <iostream> #include <cmath> #include <cstdlib> #include <ctime> #include <limits>
14 namespace KvadrRovLib Solver::Solver() srand( (unsigned)time( NULL ) ); double m = (double)numeric_limits<int>::max(); a = rand()/m*1000; b = rand()/m*1000; c = rand()/m*1000; void Solver::Read() a=read("a:"); b=read("b:"); c=read("c:"); void Solver::Solve() double d = discriminant(a,b,c); if (d == 0) cout << - b / (2 * a) << endl; return; if (d > 0) cout << (- b + sqrt(d)) / (2 * a) << endl; cout << (- b - sqrt(d)) / (2 * a) << endl; else double r = sqrt(- d) / (2 * a); cout << - b/ (2 * a) << "+" << r << "i" << endl; cout << - b/ (2 * a) << "-" << r << "i" << endl; double Solver::Read(string caption) cout << caption << endl; double r; cin >> r; return r; double Solver::discriminant(double a,double b,double c) return b*b-4*a*c; A ještě kód klienta: #include "./solver.h" using namespace KvadrRovLib; int _tmain(int argc, _TCHAR* argv[]) Solver* psolver = new Solver();
15 psolver->solve(); psolver->read(); psolver->solve(); delete psolver; return 0; Ješt m žeme vytvo it ízenou obálku (managed wrapper) Máme-li zajímavý kód v ANSI/ISO C++ a nemů žeme-li jej z ně jakého dů vodu př epsat do ně jakého.netového jazyka (nejlepší by byl MC), vyrobíme tzv. ř ízený obal. Dů vodem proč nechceme kód př epsat mů že být zachování výkonu. MC optimalizuje více než C#, ale na nativní C++ nemá. Tady vidíme plnou sílu Visual C++. Ř ízený kód je mixován s neř ízeným. Je vytvoř en odkaz na neř ízený objekt psolver a všechna volání metod jsou př evedena na ně j. Jak vidíme ř ízená tř ída mů že mít i destruktor. Trochu si asi budete muset pohrát s linkerem (smíšený kód v dll), ale zdrojový kód obálky je prů zrač ný. #include ".\solver.h" using namespace System; namespace KvadrRovLib public gc class SolverMC private: Solver* psolver; public: SolverMC()pSolver = new Solver(); ~SolverMC()delete psolver; void Read()pSolver->Read(); void Solve()pSolver->Solve(); ; A pro úplnost kód klienta v C#. using System; using KvadrRovLib; namespace KursCS class TridniObal static void Main() SolverMC solver = new SolverMC(); solver.solve(); solver.read(); solver.solve();
16 Assembly je ješt t eba ádn zatemnit Když se podíváme nástrojem ILDasm na assembly s knihovnou KvadrRovLib, s hrů zou si uvě domíme, jak snadné mů že být reverzní inženýrství našeho kódu. Visual Studio 2003 obsahuje zatemň ovač == obfuscator, komunitní DotFuscator. Po jeho použití se všechno krásně zatemní. Musíte si ovšem zatemnit všechny assembly aplikace naráz, jinak si nebudou rozumě t. Assembly se př idávají na záložku Trigger Files. Rovně ž je potř eba nastavit adresář e na záložce Build. DotFuscator najdete v nabídce Visual Studio.Net Tools (stejně jako shortcut na vsvars32.bat v př edchozí lekci).
17