Makale Özeti

Bu yazıda, .NET Framework’ün en önemli bileşeni olan Common Language Runtime’ı inceleyeceğiz. İlk olarak Common language Runtime’ın görevi tanımlayacağız, çalışma mantığını inceleyeceğiz ve CLR ile ilgili pek çok detayı inceleyeceğiz.

Makale

Common Language Runtime

Common Language Runtime

Bu yazıda, .NET Framework’ün en önemli bileşeni olan Common Language Runtime’ı inceleyeceğiz. İlk olarak Common language Runtime’ın görevi tanımlayacağız, çalışma mantığını inceleyeceğiz ve CLR ile ilgili pek çok detayı inceleyeceğiz. 

Common Language Runtime (CLR)
CLR için girişte .NET Framewok’ün en önemli bileşeni dedik, peki CLR’ı neden en önemli bileşen olarak görüyoruz. CLR’ın görevlerini incelersek neden “en önemli” bileşen diye bahsettiğimizi ve bunda ne derece haklı olduğumuzu görebiliriz.

CLR, .NET Framework’ün, .NET dilleri ile yazılmış kodların yönetimi (manage) ve çalıştırılması (execution) görevlerini üstlenmiş bileşenidir. Java’daki Virtual Machine’e benzetebiliriz. CLR, nesneleri aktive eder, nesneler üzerinde güvenlik denetimleri gerçekleştirir, belleğe aktarır, çalıştırır ve garbage-collection işlemini gerçekleştirir. Peki bunları nasıl yapar? Bu sorunun cevabını ileride vereceğiz.

CLR Ortamı
.NET Framework’ün temelini oluşturur diyebileceğimiz CLR yukarıdada belirttiğimiz gibi, .NET Framework içerisinde kodun çalıştırılmasını yönetir. Aşağıda gördüğünüz şekil (Şekil: 1.1), .NET ortamının iki bölümünü gösteriyor. İlk bölümü CLR oluşturuyor, diğer bölümde ise (üst kısım) portable executable (PE) veya CLR yürütülebilir dosyaları yer alıyor. CLR için ihtiyaç duyulan sınıfları yükleyen, ihtiyaç duyulan metotlarla just-in-time compilation işlemini gerçekleştiren, güvenlik denetimleri yapan ve pek çok run-time işlemini gerçekleştiren bir “Run-Time Engine” diyebiliriz. Şekil: 1.1’de gösterilen yürütülebilir dosyalar, genellikle metadata ve code’dan oluşan EXE veya DLL dosyaları olabilmektedir.


Şekil: 1.1

CLR Yürütülebilir Dosyaları (CLR Executables)
Microsoft .NET uygulamaları, klasik Windows uygulamalarından farklıdır. Klasik Windows uygulamaları sadece kod ve veri taşırken, .NET uygulamaları aynızamanda metadata’da taşır. (Metadata ve Intermediate language konularını ilerideki bölümlerde ele alacağız.) 

“Merhaba Dünya” – Managed C++
Geleneksel “Merhaba Dünya” örneğini C++ dili için bir Microsoft .NET eki olan managed C++ ile yazalım. Managed C++, C++ dilinin .NET Framework’ün sunduğu garbage collection gibi yeni ve gelişmiş özelliklerin sağladığı avantajlardan yararlanabilmesini sağlayan, .NET’e özgü pek çok keywordler içerir. Aşağıda, Merhaba Dünya’nın Managed C++ dilinde nasıl söylendiğini görüyoruz:

#using <mscorlib.dll>
using namespace System;

void main( )
{
Console::WriteLine(L”C++ Merhaba Dünya!”);
}


