Bir uygulama geliştiricinin dizayn yaparken en çok karşısına çıkan problem,
bilindiği gibi, uygulamanın esnek bir yapıya sahip olma sorunudur. Uygulamayı
tasarladıktan sonra tekrar tekrar dizayn üzerinde değişiklik yapmak zorunda
kalmak, bir yazılımcı için kabustan öte bir durumdur. Yazdığımız uygulamayı ne
kadar, geleceği düşünerek, ince eleyip sık dokuyarak hazırlarsak hazırlayalım,
günün birinde son kullanıcımızın ya da müşterimizin beklenmedik istekleriyle
karşılaşarak zor durumda kalmamız muhtemeldir.
Bu sorunun üstesinden gelerek, uygulamanın mümkün olduğu kadar modüler olmasını
sağlamanın en önemli yollarından biri, eklentiler çözümüdür. Microsoft .NET
teknolojisi, her konuda olduğu gibi eklenti tabanlı programlar yazımı konusunda
da, birçok yenilikle programcının işini kolaylaştırmıştır. Bu konudaki en
önemli silahımız System.Reflection namespace’idir, ve reflection kullanarak
elimizdeki assembly dosyaları hakkında bilgiler edinmemiz, metadata’larını
sorgulayarak onları tekrar tekrar hafızaya yüklememiz oldukça kolay bir iştir.
İçerik:
- Eklenti
mimarisi ve tasarımı
- Interface’lerin
tanımlanması
- Taşıyıcı
(Host) programın hazırlanması
- Eklenti
örnekleri
- Toplama
class’ının hazırlanması
- Üs
alma class’ının hazırlanması
Eklenti Mimarisi ve
Tasarımı
Hazırladığımız
uygulamanın modüler ve esnek bir yapıda olmasını sağlayacak eklentilerin
hazırlanma yollarından en önemlisi, interface tasarımıdır. Tasarladığımız
interface’leri destekler şekilde oluşturulmuş class’lar ve bunları içeren
eklentiler böylece, host uygulama tarafından kolayca tanınabilecek ve hafızaya
yüklenebileceklerdir. Bu makaledeki örnek tasarım, iki interface’den (IEklenti
ve ITasiyici) oluşacak ve bu interface’ler, yazacağımız tüm eklenti class’ları
tarafından imlemente edilecek, taşıyıcı (host) program tarafından eklentileri
tanımlama sırasında kullanılacaktır.
Interface’ler
sayesinde ve Reflection kütüphanesi kullanılarak, eklenti olarak oluşturduğumuz
tüm dll’ler ve içlerinde olması muhtemel class’lar tanımlanacak, IEklenti
interface’ini uygulayıp uygulamadıkları belirlenerek host programın eklenti
listesinde yerlerini alacaklardır.
Interface’lerin
Tanımlanması
Öncelikle,oluşturmayı planladığımız interface’leri taşıyacak dll’imizi yaratarak,
planladığımız sistemi tasarlamaya başlayabiliriz.
Örnek olarak gösterilecek uygulama sistemi, ortak bir “Bin” klasörüne ihtiyaç
duyacaktır. Bunun nedeni, yazacağımız taşıyıcı uygulamanın, bulunduğu klasör
içindeki dll’leri test ederek, eklenti olup olmadıklarını bulmaya çalışacak
olmasıdır. Bunu sağlayabilmek için, oluşturacağımız her bir Visual Studio
projesinde output klasörü olarak, hepsi için ortak bir “bin” klasörü
belirlememiz yerinde olacaktır. Aşağıdaki resimde bir projenin output klasörünün
ayarlanması sırasındaki görüntüyü bulabilirsiniz.
Şekil
1: Proje output klasörünün yerinin değiştirilmesi
Masaüstünde
“EklentilerDemo” adlı bir klasör ve içinde “Bin” adlı, tüm projelerimiz
tarafından ortak output için kullanılacak bir klasör açmamız bu iş için yeterli
olacaktır.
İlk
olarak ayrı bir proje halinde IEklenti ve ITasiyici interfacelerini
tanımlayarak, bunları içeren dll dosyasını derlemeliyiz. Aşağıdaki listede,
ilerleyen bölümlerde oluşturulacak class’lar tarafından kullanılacak her iki
interface’in de kodlarını bulabilirsiniz:
using System;
namespace Savga.ReflectionDemo.Interfaces
{
public
interface IEklenti
{
void Ilklendir(ITasiyici tasiyici);
string Isim { get;
}
double Hesapla(int
ilkSayi, int digerSayi);
}
}
|
Liste
1: IEklenti.cs (PluginInterfaces.dll)
using System;
namespace Savga.ReflectionDemo.Interfaces
{
public
interface IEklenti
{
void Ilklendir(ITasiyici tasiyici);
string Isim { get;
}
double Hesapla(int ilkSayi,
int digerSayi);
}
}
|
Liste
2: ITasiyici.cs (PluginInterfaces.dll)
Şekil
2: Interface’lerin bir arada bulunduğu projenin görünümü
Taşıyıcı (Host)
Programın Hazırlanması
Taşıyıcı programımız bir formdan oluşacak, form instantiate
edildiği sırada assembly’nin bulunduğu klasöre bakarak tüm dll’leri gözden
geçirecektir. Dll’ler arasında IEklenti interface’ini kullananları bularak
bunları eklentiler listesine katacaktır.
Senaryomuz, eklentilerin matematik işlemleri yapması üzerine
kurulmuştur. Ana programımız hiçbir hesaplama yöntemini bilmeyecek, ancak
–IEklenti implementasyonundan da görüldüğü üzere – kullandığı eklentilerden
aldığı hesaplama bilgilerine göre, iki sayıyı bir operatör yardımıyla
hesaplamada kullanacaktır.
Eklentilerin tanımlanabilmesi ve listelenmesini sağlayacak
ayrı bir class yazarak, yapılacak tüm işleri birer static metot olarak
belirlemek, yapmamız gereken işlerden ilkidir. Aşağıda, bu iş için hazırlanmış
“EklentiYoneticisi” class’ının implementasyonunu bulabilirsiniz.
using System;
using System.IO;
using System.Reflection;
using Savga.ReflectionDemo.Interfaces;
using System.Collections;
namespace Savga.ReflectionDemo
{
public
class EklentiYoneticisi
{
public struct
EldekiEklenti
{
public string
strAssemblyPath;
public string
strClassIsmi;
}
public static
EldekiEklenti[] EklentileriBul(string strYol,
string strInterface)
{
ArrayList
eldekiEklentiler = new ArrayList();
string[] eldekiDlller;
int index;
Assembly
objDll;
// Dll dizini icindeki tum dll’leri gozden gecir.
eldekiDlller
= Directory.GetFileSystemEntries(strYol, "*.dll");
for (index=0; index<eldekiDlller.Length;
index++)
{
try
{
objDll
= Assembly.LoadFrom(eldekiDlller[index]);
AssemblyiIncele(objDll,
strInterface, eldekiEklentiler);
}
catch
{
// Dll load olmadigina gore yapacak bir sey yok..
}
}
// Bulunan eklentilere return et
EldekiEklenti[]
sonuclar = new
EldekiEklenti[eldekiEklentiler.Count];
if (eldekiEklentiler.Count != 0)
{
eldekiEklentiler.CopyTo(sonuclar);
return sonuclar;
}
else
return null;
}
private static void AssemblyiIncele(Assembly objDll, string strInterface, ArrayList eklentiler)
{
Type
objInterface;
EldekiEklenti
eklenti;
// DLL icindeki tum type’lar uzerinde bir gezin...
foreach (Type objType in
objDll.GetTypes())
{
if (objType.IsPublic)
{
if ((objType.Attributes &
TypeAttributes.Abstract) != TypeAttributes.Abstract)
{
// Su anda elimizdeki type interface’i imlement ediyor mu
bir bak..
objInterface
= objType.GetInterface(strInterface, true);
if (objInterface != null)
{
eklenti
= new EldekiEklenti();
eklenti.strAssemblyPath
= objDll.Location;
eklenti.strClassIsmi
= objType.FullName;
eklentiler.Add(eklenti);
}
}
}
}
}
public static object InstanceOlustur(EldekiEklenti eklenti)
{
Assembly
objDll;
object objEklenti;
try
{
// Dll’i yukle
objDll
= Assembly.LoadFrom(eklenti.strAssemblyPath);
// Class instance’ini olustur ve return et..
objEklenti
= objDll.CreateInstance(eklenti.strClassIsmi);
}
catch (Exception exp)
{
System.Windows.Forms.MessageBox.Show(exp.Message);
return null;
}
return objEklenti;
}
}
}
|
Liste
3: EklentiYoneticisi.cs (TasiyiciApp.exe)
public struct EldekiEklenti
{
public
string strAssemblyPath;
public
string strClassIsmi;
}
|
Liste
4: EldekiEklenti struct’ı
Yukarıdaki kodlarda da görüldüğü üzere, tüm eklentileri tek
bir yapı halinde tanımlayabilmek amacıyla, “EldekiEklenti” adlı bir struct oluşturulmuştur.
Bu struct sayesinde herhangi bir eklenti, içindeki class adı ve eklentinin
AssemblyPath’i ile tanımlanabilecektir.
public static EldekiEklenti[] EklentileriBul(string strYol, string
strInterface)
{
ArrayList
eldekiEklentiler = new ArrayList();
string[] eldekiDlller;
int index;
Assembly
objDll;
// Dll dizini icindeki tum dll’leri gozden gecir.
eldekiDlller
= Directory.GetFileSystemEntries(strYol, "*.dll");
for (index=0; index<eldekiDlller.Length;
index++)
{
try
{
objDll
= Assembly.LoadFrom(eldekiDlller[index]);
AssemblyiIncele(objDll,
strInterface, eldekiEklentiler);
}
catch
{
// Dll load olmadigina gore yapacak bir sey yok..
}
}
// Bulunan eklentilere return et
EldekiEklenti[]
sonuclar = new
EldekiEklenti[eldekiEklentiler.Count];
if (eldekiEklentiler.Count != 0)
{
eldekiEklentiler.CopyTo(sonuclar);
return sonuclar;
}
else
return null;
}
|
Liste
5: EklentileriBul metodu
EklentileriBul metodu System.IO namespace’i içinde bulunan
Directory class’ı, ve onun static metotlarından biri olan
GetFileSystemEntries’i kullanarak dll dosyalarını bulmakta, Assembly class’ının
LoadFrom static metodunu kullanarak onları hafızaya yüklemektedir. Ardından AssemblyiIncele
metodu yardımıyla, eldeki dll’in bir eklenti içerip içermediği kontrol
edilmektedir.
private static void AssemblyiIncele(Assembly objDll, string strInterface,
ArrayList eklentiler)
{
Type objInterface;
EldekiEklenti eklenti;
// DLL icindeki tum type’lar uzerinde bir gezin...
foreach (Type objType in objDll.GetTypes())
{
if (objType.IsPublic)
{
if ((objType.Attributes & TypeAttributes.Abstract) != TypeAttributes.Abstract)
{
// Su anda elimizdeki type interface’i imlement ediyor mu bir bak..
objInterface = objType.GetInterface(strInterface, true);
if (objInterface != null)
{
eklenti = new EldekiEklenti();
eklenti.strAssemblyPath = objDll.Location;
eklenti.strClassIsmi = objType.FullName;
eklentiler.Add(eklenti);
}
}
}
}
}
|
Liste
6: AssemblyiIncele metodu
AssemblyiIncele metodu Reflection kullanarak, yüklenmiş olan
assembly içinde bulduğu class’ın belirli bir interface’e uyup uymadığını
kontrol edecek, ve uyduğu takdirde eklentiler listesine ekleyecektir.
public static object
InstanceOlustur(EldekiEklenti eklenti)
{
Assembly
objDll;
object objEklenti;
try
{
// Dll’i yukle
objDll
= Assembly.LoadFrom(eklenti.strAssemblyPath);
// Class instance’ini olustur ve return et..
objEklenti
= objDll.CreateInstance(eklenti.strClassIsmi);
}
catch (Exception exp)
{
System.Windows.Forms.MessageBox.Show(exp.Message);
return null;
}
return objEklenti;
}
|
Liste
7: InstanceOlustur metodu
Son olarak, InstanceOlustur metoduna bakıldığında, bu metodun
da, parametre olarak aldığı bir eklentinin (dll içindeki class’ın) instance’ını
oluşturarak, nesne olarak kullanılabilir hale getirdiği görülebilir.
Sırada, yazılacak eklentileri kullanarak hesaplamalar yapacak
ana form class’ının oluşturuluması bulunmaktadır. Aşağıda, Visual Studio
dizaynını ve kod yapısı hakkındaki listeyi görebilirsiniz:
Şekil
3: Eklentileri bulacak ve kullanacak formun görünümü
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.IO;
namespace Savga.ReflectionDemo
{
public
class FrmTasiyici : System.Windows.Forms.Form
{
private System.Windows.Forms.Label lblSonuc;
private System.Windows.Forms.TextBox txtSayi1;
private System.Windows.Forms.ListBox lbEklentiler;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Label label3;
private System.Windows.Forms.Label label4;
private System.Windows.Forms.TextBox txtSayi2;
private System.Windows.Forms.Button btnHesapYap;
private System.ComponentModel.Container components
= null;
private EklentiYoneticisi.EldekiEklenti[]
eklentiler;
private System.Windows.Forms.Label label1;
private Interfaces.ITasiyici objTasiyici;
public
FrmTasiyici(EklentiYoneticisi.EldekiEklenti[] eklentiler) : base()
{
InitializeComponent();
this.eklentiler = eklentiler;
EklentilerListesiniOlustur();
objTasiyici
= new Tasiyici();
}
protected override void Dispose( bool
disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
public void
EklentilerListesiniOlustur()
{
Interfaces.IEklenti
objEklenti;
for (int i=0;
i<eklentiler.Length; i++)
{
objEklenti
= (Interfaces.IEklenti) EklentiYoneticisi.InstanceOlustur(eklentiler[i]);
lbEklentiler.Items.Add(objEklenti.Isim);
}
lbEklentiler.SelectedIndex
= 0;
}
private void
InitializeComponent()
{
this.btnHesapYap = new
System.Windows.Forms.Button();
this.lblSonuc = new
System.Windows.Forms.Label();
this.txtSayi1 = new
System.Windows.Forms.TextBox();
this.lbEklentiler = new
System.Windows.Forms.ListBox();
this.label2 = new
System.Windows.Forms.Label();
this.label3 = new
System.Windows.Forms.Label();
this.label4 = new
System.Windows.Forms.Label();
this.txtSayi2 = new
System.Windows.Forms.TextBox();
this.label1 = new
System.Windows.Forms.Label();
this.SuspendLayout();
//
// btnHesapYap
//
this.btnHesapYap.FlatStyle =
System.Windows.Forms.FlatStyle.System;
this.btnHesapYap.Location =
new System.Drawing.Point(200, 56);
this.btnHesapYap.Name = "btnHesapYap";
this.btnHesapYap.Size = new
System.Drawing.Size(216, 23);
this.btnHesapYap.TabIndex = 0;
this.btnHesapYap.Text = "Hesap Yap";
this.btnHesapYap.Click += new
System.EventHandler(this.btnHesapYap_Click);
//
// lblSonuc
//
this.lblSonuc.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.lblSonuc.FlatStyle =
System.Windows.Forms.FlatStyle.System;
this.lblSonuc.Font = new
System.Drawing.Font("Tahoma", 14F, System.Drawing.FontStyle.Bold,
System.Drawing.GraphicsUnit.Point, ((System.Byte)(162)));
this.lblSonuc.Location = new
System.Drawing.Point(200, 104);
this.lblSonuc.Name = "lblSonuc";
this.lblSonuc.Size = new
System.Drawing.Size(216, 80);
this.lblSonuc.TabIndex = 1;
this.lblSonuc.TextAlign =
System.Drawing.ContentAlignment.MiddleCenter;
//
// txtSayi1
//
this.txtSayi1.Location = new
System.Drawing.Point(200, 32);
this.txtSayi1.Name = "txtSayi1";
this.txtSayi1.TabIndex = 2;
this.txtSayi1.Text = "";
//
// lbEklentiler
//
this.lbEklentiler.Location = new System.Drawing.Point(8, 32);
this.lbEklentiler.Name = "lbEklentiler";
this.lbEklentiler.Size = new
System.Drawing.Size(184, 147);
this.lbEklentiler.TabIndex = 3;
//
// label2
//
this.label2.FlatStyle = System.Windows.Forms.FlatStyle.System;
this.label2.Location = new
System.Drawing.Point(8, 8);
this.label2.Name = "label2";
this.label2.TabIndex = 1;
this.label2.Text = "Eklentiler:";
//
// label3
//
this.label3.FlatStyle = System.Windows.Forms.FlatStyle.System;
this.label3.Location = new
System.Drawing.Point(200, 8);
this.label3.Name = "label3";
this.label3.TabIndex = 1;
this.label3.Text = "Ilk Sayi:";
//
// label4
//
this.label4.FlatStyle =
System.Windows.Forms.FlatStyle.System;
this.label4.Location = new
System.Drawing.Point(312, 8);
this.label4.Name = "label4";
this.label4.TabIndex = 1;
this.label4.Text = "Diger Sayi:";
//
// txtSayi2
//
this.txtSayi2.Location = new
System.Drawing.Point(312, 32);
this.txtSayi2.Name = "txtSayi2";
this.txtSayi2.TabIndex = 2;
this.txtSayi2.Text = "";
//
// label1
//
this.label1.FlatStyle =
System.Windows.Forms.FlatStyle.System;
this.label1.Location = new
System.Drawing.Point(200, 88);
this.label1.Name = "label1";
this.label1.TabIndex = 1;
this.label1.Text = "Sonuc:";
//
// FrmTasiyici
//
this.AutoScaleBaseSize = new
System.Drawing.Size(5, 14);
this.ClientSize = new
System.Drawing.Size(424, 198);
this.Controls.Add(this.lbEklentiler);
this.Controls.Add(this.txtSayi1);
this.Controls.Add(this.lblSonuc);
this.Controls.Add(this.btnHesapYap);
this.Controls.Add(this.label2);
this.Controls.Add(this.label3);
this.Controls.Add(this.label4);
this.Controls.Add(this.txtSayi2);
this.Controls.Add(this.label1);
this.Font = new
System.Drawing.Font("Tahoma", 8.25F,
System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point,
((System.Byte)(162)));
this.Name = "FrmTasiyici";
this.Text = "Eklentiler ve Reflection
Demo";
this.ResumeLayout(false);
}
[STAThread]
static void Main()
{
EklentiYoneticisi.EldekiEklenti[]
eklentiler;
FrmTasiyici
objForm;
// Eldeki eklentileri al..
eklentiler
= EklentiYoneticisi.EklentileriBul(Path.GetDirectoryName(Application.ExecutablePath),
"Savga.ReflectionDemo.Interfaces.IEklenti");
if (eklentiler == null)
{
MessageBox.Show("Eklenti
bulunamadi! ", Application.ProductName, MessageBoxButtons.OK,
MessageBoxIcon.Stop);
return;
}
objForm = new FrmTasiyici(eklentiler);
Application.Run(objForm);
}
private void
btnHesapYap_Click(object sender,
System.EventArgs e)
{
Interfaces.IEklenti
objEklenti;
double sonuc = 0;
// Eklentiyi yukle ve initialize et..
objEklenti
= (Savga.ReflectionDemo.Interfaces.IEklenti)
EklentiYoneticisi.InstanceOlustur(eklentiler[lbEklentiler.SelectedIndex]);
objEklenti.Ilklendir(objTasiyici);
// Hesaplamayi yap ve goster..
try
{
sonuc
= objEklenti.Hesapla(int.Parse(txtSayi1.Text),
int.Parse(txtSayi2.Text));
}
catch
{
MessageBox.Show("Hesaplama
yapilamiyor.. Metin kutularina sayi girdiginizden emin olunuz.");
}
lblSonuc.Text
= sonuc.ToString();
}
}
}
|
Liste
8: FrmTasiyici.cs (TasiyiciApp.exe)
Liste 8’de de görüldüğü üzere formumuz, constuctor’u
üzerinden EklentiYoneticisi class’ının statik metotlarını kullanarak dll’leri
incelemekte, ve listesine ekleyerek çalıştırmaktadır. Default constructor
EldekiEklenti[] data türünde bir parametre alacak şekilde değiştirilmiş, Main
metodu üzerinde yapılan işlemlerle eklentiler bulunmuş, ve
EklentilerListesiniOlustur metodu sayesinde eklentiler görsel olarak formda
yerlerini almışlardır.
Eklenti
Örnekleri
Toplama Class’ının Hazırlanması
Basit olarak, Hesapla adlı metoduyla, iki sayının toplamına
return edecek Toplama class’ının kod listesi aşağıda görülebilir.
using System;
using Savga.ReflectionDemo.Interfaces;
namespace Eklenti1
{
public
class Toplama : IEklenti
{
public ITasiyici tasiyici;
#region IEklenti
Members
public void
Ilklendir(ITasiyici tasiyici)
{
this.tasiyici = tasiyici;
}
public string Isim
{
get
{
return "Toplama Eklentisi";
}
}
public double
Hesapla(int ilkSayi, int
digerSayi)
{
return ilkSayi + digerSayi;
}
#endregion
}
}
|
Liste
9: Toplama.cs (Eklenti1.dll)
Eklenti örneği olan Toplama class’ını oluştururken yapmamız
gereken, IEklenti interface’ini kullanmaktır. Interface’in kullanımı sayesinde
eklentimiz ana program tarafından kolayca tanınacaktır.
UsAlma Class’ının Hazırlanması
using System;
using Savga.ReflectionDemo.Interfaces;
namespace Eklenti2
{
public
class UsAlma : IEklenti
{
ITasiyici tasiyici;
#region IEklenti
Members
public void
Ilklendir(ITasiyici tasiyici)
{
this.tasiyici = tasiyici;
}
public string Isim
{
get
{
return "Eklenti 2: Üs hesaplama";
}
}
public double
Hesapla(int ilkSayi, int
digerSayi)
{
double sonuc = 1;
for (int i=1;
i<=digerSayi; i++)
sonuc
*= ilkSayi;
return sonuc;
}
#endregion
}
}
|
Liste
10: UsAlma.cs (Eklenti2.dll)
Aşağıda, örnekleri hazırlamakta kullanılan programın ve
kodların oluşturulduğu projelerin klasör hiyerarşisini, ve çalışması
sırasındaki ekran görüntüsünü bulabilirsiniz:
Şekil
4: Proje klasör hiyerarşisi
Şekil
5: Bin klasörü
Şekil 6: Eklentileri kullanan programın çalışır görünümü
Reflection kullanılarak Assembly’ler hakkında bilgi edinmek ve onları hafızaya yükleyerek
nesnelere dönüştürmek, ve bu yolla eklenti dll’leri kullanan programlar
hazırlamak, eski COM teknolojisindeki bu konudaki zorluklara nazaran daha kolay
bir biçimde esnek programlar geliştirmek, .NET teknolojisinin bize kazandırdığı
konusundaki en önemli silahlardan biridir. Bu makaledeki örneklerden yola
çıkarak görsel ve nesne tabanlı, aynı zamanda yapıları içinde gerçek esneme
imkanlarına sahip olan modern uygulamalar geliştirilebilir.
Nazmi SAVGA
MCSD.NET, MCDBA,
MCSE+I, MCSE (NT4, W2K, W2003), MCSA (W2K, W2003), MCAD, MCP+I, MCP, MCT
Trainer /
Software Engineer
[email protected]
Makale:
Eklenti (Plug-in) Tabanlı Uygulamalar ve Reflection C#, Visual C# ve .NET Nazmi Savga
|