.NET teknolojisi, COM nesnelerini yok saymayıp aksine onlarla bütünleşik bir çalışma ortamı sunar. Hem COM nesnelerinin .NET içinde kullanılmasına hem de .NET nesnelerinin COM içinde kullanılmasına destek verir. Birçok şeyi .NET nesneleriyle daha kolay ve hızlı yapabiliz ancak yeni projelerde bazı eski COM nesnelerimizi kullanmak zorunda kalabiliriz. Bu durumda nasıl bir etkileşim sözkonusu olacak. Bu yazıda .NET bileşenlerinin var olan COM bileşenlerini değiştirmeden onlarla nasıl iletişim kuracaklarını işleyeceğiz.
COM mimarisinde bileşenler arası haberleşmeler, IUnknown ve IDispatch gibi arabirimler aracılığıyla gerçekleşmekteydi. Fakat .NET’te bileşenler arası haberleşmeler, Object’ler aracılığıyla gerçekleştirilir. .NET’te type libraryler bulunmayıp onun yerine assemblyler kullanılır.
Type library, COM bileşenine ait arabirimleri ve veri tiplerini açıklayan metaveriyi içeren derlenmiş dosyadır(.tlb). COM componentini MTS’e yüklemek için gerekli olan bilgileri sağlar. Bilindiği gibi Assembly, işlevsel olarak birbirleriyle ilişkili tür ve kaynakların oluşturduğu mantıksal bir birimdir(.dll/.exe). Assembly ile ilgili tüm bilgiler, assemblynin metadatasında saklanır.
COM Nesnelerini .NET’ten Çağırmak
Mimarilerinin farklı oluşundan dolayı bir .NET bileşeni, COM bileşenleriyle doğrudan haberleşemez. Çünkü COM’un iletişim için sunmuş olduğu Interfaceler, .NET uygulaması tarafından okunamaz. Bu yüzden, COM bileşeni, çalışma zamanında .NET uygulamasının anlayabileceği şekilde özel bir bileşenle kaplanır. Bu bileşene RCW(< ı>Runtime callable wrapper-Çalışmazamanında çağrılabilen sarıcı) denilir. RCW, çalışma zamanında COM bileşenini sarar ve CLR ile arasına girer. .NET istemci ile COM bileşeni arasında bir bağ kurulur böylece COM bileşeni, .NET uygulamasına normal bir .NET bileşeniymiş gibi görünür. Burada COM bileşenlerinin sunucuya register edilmiş olması gerekmektedir.
NET ortamında bir client, COM bileşenine erişmek istediği zaman RCW’i iki şekilde oluşturur.
Her iki yöntem de temel olarak System.Runtime.InteropServices.TypeLibConverter sınıfını kullanır.
Bu iletişim sürecini hafıza yönetimi açısından değerlendirecek olursak, RCW, COM bileşenini Garbage Collector çalışıp çöpler toplanıncaya serbest bırakmaz. Garbage Collector RCW içindeki Finalize metodunu çağırır. RCW de içerden COM nesnesi içinde IUknown interfacenin Release metodunu çağırır. Fakat COM nesnesini memoryden temizlemek için GC’yi beklemek istenilmeyen durumlar oluşturabilir. Bu durumda COM nesnesiyle işimiz bittikten sonra GC’yi manual çalıştırabiliriz. Ancak GC’nin o andaki yükünün yoğun olması diğer işlemleri yavaşlatabilir. GC’nin o anda sorumlu tüm nesneler için çalışmasını değil de sadece bu COM nesnesini yok etmek istersek < ı>System.Runtime.InteropServices.Marshal.ReleaseComObject fonksiyonu kullanılabilir.
Şimdi yazdıklarımızı bir örnek üzerinde uygulayalım. Kendimiz VB 6.0 gibi bir ortamda unmanaged bir component yazıp .NET ortamında kullanabiliriz. Ancak burada yeni bir component yazmak yerine Office ile birlikte gelen Excel uygulamasına ait EXCEL.EXE içindeki bileşeni örnek olarak tercih edeceğiz. Öncelikle yeni bir Windows projesi oluşturalım. Daha sonra projenin references bölümünde Add Reference pencesini açtıralım. Buradaki COM sekmesinde Microsoft Excel 11.0 nesnesini ekleyelim. Bu nesne, benim makinemde “< ı>c:\Program Files\Microsoft Office\OFFICE11\excel.exe” pathinde bulunmaktadır.
Bu bileşeni referansları eklediğimiz Visual Studio, otomatik olarak ilgili RCW componentlerini oluşturur.
Projede Form’un üzerine bir Button ekleyelim. Button’a tıklandığı zaman program içinde şekillenmiş bir Excel dosyası oluştursun. Bu componentte hiyerarşik olarak şu şekilde bir yapı bulunur.
Application: Bileşenin en üstünde bulunur. Workbooklara erişir.
Workbook: Excel dosyasının oluştuğu spreadsheet grubudur.
Worksheet: Bir excel spreadsheet’idir.
Range: Bir ve daha fazla hücreyi temsil eder.
Imports System Imports System.Windows.Forms Imports System.Reflection Imports System.Runtime.InteropServices Public Class Form1 Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click 'Excel nesnesi oluşturalım. Dim ExcelApp As Excel.ApplicationClass = New Excel.ApplicationClass() 'Excel'e bir Worksheet ekleyelim. ExcelApp.Workbooks.Add(True) 'Hücrelere gerekli başlıkları ve değerleri atalım. ExcelApp.Range("A1", Missing.Value).Value = "Sayı" ExcelApp.Range("B1", Missing.Value).Value = "Karesi" ExcelApp.Range("A1", Missing.Value).Font.Bold = True ExcelApp.Range("B1", Missing.Value).Font.Bold = True For i As Integer = 2 To 7 ExcelApp.Range("A" & i, "A" & i).Value = i ExcelApp.Range("B" & i, "B" & i).Value = i * i Next i 'Excel dosyasını kullanıcıya gösterelim. ExcelApp.Visible = True 'COM nesnesini hafızadan kaldıralım. Marshal.ReleaseComObject(ExcelApp) End Sub End Class
using System;
using System.Windows.Forms;
using System.Reflection;
using System.Runtime.InteropServices;
namespace WindowsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
//Excel nesnesi oluşturalım.
Excel.ApplicationClass ExcelApp = new Excel.ApplicationClass();
//Excel'e bir Worksheet ekleyelim.
ExcelApp.Workbooks.Add(true);
//Hücrelere gerekli başlıkları ve değerleri atalım.
ExcelApp.get_Range("A1", Missing.Value).set_Value(Missing.Value, "Sayı");
ExcelApp.get_Range("B1", Missing.Value).set_Value(Missing.Value, "Karesi");
ExcelApp.get_Range("A1", Missing.Value).Font.Bold = true;
ExcelApp.get_Range("B1", Missing.Value).Font.Bold = true;
for (int i = 2; i < 8;="">
{
ExcelApp.get_Range("A"+i, "A"+i).set_Value(Missing.Value, i);
ExcelApp.get_Range("B" + i, "B" + i).set_Value(Missing.Value, i*i);
}
//Excel dosyasını kullanıcıya gösterelim.
ExcelApp.Visible = true;
//COM nesnesini hafızadan kaldıralım.
Marshal.ReleaseComObject(ExcelApp);
}//button1_Click
}
}
Bu programı çalıştırıp butonu tıkladığımızda içinde aşağıdaki değerlerin olduğu bir excel dosyası açılır.
Eğer bu uygulamada daha ilk satırda aşağıdaki gibi hata alırsanız bu, sizden değil Office’den kaynaklanıyor. Excel’in İngilizce versiyonunda ortaya çıkan bu bug, bilgisayarınızın dilinin İngilizce olmamasından kaynaklanmaktadır.
Bunu aşmak için Excel nesnesi oluşturmadan önce uygulamanın Culture değerini İngilizce’ye göre ayarlayabilirsiniz. Sayfa sonunda Culture bilgilerini tekrar geri alırsınız. Veya Control Panel’den Regional Settings and Languages bölümünden English(US)’i seçebilirsiniz.
Dim thisThread As System.Threading.Thread = _ System.Threading.Thread.CurrentThread Dim originalCulture As System.Globalization.CultureInfo = _ thisThread.CurrentCulture thisThread.CurrentCulture = New System.Globalization.CultureInfo("en-US") '::::::::Excel ile ilgili kodlarımız::::::::::::: 'Culture değerlerimizi geri alalım. thisThread.CurrentCulture = originalCulture
System.Threading.Thread thisThread = System.Threading.Thread.CurrentThread; System.Globalization.CultureInfo originalCulture = thisThread.CurrentCulture; thisThread.CurrentCulture = new System.Globalization.CultureInfo( "en-US"); //::::::::::Excel ile ilgili kodlarımız:::::::::::::: //Culture değerlerimizi geri alalım. thisThread.CurrentCulture = originalCulture;
Visual Studio’nın tercih edilmediği durumlarda TlbImp aracı kullanılabilir. Command Prompt’dan; TlbImp
.NET Nesnelerini COM’dan Çağırmak
Yönetilemeyen COM nesnelerini, .NET platformunda nasıl kullanılacağını öğrenmiş olduk. .NET, bu işlemin tersine de destek verir. Yani programcılıkta nadir de olsa .NET nesneleri COM istemcileri tarafından kullanılabilir.
Bir COM istemci, bir sunucuya istek göndereceği zaman öncelikle onu registry kayıtlarında arar ve iletişimi öyle başlatır. Önceki sayfada .NET bileşenlerinin, Object’ler aracılığıyla iletişim kurduğunu söylemiştik. Object tabanlı bir etkileşim, COM istemcileri tarafından tanınmadığı için, COM bileşeninin içinden .NET bileşenine erişmek için .NET bileşenini COM istemcisinin tanıyabileceği şekle dönüştürmek gerekir(Wrapping). Bu dönüştürücüye CCW(< ı>COM callable wrapper-COM cağrılabilen sarıcı). CCW, RCW gibi bir tür olup, .NET assemblylerinin COM istemcileri tarafından referans verilmesini sağlar.
CCW, Framework ile birlikte gelen RegAsm.exe(Register Assembly) veya TlbExp.exe(Type Library Exporter) araçlarıyla oluşturulur. Bu araçlar, .NET bileşeninin metadatasını okur ve CCW türünü oluşturur. Ayrıca bileşen ile ilgili registry üzerinde kayıt açar.
Bilindiği gibi COM istemcileri genellikle coCreateInstance metodunu kullanarak bileşenden nesne yaratır. .NET componentinin COM içinde kullanıldığı senaryoda coCreateInstance metodu çağrıldığı zaman çağrı, registry kaydına yönlendirilir, registry de çağrıyı sunucu üzerinde register edilmiş mscoree.dll kütüphanesine yönlendirir. mscoree.dll, kendisine gönderilen CLSID’ye göre registry üzerinde arama yaparak .NET assemblysinin yerini öğrenir. Ve iletişim bu şekilde başlar. İstemci, .NET nesnesinden bir istekte bulunduğu zaman istek öncelikle CCW’ye gider. CCW, tüm COM türlerini .NET tarafındaki eşdeğerlerine dönüştürür ve .NET’ten geri dönen sonuçları COM’un anlayabileceği formata dönüştürür.
Bir .NET assemblysi COM istemcileri tarafından erişilecekse sırayla şu adımlar uygulanır;
1. Bir Strong Name verilmesi
2. Assemblynin GAC’a yerleştirilmesi.
3. Assemblynin bir COM nesnesiymiş gibi register edilmesi.
4. COM nesnesinin çağrılması.
Bilindiği gibi eğer assembly sistem klasöründe veya uygulamayla aynı konumdaysa GAC’a atılması bir gereklilik değildir. Ve Strong Name, sadece assembly, GAC’a kurulacağı zaman gereklidir. Ancak .NET bileşeni COM tarafından kullanılma durumunda yukarıdaki aşamaların uygulanması tavsiye edilir.
COM istemcisi, .NET bileşenin içindeki tüm üyelere erişemez. Sadece public üyelere erişebilir. Ayrıca parametreli constructor, statik metotlara ve sabit alanlara erişemez.
Bir Class Library projesi oluşturup küçük bir örnek yapalım. Class1 dosyasını Matematik olarak aşağıdaki şekilde değiştirelim.
Imports System Imports System.Collections.Generic Imports System.Text Public Class Matematik 'Sayının faktoriyelini alır. Public Function Faktoryel(ByVal Sayi As Integer) As Integer Dim result As Integer = 1 Dim i As Integer = 2 Do While i [csharp]using System; using System.Collections.Generic; using System.Text; namespace ClassLibrary1 { public class Matematik { //Sayının faktoriyelini alır. public int Faktoryel(int Sayi) { int result = 1; for (int i = 2; i Bir Strong Name dosyasıyla birlikte projeyi derledikten sonra GAC'a kayıt edelim daha sonra command prompt'dan .dll dosyasını RegAsm ile register edelim. Bu işlemi gerçekleştirirken aşağıdaki hata mesajıyla karşılaşacaksınız. <table bgcolor="#B2B2B2"> <tr> <td>Microsoft (R) .NET Framework Assembly Registration Utility 2.0.50727.42<br /> Copyright (C) Microsoft Corporation 1998-2004. All rights reserved. RegAsm : warning RA0000 : No types were registered</td> </tr> </table> Bu hata mesajının bir çok nedeni olabilir ancak örneğimizde COM istemcilerinin erişebileceği bir sınıfın olmamasından kaynaklanmaktadır. COM istemcilerinin bir bileşene erişebilmesi için o bileşeni için <b>ComVisible</b> niteliği < ı>true yapılır. ComVisible sınıfı, System.Runtime.InteropServices'nın altında bulunur. Bu ifadeyi her clasın başında belirleyebileceğimiz gibi doğrudan AssemblyInfo dosyasından da değiştirebiliriz. AssemblyInfo dosyasında default olarak false olan ComVisible değerini true yapalım. [vb] [csharp][assembly: ComVisible(true)][/csharp] Şimdi RegAsm komutunu uygularsak, "< ı>Types registered successfully" mesajını alırız. Sunucuya register ettiğimiz bu componenti herhangi bir COM istemcisinde kullanabiliriz. Burada bir VBScript dosyası oluşturacağız aynı örnek bir asp sayfasında da kullanılabilir. Ornek.vbs isminde bir dosya oluşturup aşağıdaki gibi düzenleyelim. [vb]Dim objMath Set objMath = CreateObject("ClassLibrary1.Matematik") msgbox("5'in faktöriyeli :"& objMath.Faktoryel(5))
.vbs dosyasını çift tıklayıp çalıştırdığımız zaman ekrana 5’in faktöriyelini yazdırır. Eğer .vbs dosyasında Kare metoduna erişmek istersek Kare private olduğundan COM istemciler tarafından erişelemeyeceği için hata mesajı verilecektir.
Bu bileşen ile ilgili registry kaydına bakmak için registry’da “HKEY_LOCAL_MACHINE » Classes” (< ı>HKEY_CLASSESE_ROOT » Classes Win XP)bölümünden componentin CLSID’sini öğrenelim.
Buradan öğrendiğimiz CLSID’yi “HKEY_LOCAL_MACHINE » Classes » CLSID” klasörünün altında arayalım.
Şekilde InprocServer32 altında mscoree.dll dosyası görünmektedir. Bu da gösteriyor ki unmanaged istemcilerin muhatabı doğrudan bizim managed assembly değil assemblyi uyarlayacak olan CLR çalıştırma ortamıdır. InprocServer32’nin altındaki Class deyimi mscoree.dll dosyasına hangi sınıftan nesne oluşturacağını, Assembly değeri de bu sınıfın hangi assemblyde saklı olduğunu bildirir.
Bilindiği gibi COM bileşenlerinden iki şekilde nesne oluşturulur; Geç bağlama(late binding) ve erken bağlama(early binding). Erken bağlama klasik ASP’de kullandığımız CreateObject yöntemidir. Geç bağlama ise VB 6.0’da olduğu gibi New operatörünü kullanmaktır. Geç bağlama durumunda örneğimiz için şu şekilde bir ifade oluşturulur;
Dim Math As Matematik 'Veya Set objMath = New ClassLibrary1.Matematik objMath.Faktoryel
Geç bağlama yönteminde VB 6.0’da type library referanslara eklenip öyle oluşturuluyordu. Bu yüzden eğer .NET componenti early binding yöntemiyle kullanılacaksa öncelikle type library’nin oluşturulması gerekir. Bunun için de ya
TlbExp ClassLibrary1.dll
şeklinde bir TlbExp aracı kullanılır ya da RegAsm’nin tlb parametresi kullanılır;
RegAsm ClassLibrary1.dll /tlb: ClassLibrary1.tlb
.NET nesnesini registry kayıtlarından kaldırmak için de “U” parametresi kullanılır.
RegAsm ClassLibrary1.dll /U
.NET’de ActiveX Kullanımı
Klasik COM bileşenleri dışında görsel bileşen olan ActiveX’leri de .NET uygulamalarında kullanabiliriz. Bunun için de yine aynı şekilde Visual Studio kullanılabileceği gibi Aximp.exe (ActiveX Control Importer) aracı da kullanılır. Buna örnek olarak MS Web Browser’ı(shdocvw.dll) verebiliriz. Bu ActiveX’in bileşeni benim makinemde < ı>c:\windows\system32\shdocvw.dll altında bulunmaktadır. VS’da yeni bir windows projesi oluşturup Toolbox’ı sağ tıklayıp “Choose Items” menüsünden “Choose Toolbox Items” penceresini açtırıp COM Components sekmesini tıklayalım. Burada Microsoft Web Browser‘i seçip bileşenin Toolbox’a ekleyelim.
Bu nesneyi toolboxtan Form üzerine taşıyıp projeyi derlediğimizde ilgili RCW bileşenleri oluşur.
AxWebBrowser nesnesinin özelliklerini properties panelinden ayarlayabiliriz. Ben, browser, formun tümüne yerleşsin diye Dock özelliğini < ı>Fill olarak set ettim.
Me.AxWebBrowser1.Dock = System.Windows.Forms.DockStyle.Fill
this.axWebBrowser1.Dock = System.Windows.Forms.DockStyle.Fill;
Ve form açıldığı zaman Google adresini gösterecek şekilde ayarlayalım.
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load AxWebBrowser1.Navigate("https://www.google.com.tr") End Sub
private void Form1_Load(object sender, EventArgs e){ axWebBrowser1.Navigate("https://www.google.com.tr"); }
VS 2005 ile birlikte WebBrowser nesnesi geliyor olsa da eski COM bileşenini de bu şekilde kullanabiliriz. Programı derlediğimizde aşağıdaki görüntüyü elde ederiz.
Web Browser bileşeninin RCW’leri VS kullanılmadan AxImp komut aracılyla şu şekilde oluşturulabilir;
aximp c:\windows\system32\shdocvw.dll
Windows Kontrollerini IE içinde kullanmak
“.NET ortamında ActiveX oluşturmak” yerine bu başlığı seçmemin nedeni .NET platformunda gerçekten her yerde çalışabilecek bir ActiveX yapısının oluşturulamıyor olmasıdır. Windows formlarını, web browserlarda çalıştırabilmek için clientlarda .NET runtime’ın kurulu olması gerekiyor. Bu da şu aşamada çok zor olduğu için bu tür uygulamalar genellikle Intranet veya belli local alanlarda çalışma üzere geliştirilir.
Evet, CLR, sayfalarımızı daha işlevsel yapmak için herhangi bir Windows Form kontrolünü, herhangi bir HTML sayfasında kullanmamıza izin verir. Bunun için aşağıdaki adımlar takip edilir;
HTML sayfasında Windows DateTimePicker sınıfından üretilmiş bir takvim bileşeni oluşturalım. Onu da HTML sayfalarında kullanalım. VS’de adı OrnekKontrol olan bir Class Library projesi (Windows Control Library de olabilir) oluşturarak örneğimizi oluşturalım. DateTimePicker nesnesini kullanacağımız için projeye System.Windows.Forms kütüphesini referans olarak eklemeliyiz. Class1 dosyasını aşağıdaki şekilde düzenleyelim.
Imports System Imports System.Windows.Forms Imports System.ComponentModel Public Class Takvim Inherits System.Windows.Forms.DateTimePicker Public Sub New() MyBase.New() InitializeComponent() End Sub Private Sub InitializeComponent() 'DateTimePicker nesnesinin Format'ını belirleyelim. Me.Format = DateTimePickerFormat.Short End Sub End Class
using System; using System.Windows.Forms; using System.ComponentModel; namespace OrnekKontrol{ public class Takvim : System.Windows.Forms.DateTimePicker{ public Takvim(): base(){ InitializeComponent(); } private void InitializeComponent(){ //DateTimePicker nesnesinin Format'ını belirleyelim. this.Format = DateTimePickerFormat.Short; } }//Takvim }
Projeyi derleyip .dll dosyasını wwwroot altında Bilesen isimli bir klasöre yerleştirelim. wwwroot altında Ornek.html dosyasını oluşturalım. Bu tür kontroller, HTML içinde
Internet Explorer içinde client script dilleri kullanılarak bu kontrolün public üyelerine erişilebilir. Bunun için class için ComVisible niteliğini < ı>true yapmamız gerekir. Örneği biraz daha geliştirip IE içinden JavaScript aracılığıyla bu nesnenin bazı bilgilerine erişelim. Kodumuza < ı>Tarih isimli bir property ve < ı>FormatDegistir isimli bir metod ekleyelim.
Imports System Imports System.Windows.Forms Imports System.ComponentModel Public Class Takvim Inherits System.Windows.Forms.DateTimePicker Public Sub New() MyBase.New() InitializeComponent() End Sub Private Sub InitializeComponent() 'DateTimePicker nesnesinin Format'ını belirleyelim. Me.Format = DateTimePickerFormat.Short End Sub 'Tarih property Public ReadOnly Property Tarih() As String Get Return Value.ToShortDateString End Get End Property 'Formatı Long veya Short olarak değiştirir. Public Sub FormatDegistir() If Me.Format = DateTimePickerFormat.Long Then Me.Format = DateTimePickerFormat.Short Else Me.Format = DateTimePickerFormat.Long End If End Sub End Class
using System; using System.ComponentModel; using System.Windows.Forms; namespace OrnekKontrol { public class Takvim : System.Windows.Forms.DateTimePicker{ public Takvim(): base(){ InitializeComponent(); } private void InitializeComponent(){ this.Format =DateTimePickerFormat.Short; } //Tarih property public string Tarih{ get { return Value.ToShortDateString(); } } //Formatı Long veya Short olarak değiştirir. public void FormatDegistir() { if (this.Format == DateTimePickerFormat.Long) this.Format = DateTimePickerFormat.Short; else this.Format = DateTimePickerFormat.Long; } }//Takvim }
Html tarafını da aşağıdaki gibi düzenleyelim.
Şekilde görüldüğü gibi “Tarih Oku” butonu tıklandığı zaman Takvim nesnesinin Tarih propertysinden değer okunur. “Tarih Formatı Değiştir” tıklandığında da nesnenin FormatDegistir metodu tetiklenmiş olur.
Windows Form kontrolünü kullandığımız HTML sayfalarının IIS üzerinde bir virtual directory içinde olması gerekir. Virtual directorydeki Execute Permissions yetkisi scripts olarak set edilmelidir. scripts & executables olarak set edilirse kontrol yüklenmez.
.dll dosyası uygulamayla aynı klasörde olabildiği gibi GAC’a yüklenebilir. Ayrıca .dll üzerinde yaptığınız bir değişikliğin html tarafına yansıması için browseri kapatıp yeniden açmak gerekir.