Gördüğünüz gibi tek bir yeni ifade (#using) dışında bildiğimiz C++ kodu. Eğer Microsoft Visual C++ derleyicisinin COM desteği özellikleri ile çalıştıysanız, #import ifadesine aşina olmalısınız. #import ifadesi, COM arabirimleri için wrapper sınıfları üretmek amacıyla tür bilgisi (type information) üzerinde reverse-engineering yaparken, #using ifadesi, C/C++ dilindeki #include ifadesinin yaptığı gibi, belirtilen kütüphanedeki tüm türleri erişilebilir hale getiriyor. Ancak C veya C++ türlerini import eden #include ifadesinin aksine #using ifadesi herhangi bir .NET Assembly için, herhangi bir .NET dilinde yazılmış tüm türleri import ediyor.

Main() metotunun içindeki tek ifade zaten işlevini kendiliğinden açıklıyor. Burada Console sınıfında yeni bir statik veya class-level metot, WriteLine() metotunu başlattığımızı belirtiyoruz. “L” ile C++ derleyicisine literal string olan “Merhaba Dünya!” ifadesini Unicode String haline çevirmesini söylüyoruz. Console sınıfının mscorlib.dll içinde yer alan bir sınıf olduğunu söylemeye sanırım ihtiyaç yok. Using namespace ifadesi ise bizi, kullanacağımız sınıfı tümüyle belirtmek (System::Console şeklinde) yerine sadece Console yazarak kullanmamıza olanak sağlıyor. 

Bu örneği derlemek için C++ komut satırı derleyicisinde şu komutu girin;
Cl merhaba.cpp /CLR /link /entry:main

Buradaki /CLR ifadesi son derece önemli çünkü bu ifade C++ derleyicisine standart bir Windows PE dosyası yerine bir .NET PE dosyası oluşturmasını söylüyor.

C# ile Merhaba Dünya Diyelim..
.NET ile neyi hangi dille yazdığımız önem taşımıyor diyoruz şimdi bunu en basit haliyle tecrübe edelim ve C++ ile yazdığımız merhaba dünya örneğini C# ile yazalım. C#, .NET ile birlikte son derece güçlü bir Object Oriented dil olarak karşımıza çıkıyor. Java veya C++ kullanan geliştiriciler için C#’a alışmak hiçte zor olmayacaktır, Visual Basic geliştiricileri ise satır sonlarına noktalı virgül koymaya alışana kadar biraz zorlanabilirler.

Using System;
Class MainApp
{
public static void Main()
{
Console.Writeline(“C# ile Merhaba Dünya!”);
}
}


Bu sade koddanda görüldüğü gibi C# Java’ya oldukça benzemektedir. Bu kodda C++’dan farklı olan tek şey ise, Main() metotu C++ dilinde global bir fonksiyonken, C# dilinde, Java dilinde olduğu gibi Özel bir sınıfın, public static bir fonksiyondur.

C# örneğimizi compile etmek için kullanacağımız komut ise şöyle;
Csc merhaba.cs

Bu komutta csc .NET SDK ile gelen C# derleyicisidir. Bu komut sonucunda Merhaba.exe adlı CLR tarafından manage edilen bir dosya oluşturulacaktır. 

VB.NET ile Merhaba Dünya!
VB.NET ile Merhaba Dünya diyoruz:

Imports System

Public Module modmain
Sub Main()
Console.WriteLine (“VB ile Merhaba Dünya!”)
End Sub
End Module


Henüz VB.NET ile tanışmamış bir VB geliştiricisiyseniz bu kod sizi biraz şaşırtmış olabilir. Görebileceğiniz gibi VB ifadeleri, diğer Object Oriented dillere daha yakın olacak şekilde değişikliğe uğradı ve VB.NET veya C#.NET ile hiç tanışmamış olsanız bile şu iki örneğe bakarak VB.NET kodunu C#.NET’e kolaylıkla çevirebilirsiniz. Elbette bazı farklılıklar var, C# using ve class ifadelerini kullanırken VB.NET Import ve Module ifadelerini kullanıyor. Bu kodu derlemek için kullanacağımız komut ise şöyle:
Vbc /t:exe /out:Merhaba.exe Merhaba.vb

Vbc, Microsoft’un VB.NET için sunduğu komut satırı derleyicisidir. /t ifadesi oluşturulacak PE dosyasının türünü belirtiyor, bu örnekte Merhaba.exe adında bir Exe dosyası oluşturacak bir komut verdik.

Yukarıdaki üç örnektede gördüğünüz gibi Console sınıfı ve WriteLine() metotu her dilde sabit kaldı. .NET’in en güzel yanlarından biri de bu. Bir işlemi bir kez yapabildiyseniz, bu işlemi bundan sonra tüm .NET dilleriyle aynı şekilde yapabilirsiniz. Bu klasik Windows programlamasına göre çok önemli bir gelişme.

.NET Portable Executable File
Bir Windows yürütülebilir dosyası (Exe veya Dll), PE olarak tanımlanan ve Microsoft Common Object File Format’tan türetilmiş olan dosya yapısına uymak zorundadır. Windows işletim sistemi, PE formatını tanıyabildiği için Exe ve Dll dosyalarını yüklemek ve çalıştırmakta bir problem yaşamaz. Bu nedenlerden dolayı, Windows için yürütülebilir dosya üretecek düm derleyiciler, PE/COFF spesifikasyonlarına uymak zorundadır.

Standart Windows PE dosyaları iki ana bölüme ayrılmışlardır. İlk bölüm PE dosyasının içeriğini refere eden PE/COFF başlıklarını içerir. Buna ek olarak PE dosyası .data, .rdata, .rsrc ve .text gibi bazı bölümleride içerir. Bunlar Windows executable dosyalarının standart bölümleridir ancak Microsoft C/C++ derleyicisi size kendi özel bölümlerinizi, pragma ifadesini kullanarak PE dosyasına eklemenize imkan sağlar. Örneğin sadece kendinizin okuyabileceği şifrelenmiş verileri tutmak için özel bir bölüm oluşturabilirsiniz. Bu fonksiyonelliğin avantajlarını dahada artırmak ve CLR’in yeteneklerini daha iyi kullanabilmek için Microsoft normal PE dosyasına bazı yeni bölümler ekledi. CLR bu yeni bölümleri anlayabiliyor ve yönetebiliyor. Örnek olarak, CLR bu bölümleri okuyacak ve runtime esnasında sınıfları nasıl yükleyeceğine ve kodunuzu nasıl execute edeceğine karar verecektir. Şekil: 1.2’de görebileceğiniz gibi, Microsoft’un normal PE formatına eklediği bölümler, CLR başlığı ve CLR data bölümleri. CLR header bölümü PE dosyasının bir .NET executable olduğuna dair bilgileri tutarken, CLR data bölümü, metadata ve IL kodunu barındırıyor.


Şekil: 1.2

.NET executable dosyalarının bu alanları içerdiğini görmek istiyorsanızdumpbin.exe ile bir test yapabilirsiniz. Dubpbin aracı, Windows executable dosyalarının içeriğini bir metin dosyasına dönüştürmektedir. Örnek olarak yukarıda hazırladığımız Merhaba.exe dosyasını kullanarak bir deneme yapalım. Kullanacağımız komut şudur;

dumpbin.exe merhaba.exe /all

Bu komut aşağıdaki dosyayı oluşturacaktır. Oluşacak dosya aslında daha uzun olacaktır ancak burada sadece üzerinde durduğumuzun bölümlere yer verdik.

Microsoft (R) COFF/PE Dumper Version 7.00.9188
Copyright (C) 1992-2000 Microsoft Corporation. All rights reserved.

Dump of file merhaba.exe
PE signature found
File Type: EXECUTABLE IMAGE
FILE HEADER VALUES [MS-DOS/COFF HEADERS]
14C machine (x86)
3 number of sections
. . .
OPTIONAL HEADER VALUES [PE HEADER]
10B magic # (PE32)
. . .
SECTION HEADER #1 [SECTION DATA]
. . .
Code
Execute Read
RAW DATA #1
. . .
clr Header:
. . .
Section contains the following imports:
mscoree.dll
402000 Import Address Table
402300 Import Name Table
. . .
0 _CorExeMain


Bu texte bakarak görebileceğiniz gibi PE dosyası, tüm Windows uygulamalarının içermek zorunda olduğu MS-DOS ve COFF başlıklarıyla başlar. Bu ifadelerin hemen sonrasında 32-bit uygulamalar için olan PE header kısmını görüyorsunuz. Bu ifadelerden sonra executable dosyanın ilk data bölümünü göreceksiniz. Bir .NET PE dosyasında bu bölüm (SECTION HEADER #1) CLR header ve verisini içerir. Dikkat ettiyseniz bu bölümde Code ve Execute Read ifadeleri yer almakta. Bu ifadeler işletim sistemine ve CLR’a bu bölümün runtime esnasında CLR tarafında execute edilecek kod içerdiğini bildirir.

CLR Header kısmında _CorExeMain adlı bir fonksiyonun import edildiğini görüyorsunuz. Bu fonksiyon mscoree.dll dosyasında bulunmaktadır ve CLR’ın ana execution motorudur. Windows 98, 2000 ve Me işletim sistemlerinin hepsinin, standart PE dosyalarını yüklemeyi bilen işletim sistemi yükleyicileri (OS loader) vardır. Bu işletim sistemlerinde zarar verici değişikliklere neden olmamak ve aynı zamanda .NET uygulamalarının bu işletim sistemleri üzerindede çalıştırılabilmesi için Microsoft tüm bu platformların işletim sistemi yükleyicilerini güncelledi. Güncellenen yükleyiciler artık CLR header’ını nasıl denetleyeceklerini biliyorlar ve eğer bu header kısmı dosyada bulunuyorsa, _CorExeMain fonksiyonunu çalıştırıyor böylece sadece CLR’ı başlatmış olmuyor, dosyanın yürütülmesi işini CLR’a bırakmış oluyor. Bütün bu işlemlerin sonrasında CLR tarafından sizin yazdığınız Main() fonksiyonunun çağırıldığını tahmin edebilirsiniz.

CLR Header bölümünün içeriğini inceledik. Sıra şimdi, CLR Data bölümünün içeriğini incelemekte.

Metadata
Metadata bir kaynak (resource) hakkında machine-readable formattaki bilgidir (veya data-about-data). Her bilgi içerik, boyut veya veri kaynağının diğer karakteristik özellikleri hakkında detay içerebilir. .NET’te metadata, tür tanımlamalarını (type definitions), sürüm bilgilerini, harici assembly referanslarını ve diğer standartlaştırılmış bilgileri içerir.

İki sistemin veya nesnenin bir arada çalışabilmesi için, en azından birinin, diğeri hakkında birşeyler bilmesi gerekmektedir. COM da, bu “birşeyler” bileşen sağlayıcısı tarafından sunulan ve kullanıcıları tarafından kullanılan bir arabirim spesifikasyonuydu. Arabirim spesifikasyonu tüm parametreler ve return türlerini içeren metot prototiplerini tüm izmalarıyla birlikte içermektedir.

Interface Definition Language (IDL) tür tanımlamalarını sadece C/C++ geliştiricileri gönüllü olarak değiştirebilir veya kullanabilir daha da önemlisi bunun için herhangi bir araç veya middleware bulunmuyordu. Bu nedenle Microsoft, IDL haricinde, herkesin kolayca kullanabileceği başka birşeyler bulmalıydı. Bu “bir şey” ise “type library” olarak adlandırıldı. COM’da type library’ler, okumak, ters mühendislik işlemleri, wrapper class’lar oluşturmak gibi geliştiriciler için daha uygun ve fonksiyonel araçlar veya geliştirme ortamları sağlıyordu. Aynı zamanda kendilerini kullanan uygulamalar için runtime esnasında türleri araştırmak ve gerekli tesisatı veya aracı desteği sağlayan, VB, COM, MTS veya COM+ runtime gibi runtime-engine ler sağlıyordu.

Type library’ler COM’da oldukça zengindi ancak geliştiriciler standartizasyon eksikliğinden dolayı oldukça eleştiriyordu. .NET’i geliştiren ekip, bilginin capture edilmesi için farklı bir yöntem geliştirdi. Type library ifadesi yerine bu yeni türü .NET’te “metadata” olarak adlandırıyoruz.

.NET’te metadata, .NET runtime, derleyiciler ve araçlar tarafından kullanılabilen genel bir mekanizmadır. Microsoft .NET metadatayı belirli bir .NET assembly tarafından kullanılan tüm türleri tanımlamak için kullanır. Bu durumda, metadata bir assembly’ı, refere ettiği türleri, export ettiği türleri ve yürütme için gerek duyduğu güvenlik tanımlamaları ve kimlik tanımlamalarıda dahil olmak üzere detaylı şekilde açıklıyor. Bir type library’den daha zengin olacak şekilde metadata assembly, modüllerin, sınıfların, arabirimlerin, metotların, özelliklerin, alanların (field), eventlerin… tanımlamalarınıda içerir.

Metadata herhangi bir runtime, araç veya programın bileşen bütünleşmesi (component integration) için ihtiyaç duyduğu tüm bilgileri içeren, yeterli bilgi sağlar. Şimdi .NET’te metadatayı en sık kullanan bileşenleri kısaca inceleyelim.

CLR
CLR, metadatayı doğrulama, güvenlik uygulamaları, cross-context marshaling, memory layout ve execution işlemleri için kullanır. CLR, bu runtime işlemlerini gerçekleştirmek için metadatayı oldukça yoğun bir şekilde kullanır.

Class Loader
CLR’in bir bileşeni olan class loader metadatayı, spesifik bir sınıf için çok detaylı bilgi ve sınıfın nerede bulunduğu bilgisini içerdiği için, .NET sınıflarını bulmak ve yüklemek için kullanır. 

Just-in-Time (JIT) Derleyicileri
JIT derleyicileri metadatayı Microsoft Intermediate Language (IL) koduna derleme için kullanır. 

Araçlar
Araçlar metadatayı bütünleşmeyi desteklemek için kullanır. Debuggerlar, profilerlar ve nesne browserları gibi araçlar metadatayı daha zengin geliştirme desteği sağlamak için kullanabilirler. Buna en güze örnek Microsoft Visual Studio.NET’in desteklediği IntelliSense menülerdir. Kullanacağınız nesneyi yazdıktan sonra bir nokta “.” yazdığınız zaman araç size istediğinizi seçebileceğiniz ilgili metot ve özellikleri içeren bir liste sunar. Böylece, doğru metotu, özellik adlarını veya doğru syntaxı header dosyaları içinde veya dokumantasyonda aramanız gerekmez.

CLR gibi, bir .NET assemblyından metadata okuyabilen herhangi bir araç veya uygulama, bu assemblyı kullanabilirde. Bir .NET PE dosyasını araştırmak ve assembly’ın kullandığı veri türlerini öğrenmek için .NET Framework’te yer alan reflection sınıflarını kullanabilirsiniz. CLR, bellek yönetimi, güvenli yönetimi, tür denetimi, debugging, remoting gibi runtime özelliklerini sağlamak için yine bu reflection sınıflarını kullanır.

Metadata farklı dillerin, tüm bu dillerin geçerli bir PE dosyası oluşturabilmek için aynı türleri kullanmaları gerekmesinden dolayı, bir arada sorunsuz şekilde çalışabilmesini sağlar. .NET Runtime ortamı, metadatanın sunduğu özellikler olmadan bellek yönetimi, güvenlik yönetimi, memory layout, tür denetimi ve debugging gibi özellikleri destekleyemez. Bu nedenle metadata, .NET Framework içerisinde son derece önemli bir yere sahiptir, öyleki, metadata olmadan, .NET’te olmaz diyebiliriz.

Metadata’nın İncelenmesi
Bu noktada, belirtilen bir .NET PE dosyasına ait metadatayı ve IL kodunu görüntülememizi sağlayan oldukça önemli bir .NET aracından bahsedeceğiz: IL disassambler (ildasm.exe). Örneğin yukarıdaki örneklerde hazırladığımız merhaba.exe adlı .NET PE dosyasını ildasm.exe ile açarsak, aşağıdaki grafiğe benzer bir görüntüyle karşılaşırız.


Şekil: 1.3 (ildasm.exe)

Ildasm.exe, bir ağaç görünümüyle .NET PE dosyanıza ait metadatayı görüntüler. Bir .NET PE dosyasıyla ilgili tüm detayları görebilmek için dump komutunu kullanmanız gerekiyor. Menüden seçerek veya Ctrl+D kısayol tuşuyla verebileceğiniz dump komutu içeriği bir metin dosyasına kaydedecektir. Aşağıda, merhaba.exe dosyamızın dump komutu sonrasındaki çıktısını görebilirsiniz.


// Microsoft (R) .NET Framework IL Disassembler. Version 1.1.4322.510
// Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.

// PE Header:
// Subsystem: 00000003
// Native entry point address: 000025ce
// Image base: 00400000
// Section alignment: 00002000
// File alignment: 00001000
// Stack reserve size: 00100000
// Stack commit size: 00001000
// Directories: 00000010
// 0 [0 ] address [size] of Export Directory: 
// 257c [4f ] address [size] of Import Directory: 
// 4000 [858 ] address [size] of Resource Directory: 
// 0 [0 ] address [size] of Exception Directory: 
// 0 [0 ] address [size] of Security Directory: 
// 6000 [c ] address [size] of Base Relocation Table: 
// 207c [1c ] address [size] of Debug Directory: 
// 0 [0 ] address [size] of Architecture Specific: 
// 0 [0 ] address [size] of Global Pointer: 
// 0 [0 ] address [size] of TLS Directory: 
// 0 [0 ] address [size] of Load Config Directory: 
// 0 [0 ] address [size] of Bound Import Directory: 
// 2000 [8 ] address [size] of Import Address Table: 
// 0 [0 ] address [size] of Delay Load IAT: 
// 2008 [48 ] address [size] of CLR Header: 

// Import Address Table
// mscoree.dll
// 00002000 Import Address Table
// 000025be Import Name Table
// 0 time date stamp
// 0 Index of first forwarder reference
//
// 0 _CorExeMain

// Delay Load Import Address Table
// No data.
// CLR Header:
// 72 Header Size
// 2 Major Runtime Version
// 0 Minor Runtime Version
// 1 Flags
// 6000001 Entrypoint Token
// 20f4 [488 ] address [size] of Metadata Directory: 
// 0 [0 ] address [size] of Resources Directory: 
// 0 [0 ] address [size] of Strong Name Signature: 
// 0 [0 ] address [size] of CodeManager Table: 
// 0 [0 ] address [size] of VTableFixups Directory: 
// 0 [0 ] address [size] of Export Address Table: 
// 0 [0 ] address [size] of Precompile Header: 
// Code Manager Table:
// default
// Export Address Table Jumps:
// No data.

.assembly extern /*23000001*/ mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
.ver 1:0:5000:0
}
.assembly /*20000001*/ Merhaba
{
.custom /*0C000002:0A000007*/ instance void [mscorlib/* 23000001 */]System.Reflection.AssemblyCopyrightAttribute/* 01000008 */::.ctor(string) /* 0A000007 */ = ( 01 00 00 00 00 ) 
.custom /*0C000003:0A000002*/ instance void [mscorlib/* 23000001 */]System.Reflection.AssemblyKeyFileAttribute/* 01000003 */::.ctor(string) /* 0A000002 */ = ( 01 00 00 00 00 ) 
.custom /*0C000004:0A000003*/ instance void [mscorlib/* 23000001 */]System.Reflection.AssemblyDelaySignAttribute/* 01000004 */::.ctor(bool) /* 0A000003 */ = ( 01 00 00 00 00 ) 
.custom /*0C000005:0A000006*/ instance void [mscorlib/* 23000001 */]System.Reflection.AssemblyTrademarkAttribute/* 01000007 */::.ctor(string) /* 0A000006 */ = ( 01 00 00 00 00 ) 
.custom /*0C000006:0A000001*/ instance void [mscorlib/* 23000001 */]System.Reflection.AssemblyKeyNameAttribute/* 01000002 */::.ctor(string) /* 0A000001 */ = ( 01 00 00 00 00 ) 
.custom /*0C000007:0A000008*/ instance void [mscorlib/* 23000001 */]System.Reflection.AssemblyProductAttribute/* 01000009 */::.ctor(string) /* 0A000008 */ = ( 01 00 00 00 00 ) 
.custom /*0C000008:0A000009*/ instance void [mscorlib/* 23000001 */]System.Reflection.AssemblyCompanyAttribute/* 0100000A */::.ctor(string) /* 0A000009 */ = ( 01 00 00 00 00 ) 
.custom /*0C000009:0A00000A*/ instance void [mscorlib/* 23000001 */]System.Reflection.AssemblyConfigurationAttribute/* 0100000B */::.ctor(string) /* 0A00000A */ = ( 01 00 00 00 00 ) 
.custom /*0C00000A:0A00000B*/ instance void [mscorlib/* 23000001 */]System.Reflection.AssemblyDescriptionAttribute/* 0100000C */::.ctor(string) /* 0A00000B */ = ( 01 00 00 00 00 ) 
.custom /*0C00000B:0A00000C*/ instance void [mscorlib/* 23000001 */]System.Reflection.AssemblyTitleAttribute/* 0100000D */::.ctor(string) /* 0A00000C */ = ( 01 00 00 00 00 ) 
// --- The following custom attribute is added automatically, do not uncomment -------
// .custom /*0C00000C:0A00000D*/ instance void [mscorlib/* 23000001 */]System.Diagnostics.DebuggableAttribute/* 0100000E */::.ctor(bool,
// bool) /* 0A00000D */ = ( 01 00 01 01 00 00 ) 
.hash algorithm 0x00008004
.ver 1:0:1239:20072
}
.module Merhaba.exe
// MVID: {C9FA7311-4E0C-4E7A-B87F-6AE70FCCF0CE}
.imagebase 0x00400000
.subsystem 0x00000003
.file alignment 4096
.corflags 0x00000001
// Image base: 0x06f80000
//
// ============== CLASS STRUCTURE DECLARATION ==================
//
.namespace Merhaba
{
.class /*02000002*/ private auto ansi beforefieldinit MainApp
extends [mscorlib/* 23000001 */]System.Object/* 01000001 */
{
} // end of class MainApp

} // end of namespace Merhaba

// =============================================================
// =============== GLOBAL FIELDS AND METHODS ===================
// =============================================================
// =============== CLASS MEMBERS DECLARATION ===================
// note that class flags, extends and implements clauses
// are provided here for information only

.namespace Merhaba
{
.class /*02000002*/ private auto ansi beforefieldinit MainApp
extends [mscorlib/* 23000001 */]System.Object/* 01000001 */
{
.method /*06000001*/ public hidebysig static 
void Main(string[] args) cil managed
// SIG: 00 01 01 1D 0E
{
.entrypoint
.custom /*0C000001:0A00000E*/ instance void [mscorlib/* 23000001 */]System.STAThreadAttribute/* 0100000F */::.ctor() /* 0A00000E */ = ( 01 00 00 00 ) 
// Method begins at RVA 0x2050
// Code size 11 (0xb)
.maxstack 1
.language {3F5162F8-07C6-11D3-9053-00C04FA302A1}, {994B45C4-E6E9-11D2-903F-00C04FA302A1}, {5A869D0B-6611-11D3-BD2A-0000F80849BD}
// Source File d:\belgelerim\visual studio projects\merhaba\class1.cs 
.line 16:4 d:\\belgelerim\\visual studio projects\\merhaba\\class1.cs
IL_0000: /* 72 | (70)000001 */ ldstr bytearray (43 00 23 00 20 00 69 00 6C 00 65 00 20 00 4D 00 // C.#. .i.l.e. .M.
65 00 72 00 68 00 61 00 62 00 61 00 20 00 44 00 // e.r.h.a.b.a. .D.
FC 00 6E 00 79 00 61 00 21 00 ) // ..n.y.a.!. /* 70000001 */
IL_0005: /* 28 | (0A)00000F */ call void [mscorlib/* 23000001 */]System.Console/* 01000010 */::WriteLine(string) /* 0A00000F */
.line 17:3
IL_000a: /* 2A | */ ret
} // end of method MainApp::Main

.method /*06000002*/ public hidebysig specialname rtspecialname 
instance void .ctor() cil managed
// SIG: 20 00 01
{
// Method begins at RVA 0x2068
// Code size 7 (0x7)
.maxstack 1
IL_0000: /* 02 | */ ldarg.0
IL_0001: /* 28 | (0A)000010 */ call instance void [mscorlib/* 23000001 */]System.Object/* 01000001 */::.ctor() /* 0A000010 */
IL_0006: /* 2A | */ ret
} // end of method MainApp::.ctor

} // end of class MainApp


// =============================================================

} // end of namespace Merhaba

//*********** DISASSEMBLY COMPLETE ***********************
// WARNING: Created Win32 resource file C:\Documents and Settings\Kadir Sümerkent\Desktop\merhaba.exe.dump.res

Görebildiğiniz gibi çıktı, .NET assembly ile ilgili tüm bilgileri ve bağımlılıkları açıklıyor. İlk IL yönergesi, .assembly extern bize bu PE dosyasının referanslarını, ikinci IL yönergesi ise merhaba adlı assemblyımızı açıklıyor. Mainifest olarak adlandırılan .assembly bölümünün içeriğini daha ileriki bölümlerde inceleyeceğiz. Manifest bölümünün altında modul adını belirten bir yönerge görüyoruz. Merhaba.exe ve bu, kendisine özgü bir tanımlayıcıya (Globally Unique Identifier – GUID) sahiptir.

Daha sonra, .class IL yönergesiyle başlayan bir sınıf tanımlaması görüyoruz. Bu sınıf (MainApp) .NET’teki tüm sınıfların merkezi durumundaki System.Object sınıfından türemiştir. MainApp’ı System.Object sınıfından türetmemiş olsakta, Managed C/C++ veya VB.NET’te bu sınıfı yazdığımız zamanderleyici bu spesifikasyonu bizim için otomatik olarak ekliyor. Bunun nedeni, System.Object’in bir temel sınıfın spesifikasyonlarını uygulayan tüm sınıfların değişmez bir üyesi olmasıdır.

Bu sınıfla beraber iki metot görüyorsunuz. Main() bizim daha önceden yazdığımız statik bir metotken, ikinci metot .ctor() otomatik olarak üretilmiştir. Main() uygulamamızın ana başlangıç noktası halindedir .ctor() ise herhangi birinin MainApp fonksiyonunu kısa sürede çalıştırmasın sağlayan fonksiyondur.

Verdiğimiz örnekte belirtilen bir .NET PE dosyasına ait tüm metadatayı inceleyebiliriz. Buradaki en önemli noktalardan biribu işi kaynak koda veya header dosyalarına ihtiyaç duymadan yapabiliyor olmamızdır. Eğer biz bu işlemi bu kadar kolay bir şekilde yapabiliyorsak, metadatanın kullanımını kolaylaştırmak için CLR’ın veya sunduğu 3. parti yazılımların sunacağı kolaylıkları düşünün.. Bu durumda herkes, biz önleyici teknolojiler kullanmadığımız (şifreleme gibi) taktirde kodumuzu görebilecek. 

Metadata’nın Araştırılması ve Düzenlenmesi
Bir .NET assembly’ını yüklemek ve desteklediği türleri incelemek için .NET Framework tarafından sağlanan bazı sınıfları kullanıyoruz. API fonksiyonlarının aksine, bu sınıflar bize metadatayı incelemek ve düzenlemek için kolaylık sağlayan bir arabirim sağlayan metotlar sunuyor. .NET’te bu metotlar genel olarak Reflection API olarak adlandırılıyor ve bu sınıflar System.Reflection ve System.Reflection.Emit alan adlarından (namespace) sınıflar içeriyorlar. System.Reflection alan adı bir .NET assemblyındaki metadatayı incelemenize imkan veriyor. Bununla ilgili bir örnek yapalım;

using System;
using System.IO;
using System.Reflection;

public class Meta
{
public static int Main( )
{
// assembly yükleniyor.
Assembly a = Assembly.LoadFrom("merhaba.exe");
// assembly tarafından desteklenen tüm modülleri al.
Module[] m = a.GetModules( );
// ilk modüldeki tüm türleri al.
Type[] types = m[0].GetTypes( );
// ilk türü incele.
Type type = types[0];
Console.WriteLine(" [{0}] şu metotlara sahip:", type.Name);
// bu tür tarafından desteklenen metotları incele.
MethodInfo[] mInfo = type.GetMethods( );
foreach ( MethodInfo mi in mInfo )
{
Console.WriteLine(" {0}", mi);
}
return 0;
}
}


Bu C# programına baktığınızda, derleyiciye ilk olarak System.Reflection alan adındaki sınıfları kullanmak istediğimizi belirttiğimizi görüyorsunuz. Bunun nedeni metadatayı incelemek istememizdir. Main() bölümünde fiziksel bir ad belirterek assemblyı yükledik. Merhaba.exe şeklinde belirtmemiz, uygulamayı çalıştırdığımız dizinde bu PE dosyasının bulunduğundan emin olmamızı sağlar. Daha sonra yüklenen assembly nesnesine barındırdığı modüllerden oluşan bir dizi (array) istedik. Bu modül dizisinden modül tarafından desteklenen türleri aldık ve ilk türü aldık. Örneğimizdeki merhaba.exe dosyasında tek tür MainApp olmalıdır. Bu tür veya sınıfı elde ettikten sonra expose ettiği metotlar içinde bir döngü gerçekleştirdik. Eğer bu kodu derler ve çalıştırırsanız aşağıdaki çıktıyı görürsünüz.

Type [MainApp] has these methods:
Int32 GetHashCode( )
Boolean Equals(System.Object)
System.String ToString( )
Void Main( )
System.Type GetType( )


Her ne kadar biz sadece Main() fonksiyonunu yazmış olsakta, sınıfımız 4 farklı metotu destekler. Burada olağandışı bir şey yok, çünkü MainApp bu metotları System.Object alan adından alır ki nasıl alır, neden alır gibi soruları yukarıdaki satırlarda yanıtlamıştık.

Görebildiğiniz gbi System.Reflection sınıfları size metadatayı inceleme imkanı sağlar ve kullanımları gerçekten oldukça kolaydır. Eğer daha önceden COM’da tür sınıflarını kullandıysanız bunu COM’da da yapabileceğinizi biliyorsunuzdur ancak aynı işlemi çooook daha fazla çaba sarfederek yapabildiğinizide hatırlıyorsunuzdur. COM type-library ile yapamayacağınız bir şey var ki o da runtime esnasında bir COM bileşeni oluşturmaktır. Bu COM’da ki büyük eksikliklerden biri, .NET’te ise en önemli özelliklerden biridir. System.Reflection.Emit sınıflarını kullanarak, runtime esnasında bir .NET assembly oluşturacak basit bir programı kolayca yazabilirsiniz. System.Reflection.Emit sınıflarının sağladığı özellikler/kolaylıklar sayesinde, dileyen herkes (biraz çalışarak  ) kendisine özel bir .NET compiler geliştirebilir.

Ineroperability Desteği
Belirtilen türler için genel bir format sağladığı için metadata, interoperability sağlamak için farklı bileşen, araç ve runtimelara destek verir. Daha önceden örneklediğimiz gibi, herhangi bir .NET assemblyına ait metadatayı inceleyebilirsiniz. Aynı şekilde runtime esnasında bir nesneye türünü, metotlarını, özelliklerini, eventlerini vb. sorabilirsiniz. Araçlarda aynı şeyi yapabilir. Microsoft .NET SDK size, interoperability desteğini maximuma çıkarmak için 4 önemli araç sağlar. Bunlar; .NET Assembly kayıt aracı RegAsm.exe, type library exporter tlbexp.exe, type library importer tlbimp.exe ve XML şema tanımlama aracı olan xsd.exe dir.

.NET Assembly registration aracını, bir .NET Assembly’ını COM istemcilerininde onu kullanabilmesini sağlamak için registry’e kayıt etmek için kullanabilirsiniz. Type library exporter aracı ise type library dosyası (*.tlb) oluşturmak için oldukça kullanışlı bir araçtır. Belirtilen bir .NET Assemblyından bir type library oluşturduğunuz zaman bu type libraryi VC++ veya VB’ye import edebilir ve COM bileşenlerini kullanırken kullandığınız yöntemle aynı şekilde kullanabilirsiniz. Aşağıdaki komut merhaba.tlb adlı bir type library oluşturacaktır:

tlbexp.exe merhaba.exe

Microsoft type library exporter aracının yaptığı işin tam tersini yapan bir başka araç daha sunuyor: Type library importer. Type library importer aracının görevi ise, Type library exporter aracının yaptığının aksine, COM bileşenlerini .NET assemblyları gibi göstermek. Böylece, bir .NET aracı geliştiriyorsanız ve eski bir COM bileşeni kullanmak istiyorsanız, type library importer aracını kullanarak COM bileşeninde bulunan tür bilgilerini .NET karşılıklarına çevirebilirsiniz. Örneğin aşağıdaki komutu kullanarak bir .NET PE dosyası oluşturabilirsiniz:

Tlbimp.exe COMServer.tlb

Bu komut, bir DLL dosyasından (COMServer.dll) bir .NET Assemblyı oluşturacaktır. Bu DLL’ye diğer .NET Assemblyları gibi .NET kodunuz içinde referans verebilirsiniz.

Type library importer aracının, daha önceden type library exporter aracı ile export edilmiş kütüphanelerin yeniden improt edilmesine izin vermediğini unutmamalıyız. Örneğin tlbimp.exe ile tlbexp.exe aracı ile oluşturduğumuz merhaba.tlb dosyasını kullanmak istersek, tlbimp.exe bize oldukça kızacaktır.

.NET SDK ile gelen bir diğer önemli araç ise XML şema tanımlama aracıdır. Bu araç bize XML şemalarını C# sınıflarına dönüştürme imkanı sağlar.

Aşağıdaki XML şeması CCar adlı bir türe ait.

<schema xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="urn:book:car"
xmlns:t="urn:book:car">
<element name="car" type="t:CCar"/>
<complexType name="CCar">
<all>
<element name="vin" type="string"/>
<element name="make" type="string"/>
<element name="model" type="string"/>
<element name="year" type="int"/>
</all>
</complexType>

</schema>

Bu xml şemasını C# sınıfına çevirmek için aşağıdaki komutu kullanacağız:

Xsd.exe /c car.xsd

Bu  komuttaki /c ifadesi xsd.exe’ye belirtilen XSD dosyasından bir sınıf oluşturmasını söyler. Eğer bu komutu kullanırsanız, bu tür için C# kodları içeren car.cs adlı bir dosya oluşturulacaktır.

XML şema tanımlama aracı aynı zamanda bir .NET Assemblyını alarak bu assemblydan XML şema tanımlama dosyası (XSD) oluşturabilir. Örneğin aşağıdaki komutu çalıştırırsanız bir XSD dosyası oluşturulacaktır.

Xsd.exe mis.exe

Konuyu değiştirmeden önce bu araçları, bu yazıda yer veremediğim pek çok önemli özelliğini keşfedebilmeniz için kendi geliştireceğiniz örneklerle denemenizi öneriyorum.

Assembly ve Manifest
Biraz önce gördüğümüz gibi, türler, araçların ve uygulamaların kendilerine erişebilmeleri ve servislerinin sağladığı avantajlardan faydalanabilmeleri için kendi metadatalarını açıklamalılar. Türler için metadata yeterli değildir aynı zamanda türleri host eden bileşenlere ait metadataya ihtiyaç duyarız. Bu bölümde .NET assemblyları ve manifest (assemblyları tanımlayan metadatalar)leri inceleyeceğiz.

Assemblylar ve Bileşenler
COM döneminde, Microsoft dokumantasyonlarında bileşen (component) kelimesi tutarsız bir şekilde hem COM sınıflarını hem de COM modüllerini (DLL ve EXE ler) tanımlamak için kullanılıyordu ve bu okuyucuların karşılaştıkları terimle neyin kastedildiğini anlamak için uğraşmalarına neden oluyordu. Microsoft .NET ile birlikte bu karmaşaya assembly kavramı ile bir son verdi. Assemblylar, donanımlardakine benzer bir plug&play yapısını destekliyorlar. Teorik olarak bir .NET assemblyı bir COM modulüne denktir. Uygulamada ise bir assembly, runtime esnasında sorunsuz uygulamanın sorunsuz çalışması için ihtiyaç duyulabilecek pek çok türü veya fiziksel dosyayı (bitmap/PE dosyaları vb.) içinde barındırabilir veya refere edebilir. IL kodunun host edilmesine ek olarak bir assembly, sürümlendirme, deployment, güvenlik yönetimi, side-by-side execution, paylaşım ve yeniden kullanım gibi ileride açıklayacağımız pek çok konunun temel bir bileşenidir.

IL Kod
Bir aasembly, birkaç konu ileride ele alacağımız Intermediate Language konusunda göreceğiniz üzere CLR’ın runtime esnasında execute ettiği IL kodu içerir. IL kodu, tipik olarak assemblyda tanımlanmış olan türleri içerir ancak aynı zamanda farklı aassemlylardaki türleride kullanabilir veya refere edebilir. Her assembly bir entry pointe sahip olmalıdır, örneğin DllMain(), WinMain() veya Main() gibi. Bu kurala, CLR bir assemblyı yüklediği zaman assembly execution işlemini başlatmak için bu entry pointlerden birini aradığı için uymak zorundasınız.

Sürümlendirme (Versioning)
.NET’te dört assembly türü vardır.

Static Assemblylar
Bunlar compile işlemi ile oluşturduğunuz normal PE dosyalarıdır. Bu assemblyları csc, cl veya vbc arasından istediğiniz bir compiler ile oluşturabilirsiniz.

Dinamik Assemblylar
Bunlar runtime esnasında System.Reflection.Emit alan adını kullanarak ürettiğiniz in-memory assemblylardır.

Private Assemblylar
Bunlar spesifik bir uygulama tarafından kullanılan statik assemblylardır.

Public veya Shared Assemblylar
Bunlar özgün bir shared isme sahip olmak zorunda olan ve herhangi bir uygulama tarafından kullanılabilecek statik assemblylardır.
Uygulamalar private assemblyları assemblya statik bir dizin kullanarak veya XML tabanlı app.config dosyası ile referans vererek kullanabilirler.
.NET’te assemblylar bir sürüm numarası verebileceğiniz en küçük birimdir ve aşağıdaki formata sahiptir.

<major_version>.<minor_version>.<build_number>.<revision>

Deployment
Bir istemci uygulamasına ait assembly manifes harici referansları ile ilgili bilgiler içermesi ile artık COM’da marshaling ve aktivasyon için registryi kullanmanız gerekmiyor. CLR, uygulamanızın manifest’inde kayıtlı olan sürüm ve güvenlik bilgilerini kullanarak doğru shared assemblyı yükleyecektir.

Güvenlik
Kullanıcı kimliği tüm geliştirme ve işletim sistemi platformlarında genel bir kavramsada bir kod parçasının bir kimliğe sahip olmasını yani kod kimliği kavramı, yazılım endüstrisinde yeni bir kavramdır. .NET’te her assembly kendi kod kimliğine sahiptir. Bu kimlik assembly’ın shared name, sürüm numarası ve sahip oldugu public key gibi bilgileri içerir. Bu konsept sayesinde CLR bir assemblyın sizin kaynaklarınıza erişme izni olup olmadığını veya diğer assemblylara erişim hakkı olup olmadığını tespit edebilir.

CLR, code identity konseptiyle uyuşmak için code access konseptini destekler. Diğer durumlarda bir assemblya erişimle ilgili izin ve yetkilendirmeleri runtime denetler. Burada ise bu yetki denetimini CLR yapar ve execution requestleri assembly seviyesinde denetleyerek gerekli işlemleri gerçekleştirir. Bir assembly oluşturduğunuzda, client uygulamaların oluşturduğunuz assemlyları kullanmak için uymak zorunda olacakları bir dizi izin opsiyonları oluşturabilirsiniz. Runtime esnasında eğer client uygulama size ait assemblya erişim iznine sahipse, assemblyınızdai nesneleri çağırabilir/kullanabilir, aksi halde assemblyınızı kullanamaz.

Side-by-Side Execution
Assemblyların deployment ve sürümlendirme işlemlerinin bir parçası olduğunu ve .NET’in minimuma indirmeyi amaçladığı DLL cehenneminden bahsettik. CLR, aynı shared DLL’in (shared assembly) aynı proses içinde, aynı sistemde ve aynı anda çalıştırılmasına izin verir. Buna side-by-side execution diyoruz. Microsoft .NET side-by-side execution’ı tüm shared assemblyların standart özelliklerinden sürümlendirme ve deployment özelliklerini kullanarak gerçekleştirir. Bu konsept size, DLL cehennemi veya sürüm çakışması problemlerine maruz kalmadan aynı shared assemlyın farklı sürümlerini aynı bilgisayara yüklemenize imkan tanır. Bunun için yapmanız gereken tek şey ilgili assemblyın public veya shared olmasını sağlamak yani GAC (.NET Global Assembly Cache Utility – gacutil.exe) gibi bir araç ile kayıt etmeniz gerekiyor. Bir assemblyı GAC ile kayıt ettikten sonra ne isim taşıdığı bizim için bir önem taşımıyor. Bizim açımızdan önemli olan, .NET’in sürümlendirme ve deployment özellikleri ile sağlanan bilgilerdir.

Bir shared assemblyı kullanan bir uygulama geliştirdiğinizde assemblyın sürüm bilgilerinin uygulamanızın manifestine eklendiğini hatırlayın. Buna ek olarak, assemblya ait public keyi barındıran 8 bytelık bir bilgi daha manifeste eklenecektir. CLR, bu iki bilgiyi kullanarak uygulamanızın kullandığı assemblyı kesin/hatasız olarak bulacaktır.

Paylaşım ve Yeniden Kullanım
Bir assemblyı diğer kullanıcılarla paylaşmak isterseniz, assemblyınızın bir shared adı olması gerekir ve GAC ile kayıt etmeniz gerekir. Aynı şekilde bir shared assembly tarafından host edilen özel bir sınıfı kullanmak isterseniz sadece bu sınıfı değil, tüm assemblyı uygulamanıza import etmeniz gerekiyor. Bu nedenle assemblyın tamamı, paylaşma işleminin bir parçasıdır.

Assemblylar git gide .NET’teki en önemli parçalardan biri halini almaya başlıyor. Bunun nedeni assemblyların runtime’ın önemli bir parçası olmasıdır.

Unutmamamız gereken bir diğer madde ise, CLR’ın spesifik bir türü, türün assemblyını bilmeden kullanamayacağıdır. Eğer assemblyla ilgili açıklayıcı bilgileri içeren bir assembly manifestiniz yoksa, CLR uygulamanızı çalıştırmayacaktır.

Manifest: Assembly Metadata
Assembly Manifest, ilgili assembly ile ilgili tüm açıklayıcı bilgileri içeren bir metadatadır. Bu açıklayıcı bilgilere assemblyın kimliği, assemblya ait dosyalar, harici assemblylara referanslar, export edilen türler, export edilen kaynaklar, izinler vb. dahildir. Kısacası component plug&play için gerekli tüm detaylar mevcuttur. Assemblylar kendileriyle ilgili tüm bilgileri üzerlerinde taşıdıkları için COM’da olduğu gibi bu bilgilerin depolanması için registryi kullanmamıza artık gerek yok.

Şimdi merhaba.exe dosyamızın manifestini inceleyelim. Bu bilgileri edinmek için ildasm.exe aracını kullandığımızı hatırlayın..

.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
.hash = (8B BB 5A BD 8D A3 12 7D 08 A2 25 D0 48 17 28 4F 20 57 EA 07 )
.ver 1:0:2411:0
}

.assembly merhaba
{
.hash algorithm 0x00008004
.ver 0:0:0:0
}

.module merhaba.exe
// MVID: {F828835E-3705-4238-BCD7-637ACDD33B78}

Bu manifestin mscorlib adlı harici veya refere edilmiş bir assemblyı tanımlayarak başladığını farkedeceksiniz. .assembly extern ifadei CLR’a mscorlib adlı assemblyı kullanacağını bildirir. Bu harici manifest, tüm .NET uygulamaları tarafından kullanılan temel bir assembly olduğu için tüm Manifestlerde göreceksiniz. Bu assembly tanımlamasının içinde compiler tarafından eklenen publickeytoken şeklinde bir ifade dikkatinizi çekmiştir. Bu ifade mscorlib’in yayımcısına ait temel bilgileri içerir. Derleyici bu ifadenin değerini, mscorlib adlı assemblya ait public keyi hash ederek üretir. Dikkat etmemiz gereken bir diğer nokta ise .hash değeridir. Bu, mscorlib assemblyı içinde seçilmiş içeriğe ait şifrelenmiş bilgileri içerir. Publickeytoken ifadesi CLR’a runtime esnasında doğru assemblyı kullandığından emin olmasını sağlarken .hash değeri ise CLR’ın refere edilmiş assemblyın illegal olarak modifiye edilip edilmediğini tesbit etmesini sağlıyor. Son olarak dikkat etmemiz gereken son nokta mscorlib bloğu, mscorlib assemblyının sürüm numarasıdır.

İlk .assembly bloğunu inceledik, şimdi sıra ikincisinde. Burayı, uygulamamızın assemblyını tanımlayan bir manifest bloğu olarak tanımlayabiliriz çünkü burada extern ifadesi ile hiç karşılaşmıyoruz. Bu assemblyın kimliğinin “merhaba”, sürüm numarasının “0:0:0:0” olduğunu kolayca anlayabiliyoruz. Bu bloğun ilk satırında bu assemblyın içeriğinden seçilenlerin hash edilmesi esnasında kullanılan hash algoritması belirtiliyor. Bu assemblyı paylaşmadığımız için bir şifreleme ve .publickeytoken değeri yok.

Ele almamız gereken son ifade .module ifadesi. Bu ifade bu assemblyın çıktısı olacak dosya adını belirtiyor: merhaba.exe. Bu modülün, modülü her build yaptığınızda farklı bir GUID alacağınız anlamına gelen bir GUID ile ilişkilendirilmiştir.

Bu assembly oldukça sade olduğu için daha gelişmiş örneklerde karşılaşabileceğiniz bazı konuları ele alamadık. “Ben hiçbirşey anlamadım ki”, “Böyle anlatırsan tabii anlayamam hiçbir şey” veya “Ben o konularıda görmek istiyorum” diyorsanız, .NET Framework SDK ile birlikte gelen “The IL Assembly Language Programmers’ Reference” adlı dokumanı incelemenizi öneriyorum.

Assembly Oluşturmak
İki türde assembly olabilir; “single-module assembly” veya “multi-module assembly”. Bir single-module assemblyda herşey tek bir exe veya dll dosyasında yer alır. Biraz önce hazırladığımız merhaba.exe adlı örnek buna güzel bir örnektir.  Bu kolaydır çünkü yapılması gereken herşeyi compiler bizim için yapar.

Pek çok module ve resource dosyası içeren bir multi-module assembly oluşturmak isterseniz birkaç seçeneğiniz vardır. Bunlardan ilki .NET Framework SDK ile sunulan Assembly Linker (al.exe). Bu araç bir veya birden çok IL veya resource dosyasını alarak bir dosya ve assembly manifest oluşturur.

Assemblyları Kullanmak
Bir assemblyı kullanabilmek için yapmamız gereken ilk şey o assemblyı kodumuza import etmek. Örneğin örneklerimizde C# kullanıyoruz ve C# dilinde assemblyları aşağıdaki şekilde import ediyoruz:

Using System;

Assemblyınızı oluşturduğunuzda derleyicinize harici bir assemblya referans verdiğinizi bildirmeniz gerekir. Eğer C# derleyicisini kullanıyorsanız kullanmanız gereken komut aşağıdaki gibidir:

Csc /r:mscorlib.dll merhaba.cs

Merhaba.cs dosyasını /r opsiyonu olmadan derlemeyi yukarıda görmüştük ancak iki teknikte denktir. Mscorlib.dll temel .NET Framework sınıflarını barındırdığından doğal olarak dahil edilecektir.

Intermediate Language (IL)
Yazılım mühendisliğinde soyutlama (abstraction) kavramı oldukça önemlidir. Soyutlamayı, sistem veya uygulama servislerinin karmaşıklığını gizlemek için sıklıkla kullanırız. Arabirimi aynı tutabildiğimiz sürece arkaplandaki olumsuz bölümleri değiştirebilir ve farklı tüketiciler aynı arabirimi kullanabilirler.

Diller geliştikçe, bilim adamları, p-code veya bytecode gibi dil soyutlama katmanlarının farklı metotlarını geliştirdiler. Pascal-P derleyicisi tarafından üretilen p-code, prosedürel programlamayı destekleyen bir ara dildir. Java derleyicileri tarafından üretilen bytecode ise nesneye dayalı programlamayı destekleyen bir ara dildir. Bytecode, Javanın, platformlar bytecode’u çalıştırmak için Java Virtual Machine (JVM)’e sahip oldukları sürece farklı platformlarda çalışmasını sağlayan bir language abstraction’dır.

Microsoft bytecode’a benzeyen kendi dil soyutlama katmanını “Common Intermediate Language (CIL)” olarak tanımlar. IL, veri soyutlama, inheritance, polymorphism ve exceptionlar ve eventlar gibi faydalı pek çok konsepti içeren nesneye dayalı tüm özellikleri destekler. Bu özelliklere ek olarak IL özellikler, alanlar ve enumeration gibi farklı konseptleride destekler.  Tüm .NET kodları IL’e dönüştürüldüğünden .NET pek çok farklı dili destekler ve gelecekte büyük ihtimalle pek çok farklı platformuda destekleyecektir. Tabii platformlar CLR’a sahip olduğu sürece.

Geçerli bir .NET Assemblyını desteklenen IL yönergeleri ve özellikleri ile geliştirebileceğiniz gibi IL’i oldukça sıkıcı bulma ihtimaliniz oldukça yüksek. Yinede saf IL kodu yazmak isteyebilirsiniz (hatta günün birinde delirirseniz bu size eğlenceli bile gelebilir). Bunun için IL Assembler (ilasm.exe) adlı aracı kullanmanız gerekir ki bu araç IL kodunuz .NET PE dosyasına dönüştürür.

Şimdi benim yaparken çok eğlendiğim bir şeyi yapalım, IL ile biraz kod yazalım (evet, evet..).  Aşağıda önceden yaptığımız merhaba.exe programından bir alıntı var.

.assembly extern mscorlib
{
  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
  .ver 1:0:5000:0
}

.assembly Merhaba
{
  .custom instance void [mscorlib]System.Reflection.AssemblyCopyrightAttribute::.ctor(string) = ( 01 00 00 00 00 )
  .custom instance void [mscorlib]System.Reflection.AssemblyKeyFileAttribute::.ctor(string) = ( 01 00 00 00 00 )
  .custom instance void [mscorlib]System.Reflection.AssemblyDelaySignAttribute::.ctor(bool) = ( 01 00 00 00 00 )
  .custom instance void [mscorlib]System.Reflection.AssemblyTrademarkAttribute::.ctor(string) = ( 01 00 00 00 00 )
  .custom instance void [mscorlib]System.Reflection.AssemblyKeyNameAttribute::.ctor(string) = ( 01 00 00 00 00 )
  .custom instance void [mscorlib]System.Reflection.AssemblyProductAttribute::.ctor(string) = ( 01 00 00 00 00 )
  .custom instance void [mscorlib]System.Reflection.AssemblyCompanyAttribute::.ctor(string) = ( 01 00 00 00 00 )
  .custom instance void [mscorlib]System.Reflection.AssemblyConfigurationAttribute::.ctor(string) = ( 01 00 00 00 00 )
  .custom instance void [mscorlib]System.Reflection.AssemblyDescriptionAttribute::.ctor(string) = ( 01 00 00 00 00 )
  .custom instance void [mscorlib]System.Reflection.AssemblyTitleAttribute::.ctor(string) = ( 01 00 00 00 00 )
  .hash algorithm 0x00008004
  .ver 1:0:1239:20072
}

.module Merhaba.exe
.imagebase 0x00400000
.subsystem 0x00000003
.file alignment 4096
.corflags 0x00000001
.namespace Merhaba

{
  .class private auto ansi beforefieldinit MainApp
         extends [mscorlib]System.Object
  {
  } // MainApp sınıfının sonu
} // Merhaba alan adının sonu

.namespace Merhaba
{
  .class private auto ansi beforefieldinit MainApp
         extends [mscorlib]System.Object
  {
    .method public hidebysig static void
            Main(string[] args) cil managed
    {
      .entrypoint
      .custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 )
      .maxstack  1
      IL_0000:  ldstr      bytearray (43 00 23 00 20 00 69 00 6C 00 65 00 20 00 4D 00  65 00 72 00 68 00 61 00 62 00 61 00 20 00 44 00 FC 00 6E 00 79 00 61 00 21 00 )                   // Burada C# ile merhaba Dünya yazıyor.
      IL_0005:  call       void [mscorlib]System.Console::WriteLine(string)
      IL_000a:  ret
    } // MainApp::Main metotunun sonu

    .method public hidebysig specialname rtspecialname
            instance void  .ctor() cil managed

    {
      .maxstack  1
      IL_0000:  ldarg.0
      IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
      IL_0006:  ret
    } // MainApp metotunun sonu::.ctor
  } // MainApp sınıfının sonu
} // Merhaba alan adının sonu

Kodu deneyelim. Yukarıdaki kodu “Merhaba.txt” adlı bir dosyaya kaydedin ve aşağıdaki komutu kullanarak kodu ilasm.exe ile derleyin.

ilasm.exe merhaba.txt

Bu komut sonrasında Merhaba.exe oluşturulacak ve yazının başlarındaki merhaba.exe ile aynı işleve sahip olacaktır.

Kodun karmaşık görüntüsünün ardında dikkat ederseniz IL konsept oalrak herhangi bir diğer nesneye dayalı programlama diliyle aynı sözdizimsel detaylara sahiptir. Özetlemek gerekirse burada System.Objectten türettiğimiz MainApp adlı bir sınıf var. Bu sınıf konsolda bir string yazdıran Main() adlı bir statik metotu desteklemekte. Biz bu sınıf için bir constructor yazmamış olsakta, C# derleyicimiz MainApp sınıfına nesne üretimini desteklemesi için varsayılan constructor’ı eklemiştir.

IL ile ilgili detaylara geçmeden Main() metotunu biraz inceleyelim. İlk olarak aşağıdaki metot imzasını görüyoruz.

.method public hidebysig static
       void Main( ) cil managed

Bu imza metotun public ve static olduğunu deklare ediyor. Yani bu metot herhangi biri tarafından çağrılabilir ve static ifadesiyle bunun bir class-level metot olduğunu belirtiyor. Bu metotun adı Main(). Main() CLR tarafından manage edilecek veya yürütülecek IL kodu içeriyor. Hidebysig özniteliği sınıf hiyerarşisindeki önceden tanımlanmış aynı metotları (aynı imzaya sahip) metotları gizlediğini belirtiyor. Çoğu oop dillerde aynı şeyi görmüşsünüzdür. Şimdi metotun içeriğini inceleyelim:

{
       .entrypoint
       .maxstack 8
       ldstr “C# ile Merhaba Dünya”
       call void [mscorlib] System.Console::WriteLine(class System.String)
       ret
}

Bu metot iki direktif kullanıyor: .entrypoint ve .maxstack. .entrypoint direktifi Main()’in bu assembly’ın sahip olduğu tek entry point olduğunu belirtiyor. .maxstack direktifi ise bu metot için gerekli max. Stack slotunu belirtiyor. Bu durumda Main() için gerekli maximum stack slotunun sayısı sekizdir. Stack bilgisi her IL metotu için gereklidir, bunun nedeni, IL yönergelerinin stack-tabanlı olmasıdır. Bu da dil derleyicilerinin IL kodunu daha kolay oluşturabilmelerini sağlar.

Bunlara ek olarak bu metotta 3 IL yönergesi var. İlki ldstr ki işlevini söylememe eminim gerek yok, ama söyleyelim biz J Bu yönerge kullanacağımız stringi stacke yükler böylece aynı blok içindeki kod bu stringi kullanabilir. İkinci yönerge olan call ise WriteLine() metotunu talep eder. Bu metotta stringi stackten alır.

CTS ve CLS
Metadatanın ve IL’in önemini gördükten sonra sıra CTS ve CLS’ye geldi.
CTS + CLS =  compability+interoperability+integration. Bu denklem bu iki ifadenin diller için ne anlama geldiğini açıklıyor. Şimdi bunu biraz açalım.

Common Type System (CTS)
.NET’te tüm diller eşit diyoruz. .NET’te böyle diyor. Bu durumda C# ile yazılmış bir sınıf VB.NET ile yazılmış bir sınıfa denk olmalıdır veya Managed C++ ile hazırlanmış bir arabirim managed COBOL ile geliştirilmiş olanı ile aynı olmalıdır. Diller, diğer diller ile bütünleşmeden önce bu konseptleri kabul etmelidir. Dillerin bütünleşmesini gerçeğe dönüştürmek için Microsoft tüm .NET dillerinin “katlanmak” zorunda olduğu bir genel tür sistemi “common type system” oluşturdu. Bu bölümde tüm .NET dilleri için geçerli olan bu genel türleri inceleyeceğiz. Microsoft .NET oldukça geniş bir tür dizisini destekler ancak biz burada bu listeyi en önemli olanlarla kısıtlayacağız. (aksi taktirde makalelikten çıkacak ve okumak gerçekten sabır isteyecek)

Value Type
Genelde CLR iki farklı türü destekler; değer türleri ve referans türleri. Değer türleri stack üzerindeki değerleri gösterir. Null değer alamazlar her zaman mutlaka data içermek zorundadır. Değer türleri bir fonksiyona iletildiğinde bir değer ile birlikte iletilir, yani değer fonksiyonun execute edilmesinden önce üretilir. Bu orijinal değerin değişmemesini ve fonksiyonun icrası esnasında neler olduğunun bir öneminin kalmamasına neden olur. Gerçekte türler oldukça küçük boyutludur ve çok fazla bellek harcamazlar ve bir kopyalama işleminde harcanacak kaynakların miktarı üzerinde durulacak seviyede değildir. Değer türleri primitive, structure ve enumeration alrı içerir. Aşağıda bunlarla ilgili bir C# kodu yer alıyor:

int i;                     // primitive
struct Point { int x, y; } // structure
enum State { Off, On}             // enumeration


Aynı zamanda bir değer türünü System.ValueType’tan türeterekte oluşturabilirsiniz. Son olarak aklımızda tutmamız gereken önemli bir not var o da, System.ValueType’tan bir sınıf türettiğimiz zaman başka hiçkimsenin bizim sınıfımızdan türetme yapamayacağıdır.

 

Reference Type
Eğer bir tür önemli oranda bellek kullanıyorsa, bir reference type, value typedan daha faydalı olacaktır. Reference type’lar sıkça kullanılır çünkü heap-tabanlı nesnelere referanslar içerirler ve null değer alabilirler. Bu türler referans ile iletilirler yani bir nesneyi, bir fonksiyona ilettiğinizde, nesnenin adresi veya ilgili işaretçi iletilir, value typeda olduğu gibi nesnenin bir kopyası değil. Bir referans iletmenizle birlikte caller, çağrılan fonksiyonun nesnenize ne yapacağını görecektir. Buradaki ilk avantaj, bir referansın çıktı parametresi olarak kullanılabilecek olmasıdır. İkinci ve daha önemli bir avantaj ise, bir kopyalama işleminin yapılmamasından dolayı fazladan bir kaynak kaybının olmamasıdır. Eğer nesneniz çok fazla bellek kullanıyorsa, referanslar daha iyi tercih olacaktır. .NET’te referans türünün bir dezavantajı CLR tarafından manage edilmesi ve garbage-collection işlemi yapılması gerektiğinden daha fazla işlemci kullanımına neden olmasıdır. .NET’te destruction’a en yakın konset finalization’dır ancak C++’daki destructorların aksine finalization nondeterministik bir kavramdır. Finalization’ın ne zaman olduğunu bilmeyiz çünkü finalization garbage collector çalıştığında (genellikle sistemdeki bellek oranı yetersiz kaldığında) olur. Referans türleri sınıfları, arabirimleri, dizileri ve delegate leri içerir. Aşağıda örnek bir C# kodu yer alıyor.

Class Araba {}                    // class
İnterface Isteering {}            // interface
İnt[] a = new int[5];             // array
Delegate void Process ();  // delegate

Sınıfları, delegateleri ve interfaceleri kısaca inceleyeceğiz.

Boxing ve Unboxing
Microsoft .NET performans gerekçelerinden dolayı value type ları destekler ancak gerçekte .NET’teki herçey bir nesnedir. Gerçekte önemli tüm türler için .NET Frameworkte denk sınıflar vardır. Örneğin int aslında System.Int32’nin diğer bir adıdır ve System.Int32 System.Valuetype’dan türer, yani bu bir value type dır. Value typelar genelde stack üzerinde ayrılmışlardır ancak alue type lar her zaman boxing olarak adlandırılan, head-based reference-type nesnelere dönüştürülebilirler. Aşağıdaki kod ir box’ı nasıl oluşturabileceğimizi ve i’nin değerini bu box’a nasıl kopyalayacağımızı gösteriyor:

int i = 1;                      // i – value nesnesi
object box = i;             // box – reference nesnesi

Bir değeri box’a kopyaladığınız zaman, metotlar, özellikler ve eventler talep edebileceğiniz bir nesne oluşturmuş olursunuz. Örnek olarak bir integeri bir nesneye dönüştürdüğünüz zaman, System.Object içinde tanımlı ToString(), Equals() vb. metotları kullanabilirsiniz.

Boxing işleminin tersine yani heap-based reference-type nesnesinin kendi value-type karşılığına dönüştürmeye tahmin edebileceğiniz gibi unboxing diyoruz. Aşağıda bu işlemin nasıl yapıldığını gösteren bir örnek var:

int j = (int)box;

Sınıflar, indeksleyiciler ve özellikler
CLR nesneye yönelik konseptler ve sınıf özellikleri için çok büyük destek sağlar. Ek olarak CLR, indeksleyiciler, özellikler ve eventler gibi geleneksel pek çok nesneye yönelik programlama dilinde yer almayan özellikleri destekler.

Property, field’a çok benzer, bir getter ve bir setter metotu mevcuttur.

public class Car
{
private string make;
public string Make

{
get { return make; }
set { make = value; }
}
}
Car c = new Car( );
c.Make = "Acura"; // setter
String s = c.Make; // getter

Böyle bir syntax ile ilk kez karşılaşıyor olabilirsiniz ancak bu örnek gerçekten kendi kendini açıklıyor.

Property’e oldukça benzemekle birlikte indexer C++ dilindeki operator[]’e oldukça benzer, bir nesnenin içeriğine array-like erişime imkan verir. Diğer bir deyişle size bir nesneye bir diziye erişir gibi erişmenize imkan verir. Nasıl?.. Örnek J

public class Car
{
private string[] wheels;
public string this[int index]
{
get { return wheels[index]; }
set { wheels[index] = value; }
}
}

Car c = new Car( );
c[0] = "LeftWheel"; // c[0] bir l-value veya r-value olabilir

Bu konuyu bitirmeden önce belirtmemiz gereken son bir not daha var. .NET, C++’tan farklı Java’ya benzer olarak single-implementation inheritance ı destekler. (Bunu Türkçe yazsam daha zor anlaşılırdı sanırım. En azından ben daha zor anlardım.)

Interface’ler
Interface’ler, C++’daki abstract tabanlı sınıflar (ABC) ile aynı konsepti desteklerler. ABC bir veya daha fazla virtual fonksiyon deklare eden bir sınıftı. Eğer COM veya Java hakkında bilginiz varsa .NET’teki interface kavramının, Java’daki veya COM’daki interface kavramı ile aynı olduğunu söyleyebiliriz. Sizin interface’inizden türeyen bir sınıf interface’inizi implement etmelidir. Bir interface metotlar, özellikler ve indeksleyiciler içerebilir. .NET’te bir sınıf birden çok interface’den türeyebilir.

Temsilciler (Delegates)
C dilinin en güçlü özelliklerinden biri fonksiyon işaretçilerini desteklemesiydi. Fonksiyon işaretçileri size yazılımınızı bir başkası tarafından tamamlanabilecek hooklar içererek hazırlamanıza imkan verir. Gerçekte fonksiyon işaretçileri bize genişleyebilir veya özelleştirilebilir yazılımlar geliştirme imkanı sunar. Microsoft .NET, bu fonksiyon parametrelerin bir type-safe versiyonunu, temsilcileri destekler. Aşağıda anlaşılması oldukça kolay bir örnek yer alıyor.

using System;
class OrnekTemsilci
{
// 1. callback örneği tanımlanıyor
delegate void MsgHandler(string strMsg);

// 2. callback metotu tanımlanıyor
void OnMsg(string strMsg)
{
Console.WriteLine(strMsg);
}

public static void Main( )
{
OrnekTemsilci t = new OrnekTemsilci ( );
MsgHandler f = new MsgHandler(t.OnMsg);
f("Merhaba, temsilci!");
}
}

Kodumuzu açıklayalım. Ele almamız gereken ilk şey delegate keywordü. Bu keyword derleyiciye nesneye dayalı bir fonksiyon işaretçisi istediğimizi bildiriyor. Derleyici System.MulticastDelegate’den Messagehandles adlı bir sınıf türetiyor. Bir multicast temsilci, single-cast temsilcinin aksine pek çok receiverı destekler. Örneğinizi bir defa tanımladıktan sonraörneğinizle uyuşan bir imzaya sahip bir metot tanımlamanız gerekir.

Common Language Specification (CLS)
.NET’in hedeflerinden biri, pek çokd efa belirttiğimiz gibi dillerin bütünleşmesini, daha açık bir ifadeyle, yazılım geliştiricilerin bir uygulamayı .NET destekleyen dillerden istediğiyle yazmasını, geliştirdikleri uygulamaların diğer dillerle uyumlu olmasını ve inheritance, polymorphism, exceptionlar ve diğer özelliklerin tüm avantajlarından faydalanmasını sağlamaktır. Ancak elbette diller arasında bazı farklılıklar mevcuttur. Örneğin Managed C++ case sensitive bir dildir ancak VB.NET değildir. Farklı dilleri kullanan geliştiricilerin aynı kolaylıklara (ve zorluklara) sahip olması için Microsoft, Common Language Specification adlı kurallar dizisini sunuyor. CLS dil bütünleşmesini sağlamak için bir dizi temel kuralar tanımlar. Microsoft CLS’i duyurmadan önce derleyici üreticileri .NET’i destekleyen derleyicileri kendi tanımladıkları kurallarla geliştirebilir ve bu diller arasında uyumsuzluğa neden olabilirdi. Dil bütünleşmesini sağlamak için derleyici geliştiricilerin yanısıra, uygulama geliştiricilerinde CLS’i iyi bilmeleri ve CLS’in kurallarını uygulamaları gerekir.

CLS başlı başına bir yazı konusu olduğu için ne olduğuna dair temel bilgileri vererek geçiyorum. Ancak yukarıda belirttiğim gibi CLS oldukça önemli bir konudur ve .NET geliştiricileri tarafından iyi bilinmelidir. CLS ile ilgili detaylı bilgiyi MSDN’de “Collected CLS Rules” başlıklı dokumandan ve .NET SDK ile birlikte gelen “Common Language Infastructure (CLI)” başlıklı dokumandan alabilirsiniz.

CLR Execution
Bir .NET executable dosyasının elemanlarını iyice anladığımıza göre artık CLR’ın destek yönetimi ve .NET assemblylarının yürütülmesi için sunduğu servisleri incelemeye başlayabiliriz. .NET’te pek çok etkileyici bileşen mevcuttur, biz sözü uzatmamak için şekil: 1.4’te gördüğümüz en önemli bileşenleri ele alacağız.


Şekil: 1.4

CLR’in en önemli bileşenleri class loader, verifier, JIT derleyicileri ve kod yönetimi, güvenlik yönetimi, exception yönetimi, debug yönetimi, marshaling management yönetimi, thread yönetimi gibi execuion desteği bileşenleridir. Şekil: 1.4’te gördüğünüz gibi .NET PE dosyaları CLR katmanının en üstünde bulunur ve CLR’ın runtime’ın önemli bileşenlerini barındıran Virtual Execution Engine (VEE) adlı bileşeni ile çalıştırılır. .NET PE dosyalarınız çalıştırılmadan önce class loadera, sonra type verifiera, sonra JIT derleyicilere ve daha sonrada diğer execution destek bileşenlerine iletilir.

Class Loader
Standart bir Windows uygulamasını çalıştırdığınızda işletim sistemi bu uygulamayı çalıştırmadan önce yükler. Bu yazma esnasında Windows 98, Me, 2000 gibi Windows işletim sistemlerindeki varsayılan yükleyiciler sadece standart Windows PE dosyalarını tanırlar. Sonuç olarak Microsoft tüm bu işletim sistemleri için .NET’i destekleyen yeni bir işletim sistemi yükleyicisi sundu. Güncellenen işletim sistemi yükleyicileri yeni .NET PE dosya türünü tanıyarak çalıştırabiliyorlar.

Bu işletim sistemlerinden birinde bir .NET PE dosyasını yüklediğiniz zaman, işletim sistemi yükleyicisi .NET uygulamasını tanır ve böylece kontrolü CLR’a bırakır. Daha sonra CLR genellikle Main() olan giriş noktasını bulur

Ve bu fonksiyonu çalıştırır. Ancak Main() fonksiyonunun çalıştırılabilmesi için önce sınıf yükleyicisi Main() fonksiyonunu sunan sınıfı bulmalı ve yüklemelidir. Kısaca sınıf yükleyicisi bir tür ilk kez refere edildiğinde çalışır ve önemli bir işlevi vardır.

Sınıf yükleyicisi .NET sınıflarını hafızaya yükler ve çalıştırılmak için hazırlar. Bunu başarılı bir şekilde yapabilmesi için hedef sınıfı belirlemelidir. Hedeflenen sınıfı bulabilmek için sınıf yükleyicisi farklı yerlere bakar. Örneğin uygulama ile aynı dizinde yer alan uygulama konfigürasyon (.config) dosyası, GAC, PE dosyasının bir bölümü olan metadata ve özellikle manifest. Bunlardan bir veya daha fazlası tarafından sağlanan bilgi, doğru sınıfın yüklenmesinde son derece önemlidir.

Sınıf yükleyicisi hedeflenen sınıfı bulduktan ve yükledikten sonra sonra sınıfa ait tür bilgisini alır böylece işlem boyunca bu sınıfı tekrar yükleme ihtiyacı duymaz.  Bu bilgiyi almakla beraber bu sınıfın oluşturulan yeni instanceı için ne kadar bellek gerektiğide tesbit edilir. Sınıf yüklendiğinde, sınıf yükleyicisi yüklenen sınıftaki her single metota küçük bir bilgi ekler. Bu bilgi iki iş için kullanılacaktır. İlki JIT compile işleminin durumunu göstermek, ikincisi ise managed ve unmanaged kod arasında geçiş işlemidir. Bu noktada yüklenen sınıf başka sınıfları refere ediyorsa, sınıf yükleyicisi refere edilen sınıflarıda yüklemeye çalışacaktır. Ancak refere edilen türler daha önceden yüklenmişse sınıf yükleyicisi yeniden yüklemeye çalışmayacaktır.

JIT Derleyiciler
JIT derleyiciler .NET içinde oldukça önemli bir role sahiptirler. Bunun nedeni .NET Platformunda PE dosyalarının native kod değil, Metadata ve IL içermesidir. JIT derleyicileri IL kodunu native koda dönüştürür böylece kodlar platform üzerinde çalıştırılabilirler. Type safety için denetlenen ve problemsiz olan her metot CLR içindeki JIT derleyici tarafından compile edilir ve managed native koda dönüştürülür. Managed native kod, execution-destek bileşenlerinin sadece managed kodu manage etmesi ve çalıştırmasından dolayı önemlidir.

JIT derleyicilerin bir avantajıda, hedeflenen bilgisayar için optimize edilmiş kodu dinamik olarak compile edebilmesidir. Bir .NET PE dosyasını tek işlemcili bir bilgisayardan alıp iki işlemcili bir bilgisayarda çalıştırırsanız, iki işlemcili bilgisayardaki JIT derleyici ikinci işlemciyle ilgili bilgilere sahip olacağından native kodu ikinci işlemcidende faydalanacak şekilde üretecektir.  Bir diğer önemli avantajınızsa, kodu bir kere yazmak ve CLR’a sahip tüm platformlarda çalıştırabilmenizdir. Bu platform Windows olabilir, Unix olabilir, buzdolabı olabilir..

Çeşitli optimizasyon nedenlerinden dolayı JIT compilation işlemi sadece bir metotun ilk kez çağrılmasında gerçekleştirilir. Sınıf yükleyicinin her metotun sonuna bir bilgi eklediğini hatırlayın. Metotun ilk çağrılışında VEE, bu metotun JIT derleme işleminden geçmediğini belirten bu bilgiyi okur. Bu noktada JIT derleyici metodu derler ve managed koda ait adres bilgisini metotun sonundaki bilgiye ekler. Bu metot bir daha çağrıldığında VEE metotun sonunda yer alan native metota ait adres bilgisini bulacağından tekrar derlenmez.

Derlenmiş native kod proses sonlandırılana ve carbage collector bu prosese ait tüm referansları temizleyene kadar bellekte kalır. Bu, bu bileşeni veya uygulamayı bir dahaki çalıştırışınızda, JIT derleyicinin tekrar JIT compilation yapacağı anlamına gelir.

Execution Desteği ve Yönetimi
Buradan sonra, CLR’da şu ana kadar işlediğimiz tüm bileşenlerin desteklediği servisleri başarıyla sunabilmek için metadata ve IL’i bir şekilde kullandığını göreceksiniz.  Sunulan metadata ve oluşturulan managed koda ek olarak, JIT derleyici code managerın stack çerçevelerini belirlemek ve çözmek için ihtiyaç duyacağı managed datayı üretmek zorundadır. Code Manager managed dataya, kodun çalıştırılmasını kontrol etmek için ihtiyaç duyar. Code Manager’ın yanısıra CLR aynı zamanda pek çok önemli execution-destek ve yönetim servisi sunar. Bu servisleri yazmak bir makalenin sınırlarını aşacağından sadece birkaçını inceleyeceğiz.

Garbage Collection
C++’ın aksine, CLR heap-tabanlı tüm nesneleri bizim silmemizi gerektirmek yerine tüm .NET nesneleri için otomatik bir ömür tanımlayabilmemizi sağlıyor. Garbage Collector artık kullanılmayan nesneleri tesbit ederek gereksiz bellek harcamasını önlüyor.

Exception Handling
.NET öncesinde exception veya hata tuzaklama için tutarlı bir yöntem yoktu bu da hata tuzaklama ve raporlamasında “acıya” neden oluyordu. .NET’te CLR standart bir exception handling mekanizmasını destekler ve bu mekanizma tüm dilleri destekler ve tüm programların genel bir hata tuzaklama mekanizmasını kullanmasına olanak tanır. CLR exception-handling mekanizması Windows Structed Exception handling (SEH) ile bütünleştirilmiştir.

Güvenlik Desteği (Security Support)
CLR runtime esnasında kodun çalıştırılmasının güvenli olduğundan emin olmak için ve kodun herhangi bir güvenlik kuralına uymamasından emin olmak için pek çok güvenlik denetimi yapar. Code-Access güvenliğe ek olarak güvenlik motoru declarative ve imperative güvenlik denetimlerinide destekler. Declarative güvenlik herhangi bir güvenlik kodu gerektirmez ancak güvenlik gereksinimlerini öznitelikler veya yönetimsel konfigürasyon ile tanımlamanız gerekir. Imperative güvenlik ise metotunuz içinde spesifik olarak güvenlik denetimi yapabilmek için kod yazmanızı gerektirir.

Degugging Desteği
CLR debugging için oldukça fazla destek sağlar. Derleyici üreticilerinin bir debugger geliştirmelerine olanak tanıyan bir API mevcuttur. Bu API pek çok denetim yapılmasına imkan tanır.

Interoperation Desteği
CLR, managed ve unmanaged kodlar arasında operability desteği verir. COM’un Interop özelliği COM ve CLR arasında bir köprü görevi görür ve bir COM nesnesinin bir .NET nesnesini kullanmasını ve tam tersi işleme izin verir. Platform Invoke (P/Invoke) özelliği ise Windows API fonksiyonlarını çağırmanızı sağlar.

 

Bu yazıda .NET mimarisinin en önemli bileşeni olan Common Language Runtime’ı zaman zaman biraz konu dışına çıkarak detaylı olarak incelemeye çalıştık. Metadatanın programlamayı ne kadar kolaylaştırdığını gördük ve registrynin önemini nasıl kaybettiğini gördük. .NET’in “bir kez yaz her yerde çalıştır” kavramını inceledik. Umarım faydalı olmuştur.

 

Kadir Sümerkent
kadirsumerkent@msn.com
ICQ: 162200997

 

 

 

Kaynaklar:
   MSDN + techNET
   .NET Framework SDK
   OR .NET Framework Essentials
   GotDotNet