Jump to content

Common Language Runtime


wmismail

Recommended Posts

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.

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.

.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 ® COFF/PE Dumper Version 7.00.9188

Copyright © 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.

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 ® .NET Framework IL Disassembler. Version 1.1.4322.510

// Copyright © 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 of Export Directory:

// 257c [4f ] address of Import Directory:

// 4000 [858 ] address of Resource Directory:

// 0 [0 ] address of Exception Directory:

// 0 [0 ] address of Security Directory:

// 6000 [c ] address of Base Relocation Table:

// 207c [1c ] address of Debug Directory:

// 0 [0 ] address of Architecture Specific:

// 0 [0 ] address of Global Pointer:

// 0 [0 ] address of TLS Directory:

// 0 [0 ] address of Load Config Directory:

// 0 [0 ] address of Bound Import Directory:

// 2000 [8 ] address of Import Address Table:

// 0 [0 ] address of Delay Load IAT:

// 2008 [48 ] address 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 of Metadata Directory:

// 0 [0 ] address of Resources Directory:

// 0 [0 ] address of Strong Name Signature:

// 0 [0 ] address of CodeManager Table:

// 0 [0 ] address of VTableFixups Directory:

// 0 [0 ] address of Export Address Table:

// 0 [0 ] address 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

Link to comment
Share on other sites

Archived

This topic is now archived and is closed to further replies.

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...