JsRT; JavaScript Çalışma-Zamanı Barındırma

   Hatırlayacak olursanız geçtiğimiz hafta kaleme aldığım makalemde sizlere JsRT (JavaScript RunTime)’dan bahsetmiştim. //Build/ konferansının büyük duyuruları arasında gözden kaçan JsRT duyurusu, Internet Explorer 9 ile birlikte tanıdığımız Chakra javascript motorunun biz yazılım geliştiriciler tarafından da kullanılabilecek olması nedeniyle önemliydi.

   Makaleyi kaleme aldığım sırada bu duyuru o kadar yeniydi ki; henüz ortada ne dokümantasyon ne de örnek kod bulunuyordu. Geçtiğimiz bir kaç gün içerisinde öncelikle kısa bir dokümantasyon, sonrasında da örnek kod yazılımcılarla paylaşıldı. Bu makalemde sizlere JsRT API’si yardımıyla C# içerisinde javascript çalışma-zamanını nasıl barındırabileceğinizi paylaşacağım.

   Koda elimizi bulaştırmadan önce temel JsRT konseptlerinden bahsetmek faydalı olacaktır. Her şeyden önce JsRT API’sinin Internet Explorer 11 ön izlemesi ile birlikte hayatımıza girdiğini belirtmeliyim. JsRT API’si aslında, Internet Explorer 9’dan beri kullanımda olan, Chakra javascript motorunun kullanımımıza sunulması. Hal böyle olunca Windows ve Internet Explorer bağımlı. Bunun anlamı Microsoft’un el-ayak çektiği Windows XP ve önceki sürümlerinde kullanımı söz konusu değil. JsRT API’sinin iki temel bileşeni bulunmakta; runtime (çalışma-zamanı) ve execution context (yürütme içeriği).

  Runtime; adından da anlaşılabileceği gibi javascript’in çalıştığı ortamdır. Javascript’in managed (yönetilen) bir dil olması nedeniyle doğal olarak runtime; garbage collection, Just-in-Time (JIT) derleyici gibi bileşenlerden oluşmaktadır. Her bir runtime kendi içerisinde ve bağımsız olarak bu bileşenlere sahiptir.

   Execution context ise runtime içerisinde yer alan isole alanlardır ve kendi JavaScript global nesnesine sahiptirler.  Bir runtime içerisinde birden fazla execution context yer alabilir. Execution context’in isole olması nedeniyle bir execution context’te yer alan değeler bir diğerine taşınamaz/kullanılamaz.

   Hatırlarsanız önceki makalemde size JsRT için managed bir kütüphanenin henüz olmadığından bahsetmiştim. Dolayısıyla da tüm işi P/Invoke çağrıları ile yapmamız gerekli. Şanslıyız yayınlanan ilk örnek kod parçacıkları C++’ın yanında C# ve Visual Basic’te de yayınlandı. Dolayısıyla da P/Invoke çağrılarıyla doğrudan boğuşmamıza gerek yok. Paylaşacağım örnek uygulamada yayınlanan örnek içerisindeki bu kodlardan faydalanacağım.

   Öncelikle, Internet Explorer 11 ön izlemesinin kurulu olduğu bir sisteme ihtiyacımız var. Ben bu iş için geçtiğimiz haftalarda incelemek adına VMware üzerine kurduğum Windows 8.1 ön izleme sanal makinesini kullanacağım. Aynı sanal makine üzerinde Visual Studio 2013 ön izlemesi de olduğunu peşinen belirteyim 😉

    İşe bir konsol projesi oluşturmakla başlayalım;

JsRT barındırmak için bir konsol projesi oluşturuyoruz

 

   Projemiz açıldıktan sonra ilk iş olarak Microsoft tarafından yayınlanan örnek C# kodları içerisinden hosting klasörünü komple projemize kopyalayalım;

Microsoft tarafından verilen örnek kodlardan Hosting klasörünü projemize kopyalayalım

   Konsol uygulamamız çalıştırılacak olan javascript dosyasını parametre olarak alacak. Dolayısıyla uygulama parametrelerini kontrolle başlıyoruz;

if (args == null || args.Length == 0) {
    Console.Error.WriteLine("Kullanım :  JSHosting <betik adı> <parametreler>");

    return;
}

   JsRT barındırmak için yazımın başlarında belirttiğim iki temel bileşeni, runtime ve execution context, oluşturmalıyız;

