|
C# Kod Derleyicisi |
|
Gönderiliyor lütfen bekleyin... |
|
|
Genel olarak tasarlanmış bir yazılımın, farklı
kullanıcıların tüm ihtiyaçlarını karşılaması çok zordur. Daha geniş bir
kullanıcı grubuna hitab eden yazılım geliştirmek için izlenebilecek ilk yol
tasarım sırasında olabildiğince farklı durumu incelemek ve tasarımı bu
durumlara göre yapmak, diğer bir yol ise yazılıma esneklik katmaktır. Esnek bir
yazılım, üretim ortamına taşındıktan sonra da kullanıcılar tarafından
geliştirilmeye açık olan yazılımdır. Bir anlamda, programcıların bıraktıkları noktadan,
yazılımı geliştirme işini kullanıcılar devralmalıdır. Kullanıcılara bu imkanı sağlamak için ise,
tasarım araçları (form, iş akışı) ve programlama araçlarından (api’ler, scripting
dilleri, script motorları) faydalanılır.
Bu yazıda C# ile programlarımıza nasıl esneklik
katabileceğimizi, programımızı nasıl bir kod derleyiciye dönüştürebileceğimizi
göreceğiz.
CSharpCodeProvider Sınıfı
Programımıza bir kod derleyicinin sahip olduğu yetenekleri
vermek aslında düşünüldüğü kadar zor değil. Framework zaten bu görevi yerine
getiren bir sınıf barındırıyor. Bizim yapacağımız şey ise bu sınıfı kullanmak
ve birkaç ekstra özellik ekleyerek tek bir metod çağrısı ile istenen kodun
derlenip çalıştırılmasını sağlamak. Sözünü ettiğimiz sınıf olan CSharpCodeProvider
sınıfı, C# kodlarını derleyen ve CodeDOM kod modellerini kaynak koduna
dönüştüren nesnelere ulaşmamızı sağlar.
Yukarıda saydığımız büyük işleri küçük bir metodla
gerçekleştiren sınıfımızı yazmaya başlayalım. Sınıfımıza CodeRunner adını
verdim.
Öncelikle bize gerekli olan isim uzaylarını belirtiyoruz.
using System;
using System.IO;
using System.Text;
using System.Reflection;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using
System.Collections.Specialized;
|
Bir sonraki adım sınıf tanımını oluşturmak;
namespace CodeRunnerLib
{
public class CodeRunner
{
public static StringCollection ReferencedAssemblies = new StringCollection();
public static StringCollection NameSpaces = new StringCollection();
static CodeRunner()
{
ReferencedAssemblies.Add("System.dll");
NameSpaces.Add("System");
}
|
Yukarıdaki kod parçası içerisinde iki adet değişken
tanımladık. ReferencedAssemblies adındaki StringCollection nesnesi, derlenecek
olan kod için gerekli harici assembly’leri referanslara eklemek için
kullandığımız değişken.
NameSpaces adındaki diğer StringCollection nesnesi ise
derlenecek olan kodun başına “using ...” ifadesi ile eklenecek isim uzaylarının
listesini saklayan değişken.
CodeRunner sınıfının yapılandırıcısını (constructor) statik
olarak tanımladım çünkü CodeRunner sınıfı tek bir statik metoda sahip. Böylece
CodeRunner sınıfını kullanabilmek için bir CodeRunner nesnesi oluşturmamıza da
gerek kalmıyor. Statik yapılandırıcı içerisinde “System.dll” dosyasını
referanslara ekliyoruz. Ayrıca “using” ifadesi ile derlenecek kodun başında yer
alması için NameSpaces koleksiyonuna “System” isim uzayını ekliyoruz.
public static CompilerErrorCollection RunCode(string Code) { |
CodeRunner sınıfının tek metodu olan RunCode metodunun
tanımını yukarıda görüyorsunuz. Metod, parametre olarak bir string alıyor ve
derleme işleminin sonuçlarını içerisinde bulunduran bir CompilerErrorCollection
nesnesi döndürüyor.
CSharpCodeProvider cp = new CSharpCodeProvider(); |
Kodları derleme ve hataları döndürme işlemlerinde
kullanacağımız ICodeCompiler arayüzüne ulaşmamızı sağlayan CSharpCodeProvider
nesnesini tanımladık. Şimdi bahsettiğimiz işlemleri yapabilmek için
CodeProvider nesnesinin bir kod derleyici üretmesini ve bize döndürmesini
sağlayalım;
ICodeCompiler derleyici = cp.CreateCompiler(); |
CSharpCodeProvider sınıfının CreateCompiler metodunu
kullanarak ICodeCompiler arayüzünü uygulayan bir derleyici sınıf elde ettik.
RunCode metoduna parametre olarak geçilen kodları derlemek için artık bu
arayüzü kullanabiliriz.
CompilerParameters derleyiciParams = new CompilerParameters(); |
Derleme işleminin konfigurasyonunu yapabilmek için bir
CompilerParameters nesnesi tanımladık. Bu nesnenin birkaç özelliğini değiştirip
derleyici nesnesine, kodları nasıl
derleyeceğini bildireceğiz.
foreach (string refAs in
ReferencedAssemblies)
{
derleyiciParams.ReferencedAssemblies.Add(refAs);
}
|
Daha önce CodeRunner sınıfına eklediğimiz
ReferencedAssemblies koleksiyonu içerisinde bulunan tüm assembly isimlerini derleyiciParams adındaki
CompilerParameters nesnesi aktardık. Böylece derleyici arayüzü, kodları derlerken hangi assembly’leri kullanması
gerektiğini bilecek.
derleyiciParams.GenerateInMemory = true; |
Yukarıdaki ifade ile derleme işlemi bittikten sonra,
çıktının (assembly) hafızada tutulacağını belirtmiş oluyoruz. Eğer derleme
işlemi sonucunda elde edilen assembly’nin hafızada değil de diskte saklanmasını
istiyorsanız CompilerParameters sınıfının GenerateInMemory
özelliğini false yapıp, OutputAssembly özelliğinde bir dosya adı
belirtmelisiniz.
StringBuilder sbKod = new
StringBuilder();
IndentedTextWriter kodYazici = new IndentedTextWriter(new
StringWriter(sbKod));
foreach (string nSpace in
NameSpaces)
{
kodYazici.WriteLine("using
{0};", nSpace);
}
kodYazici.WriteLine("public class
DinamikSinif {");
kodYazici.Indent += 3;
kodYazici.WriteLine("public void
DinamikMetod() {");
kodYazici.Indent += 3;
kodYazici.Write(Code);
kodYazici.Indent -= 3;
kodYazici.WriteLine("}");
kodYazici.Indent -= 3;
kodYazici.WriteLine("}");
kodYazici.Close();
|
Yukarıdaki satırlarda tam bir kod dosyası hazırlayıp bir
StringBuilder nesnesi içerisinde saklıyoruz. Kodları yazdırmak için kullandığım
nesne bir IndentedTextWriter nesnesi. Bu nesneyi CodeDOM ile kod oluştururken
ya da bizim yaptığımız gibi dinamik kod geliştirirken kullanabilirsiniz. IndentedTextWriter
nesnesinin ilginç olan tek özelliği Indent
adındaki özellik. Bu özellik WriteLine
ya da Write ile yazdığımız kod
satırlarının Indent kadar girintili
olarak yazılmasını sağlıyor.
Yukarıdaki kodda önemli olan tek nokta şu:
CodeRunner.RunCode metoduna parametre olarak geçilen tüm
kodları tam bir sınıf tanımı içerisinde saklıyoruz. Önce NameSpaces
koleksiyonuna eklenen tüm isim uzaylarını using
ifadesi ile üretilen kodun başına ekliyoruz. Daha sonra dinamik bir sınıf
tanımlıyor, sınıf içerisinde geçici bir metod oluşturuyor ve parametreden gelen
kodları bu dinamik metod içerisine yerleştiriyoruz.
Örneklemek gerekirse;
RunCode metoduna parametre olarak “MessageBox.Show(“Test”);” geçilmiş olsun. Bu durumda oluşturulan dinamik
sınıf tanımı şu şekilde:
using System;
public class
DinamikSinif
{
public void DinamikMetod()
{
MessageBox.Show("Test");
}
}
|
Tabi yukarıdaki kodun çalışabilmesi için
CodeRunner.References koleksiyonuna “System.Windows.Forms.dll” ve NameSpaces
koleksiyonuna “System.Windows.Forms” eklenmeli...
CompilerResults derlemeSonuclari =
derleyici.CompileAssemblyFromSource(derleyiciParams, sbKod.ToString()); |
Bir sonraki adımda derleyici
nesnesini kullanarak oluşturduğumuz dinamik sınıf kodunu, belirlediğimiz
ayarlarla (derleyiciParams)
derliyoruz. Derleme işleminin sonucu derlemeSonuclari
adında bir CompilerResults nesnesi içerisinde saklanıyor. Eğer derleme
sırasında bir hata varsa, hata ile ilgili bilgiler –hata olan satırın numarası
ve hata mesajı- bu nesnenin Errors koleksiyonunda barındırılıyor. Ayrıca
derleme işlemi başarılı ise, derlenen assembly’ye
CompilerResults.CompiledAssembly özelliğinden ulaşıyoruz.
if
(derlemeSonuclari.Errors.HasErrors)
{
return derlemeSonuclari.Errors;
}
|
Eğer derleme işlemi sırasında bir hata ile karşılaşılırsa
hatalar RunCode metodunun dönüş değeri olarak belirleyip metoddan çıkıyoruz.
Böylece RunCode metodunu çağıran tüketiciler işler yolunda gitmezse hatalarla ilgili
bilgilere ulaşabiliyorlar.
Assembly derlenmis =
derlemeSonuclari.CompiledAssembly; |
Eğer hatasız bir derleme işlemi gerçekleşmişse derlenen
assembly’yi derlenmis adındaki
değişkende saklıyoruz. Şimdi biraz Reflection kullanarak dinamik olarak derlediğimiz
assembly içerisindeki dinamik sınıfımızın bir örneğini oluşturabiliriz;
object
dinamikSinif = derlenmis.CreateInstance("DinamikSinif"); |
derlenmis ile
sakladığımız assembly içerisindeki DinamikSinif nesnesinin bir örneğini
assembly’nin CreateInstance metodunu
kullanarak oluşturduk. Bir sonraki adım yine dinamik olarak oluşturduğumuz
metodu çağırmak;
Type dinamikSinifTipi = dinamikSinif.GetType();
dinamikSinifTipi.InvokeMember("DinamikMetod",
BindingFlags.InvokeMethod, null, dinamikSinif,
null);
return null;
|
Metodu çağırabilmek için önce Reflection kullanarak dinamikSinif nesnesinin tip tanımına
ulaşıyoruz. Daha sonra tip tanımı üzerinden InvokeMember
metodu ile DinamikMetod adındaki
metodu çağırıyoruz. Böylece RunCode metoduna parametre olarak geçilen C# kodu
sanal bir sınıf içerisindeki sanal bir metod içine gömülerek çalıştırılmış
oluyor.
Aşağıda CodeRunner sınıfının tam kodunu bulabilirsiniz.
Ayrıca yazının sonunda CodeRunner sınıfı ve test için kullanabileceğiniz bir
winforms uygulamasının kaynak kodları da mevcut...
Önsel Akın
MCSD.Net, MCSE, MCDBA, CCNA
[email protected]
using System;
using System.IO;
using System.Text;
using System.Reflection;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using
System.Collections.Specialized;
namespace CodeRunnerLib {
public class CodeRunner {
public static StringCollection ReferencedAssemblies = new StringCollection();
public static StringCollection NameSpaces = new StringCollection();
static CodeRunner() {
// Statik
yapılandırıcı fonksiyon içerisinde
// System isim uzayına
referans gösteriyoruz
ReferencedAssemblies.Add("System.dll");
NameSpaces.Add("System");
}
public static CompilerErrorCollection RunCode(string Code) {
/* Metin kutusuna
yazılan kodu derlemek için
* bir CSharpCodeProvider nesnesi
oluşturuyoruz.
*/
CSharpCodeProvider cp = new CSharpCodeProvider();
ICodeCompiler derleyici = cp.CreateCompiler();
/* Kod derleyiciyinin
birkaç özelliğini ayarlayabilmek
* için bir CompilerParameters nesnesi
oluşturuyoruz.
*/
CompilerParameters derleyiciParams = new CompilerParameters();
// Derleyiciye gerekli
olan assembly’leri referansla gösteriyoruz
foreach (string refAs in
ReferencedAssemblies) {
derleyiciParams.ReferencedAssemblies.Add(refAs);
}
// Derlenen
assembly’nin hafızada tutulmasını istiyoruz
derleyiciParams.GenerateInMemory = true;
/* Derlenecek olan
kodu oluşturuyoruz.
* Metin kutusuna yazılan kodu, dinamik
olarak oluşturduğumuz
* bir sınıf içerisine gömeceğiz
*/
StringBuilder sbKod = new
StringBuilder();
IndentedTextWriter kodYazici = new IndentedTextWriter(new
StringWriter(sbKod));
// Derlenecek kod için
gerekli olan isimuzaylarını aktarıyoruz
foreach (string nSpace in
NameSpaces) {
kodYazici.WriteLine("using
{0};", nSpace);
}
// Sınıf tanımını
oluşturalım
kodYazici.WriteLine("public class
DinamikSinif {");
kodYazici.Indent += 3;
kodYazici.WriteLine("public void
DinamikMetod() {");
kodYazici.Indent += 3;
// Parametrede gelen
kodu, geçici sınıf metodu içerisine yerleştiriyoruz
kodYazici.Write(Code);
kodYazici.Indent -= 3;
kodYazici.WriteLine("}");
kodYazici.Indent -= 3;
kodYazici.WriteLine("}");
kodYazici.Close();
// Kodu derliyoruz,
derleme işleminin sonuçlarını saklamak için bir CompilerResult nesnesi
oluşturuyoruz
CompilerResults derlemeSonuclari =
derleyici.CompileAssemblyFromSource(derleyiciParams, sbKod.ToString());
// Derleme işlemi
sonucunda bir problem varsa bunları gösteriyoruz
if
(derlemeSonuclari.Errors.HasErrors) {
return
derlemeSonuclari.Errors;
}
// Derlenmiş olan
assembly’yi alıyoruz
Assembly derlenmis = derlemeSonuclari.CompiledAssembly;
// Assembly
içerisinden kendi eklediğimiz sınıfın bir örneğini oluşturuyoruz
object
dinamikSinif = derlenmis.CreateInstance("DinamikSinif");
// DinamikSinif’a ait
DinamikMetod adındaki metodunu çağırıyoruz
Type dinamikSinifTipi = dinamikSinif.GetType();
dinamikSinifTipi.InvokeMember("DinamikMetod",
BindingFlags.InvokeMethod, null,
dinamikSinif, null);
return null;
}
}
}
|
CodeRunner ve Test Uygulaması
Kaynak Kodları
Makale:
C# Kod Derleyicisi C#, Visual C# ve .NET Önsel Akın
|
|
|
-
-
Eklenen Son 10
-
Bu Konuda Geçmiş 10
Bu Konuda Yazılmış Yazılmış 10 Makale Yükleniyor
Son Eklenen 10 Makale Yükleniyor
Bu Konuda Yazılmış Geçmiş Makaleler Yükleniyor
|
|