NET`te Kaynak (Resource) Kullanımı

Transkript

NET`te Kaynak (Resource) Kullanımı
.NET’te Kaynak (Resource) Kullanımı
Kaan Aslan
18 Ocak 2007
Yeni kuşak modern uygulamalar pek çok görsel ve işitsel öğeler içermektedir.
Menü elemanlarındaki ve araç çubuklarındaki küçük simgeler, çeşitli ses efektleri,
yazılar ve resimler uygulamalar içerisinde sıklıkla karşılaştığımız öğelerden yalnızca
bazıları. Bu tür görsel ve işitsel öğeler aslında belli formata sahip dosyalar içerisinde
(örneğin .ico, .bmp, .wav gibi) tutulan bilgilerdir. Bu öğerlerin ayrı dosyalarda
tutulması ve uygulama çalışırken bu dosyaların içerisinden alınması mümkün olsa da
tavsiye edilmez. Çünkü ayrık olan bu dosyaların silinmesi, isminin değiştirilmesi gibi
durumlarda uygulama çalışamaz duruma gelebilir. Çok sayıda dosyayı yönetmek
yerine bu küçük dosyaların içeriklerini .exe ve .dll uzantılı assembly dosyalarının içine
gömmek uygun bir yöntemdir. Kaynak kullanımı bunu sağlamaya yönelik olarak
tasarlanmış bir mekanizmadır.
Bir .NET PE dosyası yönetilen (managed) ve yönetilmeyen (unmanaged)
kaynakları içerebilir. Yönetilmeyen kaynakların organizasyonu doğal kod içeren PE
dosyalarında olduğu gibidir. Yönetilen kaynaklar ise PE formatında birer metadata
olarak tutulmaktadır. Bu nedenle yönetilen kaynaklara manifest resource da denir.
Bir kaynağı csc.exe derleyicisi ile assembly dosyası içerisine gömmek için
/resource (ya da kısaca /res) seçeneği kullanılır. Örneğin:
csc /resource:a.txt /resource:b.jpg sample.cs
Burada a.txt ve b.jpg dosyaları assembly dosyasına kaynak olarak aktarılır. /resource
seçeneğinde kaynak isminden sonra erişim belirleyicisi de ekleyebilirsiniz. Erişim
belirleyicisi public ya da private olabilir. Örneğin:
csc /resource:a.txt, private /resource:b.jpg sample.cs
Varsayılan erişim belirleyicisi public biçimdedir. Bu durumda assembly dosyası
içerisindeki kaynakları başka bir assembly’den kullanabiliriz. Halbuki private
kaynaklar ancak aynı assembly içerisinden kullanılabilir.
Herhangi bir dosyanın içeriği kaynak olarak gömülebilir. Derleyici ya da CLR
kaynağı oluşturan bilginin ne olduğuyla ilgilenmez. Sistem için kaynak byte
topluluğundan oluşan bir bilgi yığınıdır. Assembly dosyasındaki kaynakları ildasm ile
görüntüleyebilirsiniz:
ildasm sample.exe
Ağaçtan MANIFEST bölümüne tıkladığınızda assembly dosyasının manifest bölümü
görüntülenecektir:
1
Kaan Aslan Makale Arşivi – www.kaanaslan.net
Arakod sembolik makina dilinde (ILASM) .mresource (manifest resource
sözcüklerinden kısaltmadır) direktifi yönetilen kaynak bildirimleri için kullanılıyor.
Örneğin bir dosyanın içeriğini ILASM’de aşağıdaki gibi kaynak olarak assembly
dosyasına gömebiliriz
.assembly Test {}
.assembly extern mscorlib {}
.namespace CSD
{
.class App
{
.method public static void Main()
{
.entrypoint
ret
}
}
.mresource public Test.txt
{
}
}
2
Kaan Aslan Makale Arşivi – www.kaanaslan.net
Burada kaynağı public yapmakla başka bir assembly’den kullanıma açmış oluyoruz.
Yönetilen kaynakların organizasyonu diğer öğelerde olduğu gibidir. Bilindiği gibi
tüm metadata öğeleri ilişkisel bir veritabanı biçiminde PE dosyasına yazılmaktadır.
Yani her metadata öğesinin bir tablosu vardır. Tablo sütunlardan ve satırlardan
oluşur. Tabloların sütun bilgileri ECMA 335 Common Language Infrastructure
standartlarında tanımlanmıştır. Burada metadata konusunda kısa bir anımsatma
yapmayı uygun görüyoruz. PE formatının IMAGE_OPTIONAL_HEADER bölümünün
sonundaki IMAGE_DATA_DIRECTORY dizisinin 15’inci elemanı (yani 14’üncü
indeksteki elemanı) CLR başlığının yerini tutar. CLR başlığının içerisinde de
Metadata başlığının yeri tutulmaktadır. Metadata başlığınının sonunda Stream
tabloları vardır. #~ isimli Stream tablosu ise assembly metadata öğelerinden
oluşmaktadır. Assembly metadata öğeleri tablolar halinde organize edilmiştir.
Tablolar sütunlardan ve satırlardan oluşur. İşte ManifestResource isimli tablo gömülü
kaynak bilgilerini tutmaktadır. CFF Explorer yardımcı programı [1] ile bu durumu
gözlemleyebilirsiniz. PE formatının metadata organizasyonu şekilsel olarak şöyledir:
Assembly dosyası içerisindeki kaynakların program içerisinden elde edilmesi tipik
olarak şu adımlardan geçilerek yapılmaktadır:
1. Öncelikle kaynakların içerisinde bulunduğu Assembly’ye ilişkin Assembly sınıf
nesnesinin elde edilmesi gerekir. Assembly sınıf nesnesi Assembly sınıfının static
GetAssembly, GetCallingAssembly, GetExecutingAssembly, GetEntryAssembly,
LoadFile, LoadFrom gibi metotlarıyla elde edilir. Bu metotlar arasındaki farklar için
3
Kaan Aslan Makale Arşivi – www.kaanaslan.net
MSDN dökümanlarına başvurabilirsiniz. Örneğin GetAssembly bir türün Type
referansını parametre olarak alıp o türün içinde bulunduğu Assembly nesnesini
vermektedir.
Assembly a = GetAssembly(typeof(AClassName));
2. Assembly sınıfının GetManifestResource isimli static olmayan metoduyla ilgili
kaynak assembly içerisinden çekilerek kullanıma hazır hale getirilir. Bu metot bizden
kaynağın ismini alarak kaynağa erişmemizi sağlayan bir Stream verir:
Stream s1 = a.GetManifestResourceStream("a.txt");
Stream s2 = a.GetManifestResourceStream("b.jpg");
3. Elde edilen Stream kaynak türüne göre kullanıma hazır hale getirilir:
StreamReader sr = new StreamReader(s1);
Bitmap bmp = new Bitmap(s2);
Ayrıca Assembly sınıfının GetManifestResourceInfo, GetManifestResourceNames
metotlarını da kaynak belirlemesi yapmak için kullanabilirsiniz.
Kaynakları assembly içerisine gömme işlemi Visual Studio IDE’si kullanılarak kolay
biçimde gerçekleştirilebilir. Bunun için önce kaynağa ilişkin dosya projeye eklenir:
Sonra elemanın Özellikler (Properties) menüsüne girilerek Build Action seçeneği
Embedded Resource olarak girilir.
IDE kaynakların birer kopyasını proje dizinine çekecek ve ilgili dosyaların içeriklerini
kaynak olarak assembly içerisine gömecektir.
4
Kaan Aslan Makale Arşivi – www.kaanaslan.net
XML Tabanlı Kaynaklar
Kaynakların yukarıdaki gibi tek tek assembly dosyasına gömülüp oradan çekilerek
kullanılması zahmetlidir. Kaynak işlemlerini hem daha kolay hem de daha esnek hale
getirmek için XML tabanlı kaynaklar düşünülmüştür. Bu yöntemde kaynaklar bir metin
dosyasına XML olarak yazılır. Daha sonra resgen isimli yardımcı programla ikili
(binary) bir formata dönüştürülür ve bu haliyle assembly’ye gömülür. Böylece çeşitli
kaynaklar biraraya getirilerek tek bir dosya halinde assembly dosyasına aktarılmış
olur. .NET içerisinde bu biçimde gömülmüş olan kaynakların geri alınmasını sağlayan
çeşitli sınıflar vardır.
Kaynakların tanımlandığı XML dosyasının uzantısı .resx olarak verilmektedir.
resgen.exe programının ürettiği ikili dosya ise .resources uzantılıdır. XML tabanlı
kaynaklarımızı test.resx isimli bir dosyaya yazdığımızı düşünelim. resgen.exe yalın
olarak şöyle kullanılabilir:
resgen test.resx
bu işlemin sonucu olarak test.resources dosyası üretilecektir. Bu dosya da yukarıda
açıklandığı gibi assembly içerisine gömülebilir:
csc /resource:test.resources sample.cs
XML tabanlı .resx dosyaları genellikle elle yazılmaz. Visual Studio IDE’si .resx
dosyalarının görsel bir biçimde oluşturulmasına izin vermektedir. Projeye bir .resx
dosyası ekledikten sonra build işlemi yaparsak Visual Studio IDE’si .resx dosyasını
resgen.exe yardımcı programıyla derleyerek elde edilen dosyayı kaynağa
gömecektir.
Şimdi ikili biçime dönüştürülerek Assembly içerisine gömülmüş olan kaynaklardaki
bilgilerin alınması üzerinde biraz duralım. Bu işlem en kolay bir biçimde
ResourceManager sınıfıyla yapılır. Örneğin, a.txt ve b.jpg dosyalarını IDE kullanarak
görsel biçimde .resx uzantılı XML dosyasına eklediğimizi varsayalım. Bu kaynakların
oradan alınarak kullanılması şöyle yapılabilir:
ResourceManager rm = new ResourceManager("test",
Assembly.GetExecutingAssembly());
string s = (string) rm.GetObject("a");
Image i = (Image) rm.GetObject("b");
ResourceManager sınıfının başlanıç metodunun (constructor) birinci parametresi
.resources dosyasının ismini, ikinci parametresi ise kaynağın bulunduğu assembly
nesnesini almaktadır. Biz örneğimizde buna test dedik, fakat bu isim noktalı biçimde
daha uzun olabilir. Bu konuda tereddütte kalırsanız ildasm programından
faydalanmalısınız. Örneğin IDE tipik olarak uygulamaya ilişkin kaynakları Properties
5
Kaan Aslan Makale Arşivi – www.kaanaslan.net
dizininin
altına
yerleştirdiği
için
kaynak
dosyasının
tam
ismi
XXX.Properties.Resources.YYY biçiminde olur. (Burada XXX projenizin isim alanını
YYY ise .resources uzantılı dosyanın ismini belirtiyor.) Sınıfın GetObject metotları
kaynakları almak için kullanılır.
.NET sınıf kütüphanesinde .resources uzantılı gömülü kaynaklardan bilgi almak ve
onlara doğrudan bilgi yazmak için daha aşağı seviyeli ResourceReader ve
ResourceWriter sınıfları da bulunmaktadır. Aslında ResourceManager sınıfı da kendi
içerisinde dolaylı olarak ResourceReader sınıfını kullanmaktadır. ResourceReader
sınıfı ile kaynakları aşağıdaki gibi alabilirsiniz:
Stream s;
s = Assembly.GetExecutingAssembly().GetManifestResourceStream
("test.resources");
ResourceReader rr = new ResourceReader(s);
IDictionaryEnumerator ide = rr.GetEnumerator();
while (ide.MoveNext())
{
switch (ide.Key.ToString())
{
case "a":
//...;
break;
case "b":
//...
break;
}
}
rr.Close();
.resource dosyalarına da benzer biçimde ResourceWriter sınıfı ile yazma
yapabilirsiniz:
ResourceWriter rw = new ResourceWriter("test.resources");
rw.AddResource("Mesaj", "Bu bir denemedir");
//...
rw.Close();
resgen.exe yardımcı programı da muhtemelen kendi içerisinde bu sınıfları
kullanmaktadır.
.resx uzantılı XML dosyalarına görsel bir arayüzle IDE tarafından yazma yapıldığını
belirtmiştik. Programcı isterse bu dosyalara ResXResourceReader ve
ResXResourceWriter sınıfları ile okuma ve yazma yapabilir.
6
Kaan Aslan Makale Arşivi – www.kaanaslan.net
ResourceManager, ResourceReader, ResourceWriter, ResXResourceReader ve
ResXResourceWriter
sınıflarının
ayrıntılı
kullanımları
için
dökümanlara
başvurabilirsiniz. Biz burada yalnızca konunun ana hatlarını açıklamış olduk.
[1]
Daniel Pistelli tarafından yazılmış Explorer Suite içerisinde bulunan yardımcı
program. http://www.ntcore.com/exsuite.php adresinden indirebilirsiniz.
7
Kaan Aslan Makale Arşivi – www.kaanaslan.net

Benzer belgeler