using (var calismaZamani = JavaScriptRuntime.Create()) {
    var context = calismaZamani.CreateContext();
    
    //diğer JsRT kodları
}

   Elimizde execution context’imiz olduğuna göre artık parametre ile belirtilen javascript dosya içeriği okuyarak çalıştırılması için context’e verebiliriz. Bu işlem için JavaScriptContext sınıfı içerisindeki statik RunScript fonksiyonunu kullanabiliriz;

using (new JavaScriptContext.Scope(context)) {
   var betikAdi = args[0];
   var betik = File.ReadAllText(betikAdi);

   var sonuc = JavaScriptContext.RunScript(betik, JavaScriptSourceContext.FromIntPtr(IntPtr.Zero), betikAdi);
}

       Yukarıdaki bloktan çıkıldığında artık kodumuz çalışmış olacaktır. Kodun çalışması sırasında herhangi bir hata oluşması durumunda runtime JavaScriptScriptException hatası fırlatacaktır. Dolayısıyla doğru bir hata yönetimi için yukarıdaki kodumuza try-catch bloğunu ekleyip bu hatayı dinlemeliyiz;

using (new JavaScriptContext.Scope(context)) {
   var betikAdi = args[0];
   var betik = File.ReadAllText(betikAdi);

   JavaScriptValue sonuc;
   try {
       sonuc = JavaScriptContext.RunScript(betik, currentSourceContext++, betikAdi);
   }
   catch (JavaScriptScriptException e) {
       var messageName = JavaScriptPropertyId.FromString("message");
       var messageValue = e.Error.GetProperty(messageName);
       var message = messageValue.ToString();

       Console.Error.WriteLine("JSHosting: Hata: {0}", message);
   }
   catch (Exception e) {
       Console.Error.WriteLine("JSHosting: Betik çalıştırılırken hata oluştu: {0}", e.Message);
   }
}

   Gördüğünüz gibi hata mesajı okuyabilmek için takip etmemiz gereken bir kaç adım bulunuyor. Hata mesajı runtime ile gelen hata nesnesinin message alanında yer almaktadır. Toparlayacak olursak kodumuz aşağıdaki gibi olacaktır;

using Enterprisecoding.JSHosting.Hosting;
using System;
using System.IO;

namespace Enterprisecoding.JSHosting {
    class Program {
        private static JavaScriptSourceContext currentSourceContext = JavaScriptSourceContext.FromIntPtr(IntPtr.Zero);

        static void Main(string[] args) {
            if (args == null || args.Length == 0) {
                Console.Error.WriteLine("Kullanım :  JSHosting <betik adı> <parametreler>");

                return;
            }

            using (var calismaZamani = JavaScriptRuntime.Create()) {
                var context = calismaZamani.CreateContext();

                using (new JavaScriptContext.Scope(context)) {
                    var betikAdi = args[0];
                    var betik = File.ReadAllText(betikAdi);

                    JavaScriptValue sonuc;
                    try {
                        sonuc = JavaScriptContext.RunScript(betik, currentSourceContext++, betikAdi);
                    }
                    catch (JavaScriptScriptException e) {
                        var messageName = JavaScriptPropertyId.FromString("message");
                        var messageValue = e.Error.GetProperty(messageName);
                        var message = messageValue.ToString();

                        Console.Error.WriteLine("JSHosting: Hata: {0}", message);
                    }
                    catch (Exception e) {
                        Console.Error.WriteLine("JSHosting: Betik çalıştırılırken hata oluştu: {0}", e.Message);
                    }
                }
            }

            Console.ReadKey();
        }
    }
}

   Kodumuzu test edebilmek için projeye test.js adıyla bir script ekleyip, her defasında çıktı klasörüne kopyalanmasını sağlayalım. test.js içeriğini ise konumuza uygun olarak aşağıdaki şekilde hazırlıyorum 🙂

alert("Merhaba Dünya");

  Kodumuzu derleyip, çalıştırdığımızdaaaa;

Kodumuzu bir araya topladıktan sonra çalıştırdığımızda ilk hatamızı aldık : Javascript kullanım hatası-geçersiz arguman

   hep birlikte ilk hatamıza merhaba diyelim;

An unhandled exception of type ‘Enterprisecoding.JSHosting.Hosting.JavaScriptUsageException’ occurred in Enterprisecoding.JSHosting.exe

