Çalışma-Zamanı Dinamik Kod Oluşturma

Yazılım geliştiriciler olarak her zaman kullanıcıların ihtiyaçlarını karşılamak için derleme-zamanı yazmış olduğumuz kodları kullanırız; ama zaman zaman keşke kendi kendini yazan, çalıştıran programlarımız olsun dediğiniz olmuştur. İşte tam bu düşüncede olan tüm yazılım geliştiricilerin en seveceği C# namespace’i sanırım System.Reflection.Emit olacaktır. Bu makalemde sizlere bu namespace altında yer alan sınıflar yardımıyla çalışma-zamanında nasıl dinamik olarak assembly üretip çalıştırabileceğinizi anlatmaya çalışacağım.

Yazılım dünyasının en popüler örneği olan “Merhaba Dünya” örneği üzerinden System.Reflection.Emit namespace’ini kullanmayı örneklemeye çalışacağım. Aşağıda nam-î değer “Merhaba Dünya” örneğinin C# versiyonunu bulabilirsiniz. Aslında örneğimiz içerisinde kompleks bir kod yok, bir konsol uygulaması, ve sadece ekrana “Merhaba Dinamik Dünya” yazmakta.

public class DinamikSinif {
    public static void Main(string[] parametreler) {
        Console.WriteLine("Merhaba Dinamik Dünya");
    }
}

Yukarıdaki kod parçacığını incelediğimizde üç ana bölüm görmekteyiz; DinamikSinif adıyla bir sınıf tanımlaması, bu sınıf içerisinde yer alan ve string parametre listesini kabul eden Main adıyla statik bir metod (aynı zamanda uygulamamızın giriş noktası), konsola “Merhaba Dinamik Dünya” yazdıran metod çağrısı.

İşe öncelikle yukarıdaki programın tanımlanacağı assembly’e bir isim vererek başlamalıyız;

var assemblyAdi = new AssemblyName("MerhabaDinamikDunya");
var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyAdi, AssemblyBuilderAccess.RunAndSave);

Bu satırlarda MerhabaDinamikDunya adıyla bir dinamik assembly tanımlaması yapılmakta. DefineDynamicAssembly metoduna geçilen ikinci parametrede oluşturulacak olan bu dinamik assembly’nin çalıştırılabileceği, aynı zamanda da dosya sisteminde saklanabileceği belirtilmekte.  Burada kullanılmış olan AssemblyBuilderAccess enum’u aşağıdaki değerleri alabilir;

Run Dinamik assembly çalıştırılabilir; fakat saklanamaz
Save Dinamik assembly saklanabilr; fakat çalıştırılamaz
RunAndSave Dinamik assembly hem çalıştırılabilir, hem de saklanabilir
ReflectionOnly Dinamik assembly sadece reflection yapılabilir bir context’e yüklenir ve çalıştırılamaz
RunAndCollect Dinamik assembly çalıştırılması sonrası kaldırılarak hafızadan temizlenebilir.

Yukarıdaki kod ile dinamik assembly oluşturabilmek için ilk adımları attık. Devamında, assembly içerisindeki işlemlerimizi gerçekleştireceğimiz bir modül tanımı yapmalıyız;

var modul = assemblyBuilder.DefineDynamicModule("MerhabaDinamikDunya.exe");

Artık elimizde asıl işimizi yapabileceğimiz bir modülümüz bulunmakta. Bu noktadan itibaren tüm dikkatimizi oluşturmak istediğimiz örnek uygulamamızın üç ana bölümüne verebiliriz; sınıf tanımlaması, ana metodumuz ve konsola “Merhaba Dinamik Dünya” yazmak.

  1. Sınıf Tanımlaması;
    var typeBuilder = modul.DefineType("DinamikSinif", TypeAttributes.Public | TypeAttributes.Class);

    Bu satırda modülümüz içerisinde DinamikSinif adıyla yeni bir tür oluşturmaktayız. Verdiğimiz  TypeAttribute’ler (System.Reflection altında yer almakta) vasıtasıyla bu oluşturduğumuz türün herkesçe erişilebilir bir sınıf olduğunu belirtmekteyiz. TypeAttribute’u aynı zamanda FlagsAttribute’ü ile işaretlenmiş olması sebebiyle, yukarıda olduğu gibi, bitwise combinasyonlarla bu metoda birden fazla sayıda parametre olarak geçilebilir.

  2. Ana Metodumuz ;
    var metodbuilder = typeBuilder.DefineMethod("Main",
        MethodAttributes.HideBySig | MethodAttributes.Static | MethodAttributes.Public,
        typeof(void),
        new Type[] { typeof(string[]) });
    

    Bu satırda, oluşturduğumuz tür içerisinde yeni bir metod tanımlamaktayız. İlk parametrede verdiğimiz “Main” adıyla oluşturulacak bu metod ikinci parametrede verdiğimiz MethodAttributes’ler sayesinde static public olarak tanımlanacaktır. TypeAttributes’da olduğu gibi MethodAttributes’da FlagsAttribute’ü ile işaretlenmiştir ve farklı kombinasyonlarda bitwise olarak kullanılabilir. üçüncü parametrede void türünü geçerek metodumuzun bir dönüş türü olmadığını belirtiyoruz. Son parametre ile de metodumuzun alacağı parametrelerin türlerini belirtmiş oluyoruz.

    Şimdiye kadar takip etmiş olduğumuz adımlarla birlikte aslında assembly’mizin önemli bir bölümünü tamamlamış durumdayız. Eğer bu noktada oluşan kodu görebilmiş olsaydık aşağıdakine benzer olduğunu görürdük:

    public class DinamikSinif {
        public static void Main(string[] parametreler) {

    Dikkatli gözler bu görüntüde açılmış olan parantezlerin henüz kapatılmadığı rahatlıkla görecektir. Bunu bilerek bu şekilde gösteriyorum; çünkü oluşturduğumuz metod içerisine henüz return kodunu eklemedik. Yazımın devamında bunu nasıl yapacağınızı da göstereceğim.

  3. Konsola “Merhaba Dinamik Dünya” yazmak
    var ilOlusturucu = metodbuilder.GetILGenerator();
    ilOlusturucu.EmitWriteLine("Merhaba Dinamik Dünya");

    Bir önceki adımla birlikte oluşturduğumuz metodumuzun içerisinde işlemlerimizi ancak bir DotNet IL (Intermediate Language) oluşturucu ile gerçekleştirebiliriz. ILGenerator sınıfı yardımıyla vereceğimiz komutlar Intermidiate Language’e dönüştürülür. ILGenerator’ün pek çok metodu yanında konsola yazmak için EmitWriteLine metodu yer almaktadır. ILGenerator her ne kadar bize doğrudan tüm DotNet kütüphanelerindeki metodları çağırmak için yardımcı olmasa da konsola bir şeyler yazabilmek için bu özel metoda sahiptir.

    Peki ILGenerator içerisinde bu yardımcı metod olmasaydı konsola birşey yazamaz mıydık? ya da başka metod çağrıları yapmak istediğimizde ne yapmalıyız? Bu iki soruyu aşağıdaki örnekle açıklamak isterim.

    ilOlusturucu.Emit(OpCodes.Ldstr, "Merhaba Dinamik Dünya");
    ilOlusturucu.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }));

    Bu örnekte ikinci satırda gördüğümüz Call IL işlem koduyla (OpCodes) Console türü içerisinde yer alan WriteLine metodunuzun (verilen parametreleri kabul eden) bir önceki satırda verilen string ile çağırılması sağlanmakta. Bu iki örnekte oluşturulan kodlara reflector uygulaması yardımıyla bakacak olursak aynı çıktıya sahip olduklarını göreceğiz.

Neredeyse işimizi bitirmek üzereyiz. Kodumuza aşağıdaki son dokunuşları da ekleyecek olursak en baştaki hedefimize ulaşmış olacağız.

typeBuilder.CreateType();

assemblyBuilder.SetEntryPoint(metodbuilder, PEFileKinds.ConsoleApplication);
assemblyBuilder.Save("MerhabaDinamikDunya.exe");

Bu satırlarla öncelikle tanımlamasını yaptığımız türün oluşturulmasını sağlamaktayız. Ardından Assembly’mizin bir konsol uygulaması olduğunu ve giriş noktasının oluşturmuz olduğumuz “Main” metodu olduğunu belirtiyoruz. Son olarak ise oluşan assembly’nin “MerhabaDinamikDunya.exe” adıyla uygulamamızı çalıştığı dizine saklanmasını sağlıyoruz.

Tüm bu adımlar ardından bir adım geriye çekilerek büyük resme bakacak olursak elimizde aşağıdaki şekilde bir kod oluştuğunuz göreceğiz:

using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;
using System.Diagnostics;

namespace ReflectionEmitOrnegi {
    class Program {
        static void Main(string[] args)  {
            var assemblyAdi = new AssemblyName("MerhabaDinamikDunya");
            var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyAdi, AssemblyBuilderAccess.RunAndSave);
            var modul = assemblyBuilder.DefineDynamicModule("MerhabaDinamikDunya.exe");

            var typeBuilder = modul.DefineType("DinamikSinif", TypeAttributes.Public | TypeAttributes.Class);

            var metodbuilder = typeBuilder.DefineMethod("Main",
                MethodAttributes.HideBySig | MethodAttributes.Static | MethodAttributes.Public,
                typeof(void),
                new Type[] { typeof(string[]) });

            var ilOlusturucu = metodbuilder.GetILGenerator();

            //ilOlusturucu.Emit(OpCodes.Ldstr, "Merhaba Dinamik Dünya");
            //ilOlusturucu.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }));
            ilOlusturucu.EmitWriteLine("Merhaba Dinamik Dünya");

            ilOlusturucu.Emit(OpCodes.Ret);

            typeBuilder.CreateType();

            assemblyBuilder.SetEntryPoint(metodbuilder, PEFileKinds.ConsoleApplication);
            assemblyBuilder.Save("MerhabaDinamikDunya.exe");
        }
    }
}

Bu kodu çalıştırdığımızda, başta verdiğimiz işlemi yapan kodu ürettiğiniz ve “MerhabaDinamikDunya.exe” adıyla uygulamamızın çalıştığı yere saklandığını göreceğiz.

Dilerseniz oluşturduğunuz bu sınıfı saklamadan önce uygulamanız içerisinde de kullanabilirsiniz. TypeBuilder içerisinde yer alan CreateType metodu bize uygulamamız içerisinde rahatlıkla kullanabileceğimiz bir tür sunacaktır. Biraz reflection kullanarak bu tür içerisindeki metodu çalıştırabiliriz;

var merhabaDunyaTuru = typeBuilder.CreateType();
merhabaDunyaTuru.GetMethod("Main").Invoke(null, new string[] { null });

 

Bir “Merhaba Dünya” örneğinden yola çıkarak anlatmaya çalıştığım dinamik kod oluşturma örneği gerçek dünyada çok faydalı yerlerde kullanılabilir. Bu kullanımlar için aklıma gelen ilk örnek verilen bir arayüz üzerinde bir web servise bağlanılmasını sağlayan bir vekil (proxy) sınıf. Oluşturduğunuz kütüphaneyi kullanacak kişilere aşağıdaki gibi çok basit şekilde servislere bağlanmalarını sağlamak sizce de hoş olmaz mı?

IWebServisim servisVekili = WebServisVekilFabrikasi.VekilOlustur<IWebServisim>("service adresi");

Bunun için çalışma-zamanında oluşturacağımız ve IWebServisim arayüzünü implement’e eden bir dinamik tür yetecektir. Bu dinamik tür implement’e ettiği metodlar vasıtasıyla kendi içerisinde ilgili web servis metodunu çağırabilir.

Çok sıklıkla tekrar eden ve reflection kullanarak yaptığınız işlerinizi System.Reflection.Emit namespace’i altındaki sınıfları kullanarak yapmanız durumunda oldukça önemli bir performans kazanımı sağlayabilirsiniz.

Kompleks kodlarda bu yöntemin uygulanması

Kompleks olan kodların bu yöntemle üretilmesi zor olabilir. Bu gibi durumlarda aşağıda anlattığım yolla hızlıca ilerleyebilirsiniz.

Öncelikle RedGate web sitesine giderek Reflector ürününün ücretsiz sürümünü indirmelisiniz. Kuruluma ihtiyaç duymayan bu program sayesinde herhangi bir algoritma ile içeriği okunamaması için karıştırılmamış olan tüm DotNet assembly’lerini inceleyebilirsiniz.

Reflector

Yapmanız gereken, içeriğini incelemek istediğiniz sınıfa gelip ters tıklamak ve gelen menüden Disassemble seçeneğini seçmek. Bunu yaptığınızda sol tarafta kaynak kodunu istediğiniz dilde inceleyebilirsiniz.

Reflector Disassemble

Üst tarafta yer alan combobox yardımıyla kodu istediğiniz dilde görüntüleyebilirsiniz. Eklenti desteği olan bu ürün’e CodePlex içerisinde yer alan ReflectionEmitLanguage eklentisini kurarak isterseniz bu assembly’nin oluşması için kullanılabilecek Reflection.Emit kodlarını kolaylıkla görebilirsiniz. Yapmanız gereken sadece eklentiyi kurduktan sonra dil olarak yukarıdaki diller combobox’ı içerisinde Reflection.Emit’i seçmek.

Reflector Reflection.Emit

Bu işlem sonrası istediğiniz kütüphane için Reflection.Emit kodunu görebilirsiniz; ki bu özellik karmaşık kodların üretilmesi noktasında sizin için can simidi olacaktır.

Fatih Boy

Ankara'da yaşayan Fatih, bir kamu kurumunda danışman olarak çalışmaktadır. ALM süreçleri, kurumsal veri yolu sistemleri, kurumsal altyapı ve yazılım geliştirme konularında destek vermektedir. Boş zamanlarında açık kaynak kodlu projeler geliştirmeyi ve bilgisini yazdığı makalelerle paylaşmayı seven Fatih, aynı zamanda Visual C# ve Visual Studio teknolojileri konusundan Microsoft tarafından altı yıl üst üste MVP (En Değerli Profesyonel) ödülüne layık görülmüştür. İş hayatı boyunca masaüstü uygulamaları, web teknolojileri, akıllı istemciler gibi konularda Asp.Net, Php, C#, Java programlama dilleri ve MySql, MsSql ve Oracle gibi veritabanı yönetim yazılımları ile çalışmıştır. İngilizce ve Türkçe olarak yayınlanan makalelerini gerek İngilizce bloğunda, gerekse de Türkçe bloğunda bulabileceğiniz gibi web sitesinden de açık kaynak kodlu geliştirdiği yazılımlarına ulaşabilirsiniz. vCard - Twitter - Facebook - Google+

3 yorum

  1. umut   •  

    güzel saollasın …

  2. Ahmet   •  

    çok teşekkürler

    • Fatih Boy   •     Yazar

      Tembel bir yazılımcının her zaman için işleri bilgisayarların kendi kendine yapmasını istemesi, onu başarıya götüren bence en önemli etken 🙂 bende bu kişilerden birisiyim sanırım…

      Dinamik kod oluşturmayı da bu yöndeki önemli adımlardan birisi olarak görüyorum.

      Umarım makalem bu konuda sizlere bir bakış açısı oluşturabilmiş, yol gösterebilmiştir.

Bir Cevap Yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir