Makale Özeti

System.Reflection.Emit isim uzayı altında yer alan sınıflar vasıtasıyla çalışma zamanında nasıl dinamik kod oluşturulabileceğini bir önceki makalemde anlatmıştım. Bu makalemde ise oluşturduğumuz bu dinamik kodlara nasıl debug bilgisinin eklerek Visual Studio içerisinden debug edilebileceğini sizlerle paylaşacağım.

Makale

Daha önce yazdığım makalemde sizlere çalışma-zamanı dinamik kod oluşturmayı anlatmıştım. Yazım ardından aldığım bazı maillerde bu yöntemi kullanmaya başladıktan sonra ikinci bir ihtiyacın oluştuğunu gördüm; oluşturulan dinamik kodun Debug edilebilmesi. Okuyacağınız yazımda bu konuyu ele alarak dinamik kod oluşturma sürecinde yapacağınız ek kodlama ile nasıl debug edilebilir bir kod oluşturabileceğinizi göstermeye çalışacağım.

Öncelikle bir önceki makalemde ele aldığım ve dinamik oluşturulan nihayi kodu hatırlayalım;

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

Basit bir sınıf tanımlaması, içerisinde yer alan Main metodunda sadece “Merhaba Dinamik Dünya” yazdırdığımız bu konsol uygulamasını aşağıdaki kodu kullanarak üretebilmiştik;

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");
        }
    }
}

İçinde bulunduğumuz domain içerisinde (AppDomain.CurrentDomain) yeni bir dinamik assembly tanımlayarak içinde oluşturduğumuz modüle sınıfımızı ve metodumuzu eklemiş, dosya sistemine kaydetmiştik.

Dinamik oluşturduğumuz bu kodu isterseniz aşağıdaki şekilde saklamadan önce/sonra programınız içerisinde kullanabilirsiniz;

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

 

Bu noktada, kimi zaman oluşan kodla ilgili problem yaşayabilirsiniz; acaba üretilen kod istediğiniz şekilde mi üretildi? Üretilen kod içerisinde bir noktada hatalı bir kullanım mı var? Ürettiğim kod zaman zaman hata fırlatmakta, neden?

Bu problemleri aşabilmek için kimi zaman saatlerce/günlerce uğraşmanız gerekebilir, problemin kaynağını bulamadığınız zamanlar dahi olabilir. Keşke bu kodu Visual Studio içerisinde yazdığınız diğer kodlar gibi debug edebilsediniz, programda ileri-geri gidip değişkenlerin değerlerini görüp değiştirebilseydiniz. Eğer bunları düşünüyor ve istiyorsanız, size iyi bir haberim var; System.Reflection.Emit içerisinde bunu da yapabilirsiniz.

Öncelikle oluşturmak istediğiniz kodu bir dosyada saklayın. Sakladığınız bu dosya herhangi bir uzantıya sahip olabilir .cs, .txt, .abc ya da istediğiniz başka bir uzantı. Bu makalede dosya uzantısının önemli olmadığını göstermek adına .txt uzantısını kullanacağım, MerhabaDinamikDunya.txt.

MerhabaDinamikDunya.txt

Projeme eklediğim bu kodu makalemin devamında kolayca debug edebilmek için çıktı klasörüne kopyalamak istiyorum. Bunu için Solution Explorer’da dosyaya ters tıklayıp properties seçeneği üzerinden özelliklerinin listelendiği ekrana gelip “Copy to Output Directory” özelliğini “Copy always” yapıyorum.

MerhabaDinamikDunya.txt Properties

Bu adımı sizinde takip etmeniz zorunlu değil. Benim yapmış olmamın tek nedeni kaynak kodunun bulunduğu dosyanın uzun uzadıya absolute path’ını vermek istememem. Dilerseniz sizde benimle aynı şekilde her derlemede kaynak kodu çıktı klasörüne kopyalatabilir ya da kaynak kodun absolute path’ini kullanabilirsiniz.

Dinamik bir kodun debug edilebilmesi için öncelikle tanımlandığı assembly’nin debug edilebilir olarak işaretlenmesi lazım. Bunu yapabilmek için DebuggableAttribute’ün kullanmanız gerekecektir. DebuggableAttribute’ünü kullanarak .net runtime’ın bir modüle ne şekilde davranacağını kontrol edebilirsiniz. Runtime üretilen kod içerisinde ekstra bilgiler taşıyabilir, hatta bu attribute’ün taşıdığı değerler doğrultusunda varsayılan olarak yapılan bazı optimizasyonları iptal edebilirsiniz. Burada dikkat edilmesi gereken nokta, bir kodun debug edilebilmesi için DebuggableAttribute’ün DefineDynamicModule metodu çağrılmadan önce tanımlanarak AssemblyBuilder içerisine attribute olarak eklenmiş olması gerektiğidir.

Aşağıdaki kod parçacığı yardımıyla AssemblyBuilder içerisinde kullanacağımız DebuggableAttribute’ü bizim için oluşturacak CustomAttributeBuilder’ı oluşturabilirsiniz.

var debuggableAttributeCtor = typeof(DebuggableAttribute).GetConstructor(new[] { typeof(DebuggableAttribute.DebuggingModes) });
var debuggableAttributeBuilder = new CustomAttributeBuilder(debuggableAttributeCtor, 
    new object[] { 
        DebuggableAttribute.DebuggingModes.DisableOptimizations | 
        DebuggableAttribute.DebuggingModes.Default 
});

Oluşturduğumuz bu CustomAttributeBuilder ile DebuggableAttribute’ü AssemblyBuilder içerisine eklemek ise aşağıdaki kodda görüldüğü kadar kolay;

assemblyBuilder.SetCustomAttribute(debuggableAttributeBuilder);

Dinamik bir assembly’nin debug edilebilmesi için ufak bir ayrıntı daha var; DefineDynamicModule ile modül tanımlamasını yaparken sembol bilgilerinin de üretilmesi gerektiğini belirtmeliyiz. Önceki makalemde sadece üretilecek olan dinamik modülün adını verdiğim DefineDynamicModule metodunu aşağıdaki şekilde yeniden düzenlemeliyiz;

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

Yukarıda sıraladığım adımlar sonrasında artık dinamik assembly’mizi debug edilebilir şekilde ayarlamış oluyoruz; ama işimiz henüz bitmedi. Öncelikle debug sembollerinin saklanacağı dokümanı (.pdb dosyası) hazırlamalı, ardından da bu dokümana debug bilgilerini eklemeliyiz.

Aşağıdaki satır debug sembollerinin saklanacağı dokümanı oluşturmamızı sağlayacaktır;

var document = modul.DefineDocument("MerhabaDinamikDunya.txt", SymLanguageType.CSharp, SymLanguageVendor.Microsoft, SymDocumentType.Text);

ModuleBuilder üzerinden ulaşılabilen DefineDocument metodu ilk parametre olarak bizden kaynak kodunun bulunduğu dosyayı isteyecektir. Makalemin başında oluşturduğu MerhabaDinamikDunya.txt dosya adını bu parametrede kullanıyorum. Burada oluşan assembly ile kaynak kodunun aynı klasörde bulunacağını varsaydığım için sadece kaynak kodu dosyasının adını vermekle yetindim; fakat daha öncede belirttiğim gibi istenirse burada absolute path’te kullanılabilir. Ardından vereceğiniz parametrelerle kullanılan programlama dilinin, dilin dağıtıcısının ve doküman türünün guid değerlerini vermemiz gerekmekte. Bu değerleri biliyorsanız guid’lerini oluşturup parametre olarak geçebilir ya da benim yaptığım gibi SymLanguageType, SymLanguageVendor ve SymDocumentType sınıfları içerisinde tanımlanmış static değerleri kullanabilirsiniz.

Bu adımları takip etmemiz ardında artık kodumuzu üretirken adım adım bu üretilen kodun kaynak kodumuz içerisinde hangi satırları kapsadığını belirtmeliyiz. Bu şekilde bir debugger debug edilen kod parçacığının kaynak kod içerisinde tam olarak nerede olduğu bilebilir. Bu bilgi aşağıdaki örnekte olduğu gibi MarkSequencePoint metodu yardımıyla verilebilir.

ilOlusturucu.MarkSequencePoint(document, 3, 9, 3, 52);

Burada verdiğimiz değerler bilginin hangi dokümana yazılacağı ve ilgili kodun kaynak kod içerisinde hangi satır/sütunda başlayıp hangi satır/sütunda bittiğini belirtmelidir. Yukarıdaki örnek üzerinden gidersek konsola “Merhaba Dinamik Dünya” yazdığım kodun bulunduğu 3.satır 9. kolon ve bitişi olan 3. kolon 52. satırı belirtmekteyiz.

Şimdiye kadar anlattıklarımı toparladığımızda ilk makalemdeki programa debug bilgileri aşağıdaki şekilde eklenebilmekte:

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

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

            var debuggableAttributeCtor = typeof(DebuggableAttribute).GetConstructor(new[] { typeof(DebuggableAttribute.DebuggingModes) });
            var debuggableAttributeBuilder = new CustomAttributeBuilder(debuggableAttributeCtor, 
                new object[] { 
                    DebuggableAttribute.DebuggingModes.DisableOptimizations | 
                    DebuggableAttribute.DebuggingModes.Default 
                });

            assemblyBuilder.SetCustomAttribute(debuggableAttributeBuilder);

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

            var document = modul.DefineDocument("MerhabaDinamikDunya.txt", SymLanguageType.CSharp, SymLanguageVendor.Microsoft, SymDocumentType.Text);
        
            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[]) });

            metodbuilder.DefineParameter(1, ParameterAttributes.None, "parametreler");

            var ilOlusturucu = metodbuilder.GetILGenerator();
            
            ilOlusturucu.MarkSequencePoint(document, 3, 9, 3, 52);
            //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.MarkSequencePoint(document, 4, 5, 4, 6);
            ilOlusturucu.Emit(OpCodes.Ret);

            var dinamikSinifType = typeBuilder.CreateType();

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

            dinamikSinifType.GetMethod("Main").Invoke(null, new string[] { null });

        }
    }
}

Yapmamız gereken sadece bu kadar. Bu adımları doğru şekilde takip ederseniz artık kodu çalıştırdığınızda çıktı klasöründe MerhabaDinamikDunya.exe dosyasının yanında debug sembollerinin bulunduğu MerhabaDinamikDunya.pdb dosyasının da oluştuğunıu görebilirsiniz.

Kodun debug edilip edilemediğini test etmek için makalemin başında belirttiğim kod içerisinde dinamik türümüzü oluşturarak çağırdığımız satıra bir break point ekleyebiliriz.

Breakpoint

Uygulamamızı Visual Studio içerisinden F5 ile debug edecek olursak bu satırdaki BreakPoint’e takıldığında F11 ile kod içerisine girmek istediğimizde Debugger’ımızın MerhabaDinamikDunya.txt içerisinde 3. satıra konumlandığını rahatlıkla görebiliriz.

MerhabaDinamikDunya.txt BreakPoint

Yukarıdaki ekran görüntüsünde Locals penceresine dikkat ederseniz, burada yerel değişkenlerimizin de bulunduğunu görebiliriz.

Artık bu kod içerisinde Cursor’u sürükleyerek debug edilen kod içerisinde debuggerın ileri-geri gitmesini sağlayabilir, değişken değerlerini değiştirebilir, hatta “İmmediate Window” içerisinde kod parçacıkları dahi çalıştırabilirsiniz.


Fatih Boy

fatih@enterprisecoding.com
http://www.enterprisecoding.com
http://twitter.com/fatihboy