.NET uygulamaları
geliştirirken .NET Framework Base Class Library (BCL) kütüphanelerini
projelerimize referans ekleyerek kullanırız ve bu kütüphaneleri projemizi
dağıtırken dağıtmamız yani istemci bilgisayarlara kopyalamamıza gerek yoktur,
zira örneğin System.Net.Dll dosyasını referans ekleyip kullandığınızda sadece
bu dosyayı kullanan kendi proje dosyalarınızı uygulamanızın çalışacağı ortama
kopyalamanız gerekir.
Ancak bir şekilde
edindiğiniz .NET uygulamalarının tek bir dosya olarak dağıtıldığına şahit
olmuşsunuzdur. Eğer .NET Framework BCL dışında kullanılan tüm sınıflar sizin
tarafınızdan yazılmış ise, ya da açık kaynak kodlu bir kütüphane desteği
alıyorsanız ve bu harici kütüphanenin lisanslama modeli kaynak kodları kendi
projenize dahil etmenizi ve bu şekilde derleyip dağıtmanızı engellemiyor ise
sorun yok tek bir dosyadan oluşan ve kolay dağıtılabilir bir projenizi olmasını
sağlayabilirsiniz.
Ancak harici bir
kütüphane kullanıyorsanız ve bunu kaynak kod olarak değil bir dll olarak
kullanmak durumundaysanız. Projenizi tek bir dosyadan oluşacak şekilde dağıtma
şansı elde edebilirmisiniz? Cevap; evet yapılabilir. Nasıl?
.NET Framework
Runtime bir assembly (genellikle bir dll’dir) dosyasını yüklerken sizin
yazdığınız kod ile yükleme işlemine müdahale etmenize izin verir. Bir event
handler’a (AppDomain.CurrentDomain.AssemblyResolve) register olduktan sonra yükleme sırasında kendi kodunuzu
çalıştırabilirsiniz. Tabi AppDomain.CurrentDomain.AssemblyResolve eventine programınız çalışır çalışmaz register olmanız gerekir. Event
Handler methodunu register etmenizden önce ilgili typelardan birisine
erişilmeye çalışılırsa kendiniz yükleme işlemine müdahale edemezsiniz.
Peki tek dosya
olarak deploy etmek için nasıl bir yükleme yöntemi geliştirebilirsiniz. İlgili
DLL dosyalarını geliştirme anında projenize standart yöntemler ile referans ekler kullanırsınız. Ancak aynı
zamanda bu şekilde dağıtmak istediğiniz dll dosyalarını projenize embedded
resource olarak eklersiniz ve custom assembly load yaptığınız noktada
çalışmakta alan exe dosyasınıza gömülü olan resource dosyasını okur ve hiç
diske kaydetmeden assembly olarak yüklersiniz.
Şimdi bu
senaryoyu bir örnek proje ile adım adım gerçekleştirelim.
Örnek bir harici
kütüphane olarak SharpZipLib (http://www.icsharpcode.net/OpenSource/SharpZipLib)
kütüphanesini seçtim. Proje gereği bu kütüphaneyi kullanan bir windows
uygulamam var. Büyük bir yazılım ürünün client tarafındaki bazı yapılandırmalarını
yapmak için kullanacağım küçük bir tool geliştiriyorum ve bu tool da aldığı
sıkıştırılmış biçimli yapılandırma dosyasını okuyup client sistemini ana
uygulamamız için hazır hale getiriyor.
Yeni bir Windows
Application projesi oluşturdum. ICSharpCode.SharpZipLib kütüphanesini Add Reference
diyerek referanslara ekledim. Uygulama açılınca çalıştırılacak forma bir buton
ekleyip sharpziplib kütüphanesini kullanarak ilgili işlemleri yapan kodu
yazdım.
static void Main()
{
…
Application.Run(new Form1());
}
namespace
ClientSetupTool
{
public partial class Form1 : Form
{
public
Form1()
{
InitializeComponent();
}
private
void button1_Click(object
sender, EventArgs e)
{
ICSharpCode.SharpZipLib.Zip.FastZip fz = new
ICSharpCode.SharpZipLib.Zip.FastZip();
//sharpziplib
kütüphanesinden bir type'i kullanarak sharpziplib kütüphanesinin
//.net
runtime'i tarafından yüklenmeye çalışılmasını sağlamak için yazılmış
//fonksiyonel
olmayan bir method
}
}
}
Projeyi bu
şekilde derleyip çalıştırdığımda hiç bir sorun (tür yükleme hatası vs) olmayacaktır.
Uygulamanın
çalışacağı bilgisayara esas uygulamamız (ClientSetupTool.exe) ve yanından
sharpziplib kütüphanesi (ICSharpCode.SharpZipLib.dll) kopyalanarak
çalıştırılmalıdır. Ancak ICSharpCode.SharpZipLib.dll dosyası gönderilmez ve
ClientSetupTool.exe tek başına istemci bilgisayara yüklenir ve çalıştırılırsa
aşağıdaki hata mesajı alınacaktır.
System.IO.FileNotFoundException: Could not load
file or assembly 'ICSharpCode.SharpZipLib, Version=0.85.5.452, Culture=neutral,
PublicKeyToken=1b03e6acf1164f73' or one of its dependencies. The system cannot
find the file specified.
File name: 'ICSharpCode.SharpZipLib,
Version=0.85.5.452, Culture=neutral, PublicKeyToken=1b03e6acf1164f73'
Bu durumda biz
sharpzip kütüphanesinin dll dosyasını projemize embedded resource olarak
ekleyelim.
Ben projeye DLLResources
adında yeni bir klasör ekledim. Add >
Existing Item seçeneği ile ICSharpCode.SharpZipLib.dll dosyasını ekledim. ICSharpCode.SharpZipLib.dll
dosyasını seçip klavyeden F4 tuşuna basara dosyanın özelliklerini gösterecek
şekilde Properties penceresine geçtim. Buradan Build Action seçeneğini Embedded
Resource olarak seçtim.
Artık projemiz
derlendiği zaman binary olarak ICSharpCode.SharpZipLib.dll dosyası ClientSetupTool.exe
içerisine dahil edilecek.
Bir Assembly
yüklemesini .Net Runtime yapamadığı zaman (aynı klasörde,GAC’da aradıktan
sonra) AssemblyResolve metodunu çağırır. Bu sebeble projemiz derlendiğinde ICSharpCode.SharpZipLib.dll
dosyasının derleme output klasörüne düşmemesi gerekir. Bunun için reference
altında ICSharpCode.SharpZipLib.dll dosyasını seçin F4 ile properties
penceresine geçin ve copy local seçeneğini false yapın. Bu durumda .Net Runtime
ICSharpCode.SharpZipLib.dll kütüphanesini bulamayacak ve AssemblyResolve ile işi
bize devredecektir. Referansın ekli kalmasının sebebi derleme anında compilerin
dll dosyasını bulabilmesi ve projenin derlenebilmesi içindir.
using System;
using
System.Collections.Generic;
using
System.Windows.Forms;
namespace
ClientSetupTool
{
static class Program
{
static Dictionary<string,
System.Reflection.Assembly>
yuklenenAssemblyList
= new
Dictionary<string,
System.Reflection.Assembly>();
[STAThread]
static void Main()
{
AppDomain.CurrentDomain.AssemblyResolve
+= CurrentDomain_AssemblyResolve;
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
static
System.Reflection.Assembly
CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
System.Reflection.AssemblyName asmName = new
System.Reflection.AssemblyName(args.Name);
System.Reflection.Assembly asm = null;
if
(!yuklenenAssemblyList.TryGetValue(asmName.Name, out
asm))
{
string
resourceName = "ClientSetupTool.DLLResources."
+ asmName.Name + ".dll";
System.IO.Stream dllStream = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName);
byte[]
asmData = new System.IO.BinaryReader(dllStream).ReadBytes((int)dllStream.Length);
dllStream.Close();
asm = System.Reflection.Assembly.Load(asmData);
yuklenenAssemblyList[asmName.Name] = asm;
}
return
asm;
}
}
}
Program.cs içerisinde
Main metodu içerisine aşağıdaki satırı ekleyerek .NET Runtime yükleyemediğinde
assembly yüklemesi için bizim methodumuzn çalışmasını sağlıyoruz. Eğer
uygulamanızı çalıştırırken aynı klasörde sharpziplib (yada siz hangi dll i
embedded olarak kullanacaksanız) dll dosyasını uygulama ile aynı klasöre
koyarsanız .net runtime otomatik oradan yükleyecektir ve sizin methodunuz
çalışmayacaktır.
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve
CurrentDomain_AssemblyResolve
metodu ise gayet açık aslında, ilgili assembly dosyasını bulur (kaynağımız
burada embedded resource) Assembly.Load
methodu ile yükler ve geriye döner. args.Name parametresi ile o an yüklenmeye çalışılan
assembly adını öğreniyoruz ve zaten yüklediklerimizi tuttuğumuz dictionary
listemizden kontrol ettikten sonra getmanifestresourcestream methodu ile exe
içine gömülü resource u stream olarak okuyoruz ve Assembly.Load ile yüklüyoruz.
Kodun içinde ki typelerın namespacelerini using ile eklemek yerine uzun uzun
yazdım tylerin BCL’de ki yerlerini görebilmeniz için.
Uygulamamızı
sadece ClientSetupTool.exe olarak istemci bilgisayarlara yükleyerek
çalıştırabiliriz. Bu şekilde aslında harici bir dll dosyasını kullanan
uygulamamızı bir zip dosyaları vs gibi dağıtmak yerine gayet basit bir şekilde
tek dosya, kopyala, yapıştır, çalıştır şeklinde çalıştırılabilir olmasını
sağladık.
Uygulamalarınızda
faydası olabileceğini düşündüğüm bu yöntemi manuel resource eklemek yerine veya
büyük bir ekipte her yazılımcıya bu disiplini kazandırmak yerine continuous integration
sürecinizde de derlenmmiş bir assembly dosyasına başka bir assembly dosyasını
(harici kütüphaneniz) embedded resource olarak ekleyebilirsiniz.
Cengiz Han
cengiz@cengizhan.com