Mellékelt példában olyan EXE-ket hozunk létre, melyek konzol alkalmazás csoportjába esnek. Indításukkor egyetlen kódot, egy GUID számot írnak a standard kimenetre. Ez a GUID szám a generáló alkalmazásban kerül létrehozásra, így egyedi és jellemző lesz arra a számítógépre, melyen programunk fut.
A Form-ra egyetlen gombot helyezünk, melynek lenyomásakor fogjuk az EXE-t létrehozni. Az EXE neve egy szám lesz. Először 1.EXE, majd a 2.EXE és így tovább. Ezt a folyamatos számozást úgy érjük el, hogy ellenőrizzük, hogy az adott EXE létezik-e már.
private void button1_Click(object sender, System.EventArgs e)
{
int i = 1;
while (File.Exists(GetFileName(i)))
{
i++;
}
Ha találunk egy olyan EXE-t, mely még nem létezik, akkor ennek nevét tároljuk a filename változóba.
string filename = GetFileName(i);
Nézzük miként is megy egy EXE létrehozása. Először is kell egy AssemblyName osztály példánya. Ezzel tudjuk azonosítani az újonnan létrehozandó assembly-t egyedileg. A Name property-be egy tetszőleges nevet adunk.
AssemblyName am = new AssemblyName();
am.Name = "TestAssembly";
Az assembly létrehozásához egy AssemblyBuilder osztály felhasználása válik szükségessé, melyet úgy hozhatunk létre, hogy az AppDomain osztály CurrenctDomain property-jéből kiolvasott osztály DefineDynamicAssembly függvényét hívjuk. Ezzel létrehozhatunk egy új dinamikus alkalmazás modult. Első paraméterébe a már létrehozott AssemblyName osztálynak a példánya jön, míg másodikként a hozzáférési módot adhatjuk meg. Save mód esetén a generált alkalmazást menthetjük EXE-be, míg Run mód esetén csak futtathatjuk azt. A harmadik paraméterben megadhatunk egy alkönyvtárat, ahová az EXE-t majd menteni szeretnénk.
AppDomain ad = AppDomain.CurrentDomain;
AssemblyBuilder ab = ad.DefineDynamicAssembly(am, AssemblyBuilderAccess.Save, Application.StartupPath);
Ezt követően definiálnunk kell egy új modult a már létrehozott assembly részére. Ehhez a DefineDynamicModule függvény nyújt segítséget. Első paraméterként a modul nevét, míg másodikként az EXE állomány nevét kell megadnunk.
ModuleBuilder mb = ab.DefineDynamicModule("TestModule", filename);
Következő lépésként egy típusfordítót kell létrehoznunk. Ennek segítségével tudunk majd függvényt létrehozni a modulon belül. A típusfordítót a TypeBuilder osztály képviseli, melyet a már meglévő modul DefineType függvényének hívásával hozhatunk létre. Ezt követően a DefineMethod segítségével létrehozhatunk egy függvényt. Mivel minden modulban szükséges egy olyan függvény, mely alapértelmezettként lesz meghívva az alkalmazás indításakor, így ezt nevezzük EntryMethod-nak, melyet publikusra és statikusra választunk. Ennek a függvénynek nem lesz sem paramétere, sem visszatérési értéke. Ha ilyen függvényt szeretnénk készíteni, akkor a DefineMethod harmadik paraméterében kellene megadnunk a függvényünk visszatérési értékét, míg a negyedikben egy olyan tömböt, melyben felsoroljuk a paramétereinek típusait. A harmadik és negyedik paraméter egyaránt Type típusban várja ezeket az információkat.
TypeBuilder tb = mb.DefineType("TestType", TypeAttributes.Public);
MethodBuilder metb = tb.DefineMethod("EntryMethod", MethodAttributes.Public | MethodAttributes.Static, null, null);
Attól, hogy a függvényünk neve EntryMethod, még nem lesz alapértelmezett. Hogy azzá váljon meg kell hívnunk a SetEntryPoint függvényt, melynek paraméterként megadjuk az imént létrehozott függvényünket.
Ezt követően tetszőleges kódot generálhatunk a függvényünkhöz. A kód generálásánál természetesen nem használhatunk fel C#, vagy egyéb forráskódot, hiszen itt most egy közvetlenül készítendő futtatható állományról van szó. A Visual Studio.NET által generált alkalmazások azonban nem gépi kódot, hanem egy belső kódot tartalmaznak, melyet Microsoft Intermediate Language-nek (MSIL) neveznek. Ilyen kódokat írhatunk a függvényünkbe. Ehhez segítséget nyújt számunkra az ILGenerator osztály, mely képes Microsoft Intermediate Language kódok létrehozására.
Az ILGenerator osztály EmitWriteLine függvény például olyan kódot generál, mely megfelel a Console.WriteLine utasításának. Paraméterként is azt a sztringet kell átadnunk, melyet a WriteLine-nak adnánk és hatása is azzal megegyező lesz. Mivel most egy új GUID szám kiírása a cél, így ennek megfelelően itt ezt adjuk meg paraméterként.
ILGenerator il = metb.GetILGenerator();
il.EmitWriteLine(Guid.NewGuid().ToString());
Példánk egyszerűsége miatt a létrehozott EntryMethod függvénynek más feladata nincs, így már csak be kell fejeznünk a függvényünk futását egy return utasítással. Az ehhez szükséges Intermediate Language kódot az Emit függvény hívásával írhatjuk be. Paraméterként a Ret kódot kell megadni, mely a return utasítással egyenlő.
Végső lépésként a fentiekben megfogalmazott típust legeneráltatjuk és elmentjük egy EXE állományba, mely ezt követően máris futtatható.
tb.CreateType();
ab.Save(filename);
}
Kicsit térjünk még vissza az Emit függvényre. Ennek paramétereként az OpCodes felsorolt típus elemei közül választhatunk egy-egy utasítást, mely megfelel az Intermediate Language azonos nevű utasításainak. Sok esetben ezek az utasítások paraméter nélküliek, mint a Ret is, van azonban olyan eset, ahol paramétert is vár az utasítás. Ekkor az Emit függvény azon változatát kell használnunk, mely második paraméterrel is rendelkezik és itt megadható az adott parancshoz tartozó paraméter értéke is.