Additional information: Invalid argument.

   Hata mesajını incelediğimizde Javascript kullanımımız ile ilgili bir hata olduğu görülüyor, geçersiz arguman; fakat hatayı tespitte yeterli bir bilgi değil. Call Stack penceresinden hatayı takip ettiğimizde Program sınıfı Main fonksiyonu içerisinde bir hata aldığımızı görebiliriz;

Hataya ait call stack

Hatanın meydana geldiği kod satırı   Betiğimizin çalışması sırasında aldığımız bir hatayı konsolda göstermeye çalışırken ikinci bir hata ile karşılaşılmış. Javascript’in doğası gereği dikkat edecek olursanız betik içerisindeği nesnelere reflection’ı anımsatan işlemlerle ulaşıyoruz. Bu noktada akla verdiğimiz string ile ilgili bir problem olduğu gelecektir. Microsoft tarafından sunulan örnek kodlar da dikkatlice incelenecek olursa assembly.cs içerisinde DllImport için varsayılan olarak unicode kullanılsın diye aşağıdaki gibi bir girdi olduğu görülecektir;

[module: DefaultCharSet(CharSet.Unicode)]

   Biz de aynı şekilde assembly.cs dosyasına bu girdiyi ekleyip kodumuzu yeniden derlersek asıl hata mesajını görebiliriz;

"alert" fonksiyonu tanımsız hatası

 

  Durun bir dakika, “alert” fonksiyonu tanımsız mı??! Javascript’in en temel fonksiyonlarından birisidir, alert.. ve tanımsız?

  Unutmayın JsRT ile bize sunulan JavaScript çalışma moturu. Bunu aynı .Net’in CLR gibi düşünün; ama Temel Sınıf Kütüphanesi (Base Class Library-BCL) olmayan bir CLR… Dolayısıyla da BCL olmadan C# ile ne yapabiliyorsak burada da durum aynı…

   Bu durumda iş başa düşüyor, bu harici javascript nesne ve fonksiyonlarını uygulamamızda bizim sunmamız gerekiyor. Bu da bizi makalemizin bir diğer kısmına geçiyor; harici fonksiyon tanımlama.

   Önce işin kolay tarafından başlayayım; kod içerisindeki Alert fonksiyonuma javascript runtime’ından erişebilmek. İşe aktif execution context’imin Global nesnesine ulaşarak başlamalıyım;

var globalObject = JavaScriptValue.GlobalObject;

   Sıra fonksiyon adını tanımlamakta;

var propertyId = JavaScriptPropertyId.FromString("alert");

   Ardından da fonksiyonumu tanımlamalıyız;

var function = JavaScriptValue.CreateFunction(Alert);

   Son olarak bunların hepsini bir araya toplamalıyız. Global nesnemize alert adının az önce tanımladığımız fonksiyon için kullanılacağını belirtmeliyiz;

globalObject.SetProperty(propertyId, function, true);

  Dediğim gibi, bu işin kolay kısmı. Yukarıdaki koda dikkat edecek olursanız CreateFunction çağrısına parametre olarak Alert geçmekteyiz. CreateFunction fonksiyonu JavaScriptNativeFunction adındaki bir delegate’i parametre olarak kabul etmekte. Bu delegate’in tanımı bize Alert fonksiyonunun nasıl bir imzaya sahip olması gerektiğini de göstermekte;

public delegate JavaScriptValue JavaScriptNativeFunction(
    JavaScriptValue callee, 
    bool isConstructCall, 
    
    [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] 
    JavaScriptValue[] arguments, 
    
    ushort argumentCount);

   alert fonksiyonumuzun tek yapması gereken gelen parametrelerin her birini dolaşarak string olarak alıp konsola yazdırmak. Dolayısıyla da kodu aşağıdaki gibi olacaktır;

private static JavaScriptValue Alert(JavaScriptValue callee, bool isConstructCall, JavaScriptValue[] arguments, ushort argumentCount) {
    for (uint index = 1; index < argumentCount; index++) {
        if (index > 1) {
            Console.Write(" ");
        }

        Console.Write(arguments[index].ConvertToString().ToString());
    }

    Console.WriteLine();

    return JavaScriptValue.Invalid;
}

   Hepsini toparladığımızda Program.cs dosyamızın içi aşağıdaki gibi olacaktır;

