Makale Özeti

Global assembly ler ve konfigürasyon örnekleri

Makale

Evet, madem teoride shared assembly ler nasıl çalışıyor gördük,  o zaman bir parça da işin pratiğine bakalım . Daha önceki örneklerde kullandığımız kaynak kodlarını bu yazıda da kullanacağız. Aslında bu yazıda shared assembly kavramı ile birlikte, binding mekanizmasına da oldukça detaylı olarak bakıyor olacağız.
Bu yazıdaki çalışmaları yapmadan önce yapmanız gerekenler:

  • Bir önceki uygulamada C:\ altında GLOBAL isimli bir dizin açmıştık, şimdi yine c:\ altında GLOBAL2 isimli bir dizin açalım
  • Bu dizine anahtar.snk, anahtar.cs, globalclient.cs, globaldll.cs dosyalarını kopyalayalım.
İlk olarak geçen yazıdan kafanıza takılmış olabilecek sorulara yanıt verelim. Bu kadar versiyon bilgisi anlattık, iyi güzel, ama biz bu versiyonları nereye yazıyoruz ?Daha önce dll imizi derlerken anahtar.cs isimi bir dosya kullanmıştık, ve bu dosyanın içinde anahtar.snk nın adını vermiştik. Aynı dosya içinde versiyon bilgisini de vereceğiz. En başta bu dosyanın yeni halini görelim:

//anahtar.cs

using System.Reflection;

using System.Runtime.CompilerServices;

[assembly: AssemblyKeyFile("anahtar.snk")]

[assembly: AssemblyVersion("1.0.0.0")]

//anahtar.cs bitti

Burada önceden olmayan bu yeni satır sayesinde istediğimiz versiyonu veriyoruz. (Tabii VS.NET gibi yazılım geliştirme araçları gerekli ayarlar ile, bu değerleri isterseniz sizin için otomatik olarak düzenliyor, ama biz örneklerimizde elle gireceğiz ).Şimdi derlenmiş dlli GAC içine yerleştirmek için kullandığımız gacutil.exe ye geri dönelim. Bu program sadece dllleri yerleştirmek için değil, eğer gerekirse  onları GACdan çıkarmak için de kullanılabiliyor.
Biz en baştan her şeyi yeniden yapacağımız için bu program ile önce assembly mizi GACdan siliyoruz. ( aslında  bunu yapmak zorunda değildik aslında, ama elinizin .net Framework ile gelen araçlara alışmasını istedim :)
O zaman komutumuzu verelim ve dll i GAC dan silelim:

C:\>gacutil  /u globaldll

Microsoft (R) .NET Global Assembly Cache Utility.  Version 1.0.2914.16

Copyright (C) Microsoft Corp. 1998-2001. All rights reserved.

Uninstalled: globaldll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=4924aa1

6077b6f5b, Custom=null

Number of items uninstalled = 1

Number of failures = 0

Bakın burada hep bahsedip durduğumuz bir sürü kavramı da somut olarak görmüş olduk. Özellikle PublicKeyToken kısmına dikkat !! Bu arada komutu verirken dllin değil assemblynin ismini kullandığımıza da dikkat edin. Yani globaldll.dll yazmadık...Şimdi artık globaldll diye bir assembly GAC içinde yok. Hemen bu durumu test edelim, ve bundan bir önceki  örnek çalışmada yazdığımız globalclient.exe yi çalıştıralım :
C:\>C:\GLOBAL\globalclient.exe

Unhandled Exception: System.IO.FileNotFoundException: File or assembly name globaldll, or one of its dependencies, was not found.

File name: "globaldll"

   at clientapp.mainone.Main()

Fusion log follows:

LOG: Post policy reference: globaldll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=4924aa16077b6f5b

LOG: Attempting download of new URL file:///C:/GLOBAL/globaldll.DLL.

LOG: Attempting download of new URL file:///C:/GLOBAL/globaldll/globaldll.DLL.

LOG: Attempting download of new URL file:///C:/GLOBAL/globaldll.EXE.

LOG: Attempting download of new URL file:///C:/GLOBAL/globaldll/globaldll.EXE.

Beklediğimiz gibi program çakıldı. Peki o zaman şimdi versiyon bilgisi ile birlikte dllimizi yeniden oluşturup GAC a atacağız. Ancak bunu yapmadan önce gelin işleri biraz daha somutlaştırmak için, kaynak kodunda ufak bir değişiklik yapalım ve dllin kendi versiyonu ekrana verebilmesini sağlayalım. Bu işi yapacak şekilde hazırlanmış kaynak kodu şöyle bir şey olacaktır:

//globaldll.cs

 

using System;

namespace sharedones

{

       public class container

       {

             public int member1;

             public string member2;

             public container()

             {

             this.member1=10;

             this.member2="Ben bir public elemanim! VERSIYON: 1.0.0.0 ";

             }

       }

}

//globaldll.cs bitti

Güzel, şimdi de bu dlli derleyelim. Bu noktada artık environment variablelar ile ilgili uyarıyı yapmıyorum, eğer daha önce dikkatinizi çekmediyse bu seride 4. makaleye bir göz atın :)

C:\>csc /t:library /out:c:\global2\globaldll.dll  c:\global2\anahtar.cs c:\global2\globaldll.cs

Komutu ile global2 altında dllimizi oluşturduk . Önceden yaptığımız işlemden farklı olarak bu defa versiyon bilgisi de içeren bir dllimiz var.Şimdi de bu dlli kullanacak olan istemci uygulamamızı yazalım ve derleyelim:

//globalclient.cs

using System;

using sharedones;

namespace clientapp

{

            public class clientclass

            {

            public container mycontainer;

            public clientclass()

                        {

                        this.mycontainer=new container();

                        }

            }

            public class mainone

            {

            public static void Main()

            {

            clientclass myclient=new clientclass();

            Console.WriteLine(myclient.mycontainer.member2);

            }

            }

}

//globalclient.cs bitti

Buradaki kodda hiçbir değişiklik yapmadık, şimdi bu kodu da derleyelim:

C:\>csc /t:exe /out:c:\global2\globalclient.exe /reference:c:\global2\globaldll.dll c:\global2\globalclient.cs

Tamam, artık istemcimiz de var.

Şimdi gacutil ile 1.0.0.0 versiyonlu assemblyi GAC içine yerleştirelim:

C:\>gacutil /i c:\global2\globaldll.dll

Microsoft (R) .NET Global Assembly Cache Utility.  Version 1.0.2914.16

Copyright (C) Microsoft Corp. 1998-2001. All rights reserved.

Assembly successfully added to the cache

İşte bu kadar. Artık GAC içinde bizim verdiğimiz versiyon numarası ile bir assembly var. Eğer GAC  içinde bulup sağ tuşla özelliklerine bakarsanız, versiyon numarası olarak 1.0.0.0 görürsünüz. Bir önceki çalışmamızda bu numara 0.0.0.0 olarak kalmıştı.Madem istediğimizi yapıp, dilediğimiz versiyonu yerleştirdik,  o zaman biraz test edelim . Global2 dizini altından globaldll.dll i silip, sonrada istemci programımızı çalıştıralım: (lütfen bu adımı atlamayın ve bu dlli silin )

C:\>c:\global2\globalclient.exe

Ben bir public elemanim! VERSIYON: 1.0.0.0

Harika, demek ki her şey yolunda. Buraya kadar bir sorun çıkmadığına göre, şimdi de bu dllin yeni bir versiyonunu hazırlayalım. :

//globaldll1.0.1.cs

 

using System;

namespace sharedones

{

       public class container

       {

             public int member1;

             public string member2;

             public container()

             {

             this.member1=10;

             this.member2="Ben bir public elemanim! VERSIYON: 1.0.1.0 ";

             }

       }

}

//globaldll1.0.1.cs bitti

Şimdi bu dll için versiyon bilgisini de anahtar1.0.1.cs içinde yazalım:

//anahtar1.0.1.cs

using System.Reflection;

using System.Runtime.CompilerServices;

[assembly: AssemblyKeyFile("anahtar.snk")]

[assembly: AssemblyVersion("1.0.1.0")]

//anahtar1.0.1.cs bitti

Bu yeni versiyonu da derleyelim ( bu arada tüm dosyalarımızı düzenli bir programcı olarak global2 altında tutuyoruz :)

C:\>csc /t:library /out:c:\global2\globaldll.dll c:\global2\anahtar1.0.1.cs c:\global2\globaldll1.0.1.cs

İkinci versiyonu da GAC içine koyalım:

C:\>gacutil /i c:\global2\globaldll.dll

Microsoft (R) .NET Global Assembly Cache Utility.  Version 1.0.2914.16

Copyright (C) Microsoft Corp. 1998-2001. All rights reserved.

Assembly successfully added to the cache

Tamam, artık yeni versiyon da GAC içinde. Şimsi global2 altından bu versiyonu da silin. Artık GAC içinde iki değişik versiyon var, ama neden var ? Bu iki dll sayesinde, bir yazılımın aynı assemblynin nasıl iki farklı versiyonunu kullandığını göreceğiz. Şimdi bu noktada bir parça teorik bilgiye ihtiyacımız var, o zaman hep birlikte assemby binding işlemine yani aranan kaynakların bulunmasıne biraz yakından bakalım.

Binding ve konfigürasyon dosyaları

Herhangi bir program bir assemblye ulaşmak isterse, ve bu assembly bir shared assemby ise, o zaman versiyon numaraları ve uyumluluk gibi kavramların devreye girdiğini biliyoruz. (bir kez daha hatırlayalım: private assemblyler için versiyon politikaları uygulanmaz) Ancak bu versiyon politikaları nasıl belirlenecek ? Her ne kadar .NET belli öneriler getirse bile bu kullanıcıları kısıtlamaya dönüşmesin diye bu ayarları bizim yapmamızı sağlayan bir mekanizma olması gerekmiyor mu ? Evet, gerekiyor, ve zaten böyle bir mekanizma .NET ile geliyor. Daha önce bir private assembly nin yerini belirtmek için kullandığımız XML formatındaki dosyayı hatırladınız mı  ? Birazdan o dosyayı yeniden, ama bu sefer versiyon politikasını belirlemek için kullanacağız.
Ama önce biraz bilgi:
.NET size versiyon uyumluğu için getirdiği önerileri kendisi otomatik olarak uyguluyor. Ancak bu otomatik olarak uygulama işlemi henüz 100% kesinleşmiş kurallar edinemedi. Beta 1 de durum şuydu:
Eğer siz diyelim  1.0.0.0 numaralı bir assembly i arayan bir istemci program çalıştırırsanız, runtime de GAC içinde 1.0.0.6 veya 1.0.1.2 gibi bir versiyon bulursa, o zaman otomatik olarak bu versiyona bağlanılıyordu. Bu mekanizmanın adı da default version policy .
Ancak beta 2 de bu durum değişti. Her ne kadar dökümanlar hala bu mekanizmanın çalıştığını söylüyorsa bile, benim sistemimde default version policy çalışmadı. Yani yukarıdaki  dlller GAC içine konduktan sonra, c:\global2\globalclient.exe  yi çalıştırdığımda sistem default version policyi uygulamadı.
Bayağı bir araştırdım, ancak bu konu şu anda hala bir muamma, açığa kavuşunca bu yazıyı güncellemeye karar verdim.
Ancak versiyon politikası açısından tek kural bu değil tabii. Bir çok ayar var. Bunlar da belirli konfigürasyon dosyaları ile belirleniyor. Şimdi bu dosyalara bakalım.
Runtime, binding işlemi sırasında versiyon kurallarını uygulamak için default version policy ile beraber aşağıdaki dosyalardan da faydalanır:

Application Configuration File ( uygulama konfigürasyon dosyası )

Daha önce de faydalandığımız bu dosya, çalışacak programın .exe dosyası ile aynı dizinde duruyor, ve programa ait bir çok ayarı belirlememizi sağlıyor.Bunlardan birisi de, programın kullandığı bir shared assembly nin istenilen bir versiyonuna redirect etmek, yani versiyon yönlendirmesi.

(Bu dosya program_adi.exe.config ismi ile bulunmalı)

Eğer bizim istemci programımız belirli bir dll versiyonu ile derlendiyse, (mesela globalclient.exe 1.0.0.0 ile derlendi) ve biz daha sonra programı değişik bir versiyon ile çalıştırmak için yeniden derlemek istemiyorsak, o zaman uygulama konfigürasyon dosyası sayesinde bunu yapabiliyoruz. Birazdan bunun örneğini göreceğiz. Uygulama ayar dosyası, sadece yanında yer aldığı exe için geçerli ayarları değiştirir. Peki ama biz bir bilgisayardaki tüm programları istediğimiz bir assembly nin belli bir versiyonuna yönlendirmek istersek ne olacak ? Teker teker hepsinin yanına bir application configuration file mı yazacağız ? Tabii ki hayır, işte cevabımız :

Machine Configuration File (makina ayarları dosyası )

Bu dosya bende C:\WINNT\Microsoft.NET\Framework\v1.0.2914\CONFIG\ altında bulunuyor.Adı da machine.config . Bu dosyada belirtilen herhangi bir versiyon politikası, belirtilen assembly i kullanan tüm programlar için otomatik olarak geçerli olur. Uyarı: bu dosya .NET framework açısından oldukça önemlidir, eğer üzerinde bir değişiklik yapmak isterseniz, mutlaka bir yedek alın.

Tabii bu da her zaman tercih edilecek bir durum değil. Ancak bir dll içinde kesin bir bug bulursanız, ya da bu dll içindeki bir fonksiyonu değiştirip bu değişikliği sistemdeki tüm istemci yazılımlara yansıtmak isteserseniz, bu dosyayı kullanbilirsiniz.

Publisher Configuration File (yayıncı ayar dosyası )

Bu dosya  assemblyi yazan kişi tarafından assembly nin bir parçası olarak GAC içine yerleştirilir, ve aynı machine configuration file gibi bu assembly i  kullanan tüm istemci programları etkiler. Üstelik etki açısından da uygulama ayar dosyasından üstündür
Temel olarak bu üç dosya ile versiyon politikası belirlenir. Şimdi elimizdeki örnekleri kullanarak biraz pratik yapalım .

Global2 altındaki globalclient.exe yi çalıştıralım, bakalım ne olacak ??

C:\>c:\global2\globalclient.exe

Ben bir public elemanim! VERSIYON: 1.0.0.0

Hmm, demek ki default version policy beta 2 ile çalışır durumda değil ( bu arada yukarıdaki çıktı benim sistemimdeki binding i gösteriyor, eğer sizde çıktı değişikse lütfen bana haber verin :)
O zaman biz kendi ayarlarımızı devreye sokalım, ve 1.0.0.0 versiyonlu assembly için derlenen globalclient.exe yi 1.0.1.0 a yönlendirelim. İşte bu iş için kullanacağımız uygulama ayar dosyası:

<?xml version="1.0"?>

<configuration>

   <runtime>

    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">

      <dependentAssembly>

        <assemblyIdentity name="globaldll" publicKeyToken="4924aa16077b6f5b" />

        <bindingRedirect oldVersion="1.0.0.0" newVersion="1.0.1.0" />

      </dependentAssembly>

    </assemblyBinding>

  </runtime>

</configuration>

Dosyadaki tagler yeterince anlaşılır isimler taşıyor sanıyorum . Peki ama orada yazılı olan public keyi nereden öğreneceğiz diyorsanız,

C:\>Gacutil.exe /L

ile tüm GAC içinde bulunan assembly lerin listesini ve diğer bilgilerini alabilirsiniz.

Bu dosyayı globalclient.exe.config adı ile global2 altına kaydedip tekrar programı çalıştırıyoruz.

C:\>c:\global2\globalclient.exe

Ben bir public elemanim! VERSIYON: 1.0.1.0

İşte bu kadar!!! Gördünüz gibi artık istediğimiz versiyonu kullanarak çalışmasını sağlayabiliriz. Ancak unutmadan eklemem gereken bir başka nokta daha var, XML dosyaları format olarak bir parça kaprislidir, eğer işler burada olduğu gibi yürümezse, lütfen bu dosyanın yazımını kontrol edin.
Bu noktada bir parça geriye dönüp bakmakta yarar var. .NET üzerinde assemblyler konusunu burada noktalıyoruz. Burada en temel ve en gerekli olduğunu düşündüğüm konuları vermeye çalıştım. Ancak bu temelleri kavrarsanız, ileride karşınıza çıkacak hiçbir konunun sizi fazla zorlamayacağına eminim .
Burada ana hatlarını işlememize rağmen, .NET ile önceden son derece komplex ve kafa  karıştırıcı olabilecek , hatta bir çoğu da mümkün olmayan bir sürü işi, son derece rahat olarak yapabiliyoruz.Umarım bu yazılar sizin için .NET altında kod paylaşımını kolay kavranabilir bir hale getirir. Burada anlatmadığım, ama ileride mutlaka yazacağım, ve kesinlikle çok hoşunuza gidecek bir sürü olanak var :)
.NET in temelleri serisini burada bitirmeyi düşünmüyorum, ilerleyen yazılarda, multithreading, application domains, web services gibi konuları da yazmayı planlıyorum.
Eğer .NET in teknik altyapısını kavrayabilirseniz, inanın hangi dil ile çalıştığınızın da pek bir önemi kalmayacaktır.
Lütfen yazılar hakkındaki yorumlarınız veya öğrenmek istediğiniz konuları yazmaktan çekinmeyin.
Yeni yazılarda görüşmek üzere, hoşçakalın ...

Şeref Arıkan