using Enterprisecoding.JSHosting.Hosting;
using System;
using System.IO;

namespace Enterprisecoding.JSHosting {
    class Program {
        private static JavaScriptSourceContext currentSourceContext = JavaScriptSourceContext.FromIntPtr(IntPtr.Zero);

        static void Main(string[] args) {
            if (args == null || args.Length == 0) {
                Console.Error.WriteLine("Kullanım :  JSHosting <betik adı> <parametreler>");

                return;
            }

            using (var calismaZamani = JavaScriptRuntime.Create()) {
                var context = calismaZamani.CreateContext();

                using (new JavaScriptContext.Scope(context)) {
                    var globalObject = JavaScriptValue.GlobalObject;

                    var propertyId = JavaScriptPropertyId.FromString("alert");
                    var function = JavaScriptValue.CreateFunction(Alert);
                    globalObject.SetProperty(propertyId, function, true);

                    var betikAdi = args[0];
                    var betik = File.ReadAllText(betikAdi);

                    JavaScriptValue sonuc;
                    try {
                        sonuc = JavaScriptContext.RunScript(betik, currentSourceContext++, betikAdi);
                    }
                    catch (JavaScriptScriptException e) {
                        var messageName = JavaScriptPropertyId.FromString("message");
                        var messageValue = e.Error.GetProperty(messageName);
                        var message = messageValue.ToString();

                        Console.Error.WriteLine("JSHosting: Hata: {0}", message);
                    }
                    catch (Exception e) {
                        Console.Error.WriteLine("JSHosting: Betik çalıştırılırken hata oluştu: {0}", e.Message);
                    }
                }
            }

            Console.ReadKey();
        }

        private static JavaScriptValue Alert(JavaScriptValue callee, bool isConstructCall, JavaScriptValue[] arguments, ushort argumentCount) {
            for (uint index = 1; index < argumentCount; index++) {
                if (index > 1) {
                    Console.Write(" ");
                }

                Console.Write(arguments[index].ConvertToString().ToString());
            }

            Console.WriteLine();

            return JavaScriptValue.Invalid;
        }
    }
}

  Kodumuzu derleyip çalıştırdığımızda artık konsolda beklediğimiz gibi “Merhaba Dünya” yazımızı görebiliriz;

Tüm adımlar sonrasında yazdığımız javascript kodunun çalıştığını görebiliriz

   Son olarak bir ipucu; bilgisayarınıza TypeScript kurarak doğrudan JavaScript üzerinde çalışmak yerine editör destekli ve nesnel olarak JavaScript kodlarınızı geliştirebilirsiniz. Örneğin; projeye app.d.ts adıyla bir TypeScript dosyası ekleyerek tüm fonksiyon tanımlamalarınızı bu dosya içerisinde yapabilirsiniz. Ardından yukarıdaki örneğimiz için test.ts adıyla bir dosya oluşturup app.d.ts dosyasına aşağıdaki gibi referans verebilirsiniz;

/// <reference path="app.d.ts"/>
alert("Merhaba Dünya");

Tanım dosyasına referans verilmesi sonrasında TypeScript gerekli IDE desteğini sunacaktır

Fatih Boy

Ankara'da yaşayan Fatih, bir kamu kurumunda danışman olarak çalışmaktadır. ALM süreçleri, kurumsal veri yolu sistemleri, kurumsal altyapı ve yazılım geliştirme konularında destek vermektedir. Boş zamanlarında açık kaynak kodlu projeler geliştirmeyi ve bilgisini yazdığı makalelerle paylaşmayı seven Fatih, aynı zamanda Visual C# ve Visual Studio teknolojileri konusundan Microsoft tarafından altı yıl üst üste MVP (En Değerli Profesyonel) ödülüne layık görülmüştür. İş hayatı boyunca masaüstü uygulamaları, web teknolojileri, akıllı istemciler gibi konularda Asp.Net, Php, C#, Java programlama dilleri ve MySql, MsSql ve Oracle gibi veritabanı yönetim yazılımları ile çalışmıştır. İngilizce ve Türkçe olarak yayınlanan makalelerini gerek İngilizce bloğunda, gerekse de Türkçe bloğunda bulabileceğiniz gibi web sitesinden de açık kaynak kodlu geliştirdiği yazılımlarına ulaşabilirsiniz. vCard - Twitter - Facebook - Google+

Bir Cevap Yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir