Dan Newcome, blog

I'm bringing cyber back

Embedding IronJS – Part II

with 16 comments

Over the past year or so I have been working on-and-off on a project called Node.NET in which I have experimented with embedding several different Javascript implementations within the .NET framework. Many different implementations exist including one integrated CLR implementation from Microsoft called JScript.NET. Although JScript is well integrated and allows us to write everything in Javascript, it isn’t well supported and I’m always wondering if it will be included at all in each new release of the .NET framework.

I have successfully used unmanaged Javascript implementations such as Google’s V8 by compiling mixed-mode binaries using the Microsoft C++ compiler, which supports mixing native and managed code. However, this is much more complicated than writing pure managed code since in addition to marshaling between JS contexts and the CLR we have the added complexity of marshaling data between the CLR and native C++ code. To see what is involved the code from this early effort is still in the Node.NET project on github and there is now another project from Noesis dedicated to embedding V8 called Javascript.NET.

Since searching for a fully-managed JS implementation, I have found that IronJS is the best solution so far, leveraging the Dynamic Language Runtime from Microsoft to handle many of the dirty tasks of garbage collection and code generation from the AST. This means that IronJS will benefit, along with IronPython, IronRuby and others, from all of the research that MS has done with dynamic languages on the .NET runtime.

In a past article I outlined my experiences with embedding the first IronJS, which was written in C#. In this installment I will be using the newer F# IronJS implementation. This version of IronJS is a full rewrite and there have been lots of API changes and improvements. This article has been a long time coming since I have not had the time to dive into the new codebase until this past week. I’m not an expert at F# so I had to learn a bit of the language to figure things out since what I’m going to show here is not documented by the author. Since everything here was learned by reading the source and by reverse-engineering, there may be better ways to accomplish things and there may be some errors, so I apologize for this in advance.

Now, on to some code examples.

The very simplest thing we can do is create the Javascript context and execute a string of source code. Here we create the context and calculate 2+2 and write the result to the console:

   IronJS.Hosting.Context ctx = IronJS.Hosting.Context.Create();
   Console.WriteLine( ctx.Execute("2+2;") );

Beyond simply executing a string of code, the first thing we’d like to do when embedding a language is to make some code from the host environment available to the embedded language environment. In our case the host environment is the .NET CLR and the embedded environment is IronJS.

The most basic example is making a simple ‘print’ statement available so that we can output debug statements that print to the console from Javascript. Fortunately there is some code that does this right in the IronJS sources under the DevUI project. Here is my version of the Print host function:

static void Print(IronJS.Box box) {
    Console.WriteLine(IronJS.Api.TypeConverter.toString(box));
}

Notice that the argument type is IronJS. Box. The Box type is a concept introduced in IronJS which represents a value that is not known at compile time. Box may be required for certain argument types, but I have been able to get away with using System.String directly in some cases.

IronJS.Box is a struct laid out to mimic a C-style union like this:

type [<NoComparison>] [<StructLayout(LayoutKind.Explicit)>] Box =
  struct 
    //Reference Types
    [<FieldOffset(0)>] val mutable Clr : ClrObject 
    [<FieldOffset(0)>] val mutable Object : IjsObj
    [<FieldOffset(0)>] val mutable Array : IjsArray
    [<FieldOffset(0)>] val mutable Func : IjsFunc
    [<FieldOffset(0)>] val mutable String : IjsStr
    [<FieldOffset(0)>] val mutable Scope : Scope

    //Value Types
    [<FieldOffset(8)>] val mutable Bool : IjsBool
    [<FieldOffset(8)>] val mutable Number : IjsNum

    //Type & Tag
    [<FieldOffset(12)>] val mutable Tag : TypeTag
    [<FieldOffset(14)>] val mutable Marker : uint16
  end

IronJS provides a TypeConverter for dealing with Box values, so instead of trying to extract the data from a Box ourselves, it is safer to use the TypeConverter as shown in the line:

Console.WriteLine(IronJS.Api.TypeConverter.toString(box));

Now that we have a .NET function let’s put it into the global JS environment:

IronJS.Hosting.Context ctx = IronJS.Hosting.Context.Create();
 var print =
           IronJS.Api.HostFunction.create<Action<IronJS.Box>>(
       ctx.Environment, Print);
ctx.PutGlobal("print", print);

Now we can call the global ‘print’ function from Javascript:

 ctx.Execute("print( 'hello from javascript' )");

The next thing we want to do is to call a function that is defined in Javascript from the host environment. In IronJS, we do this by compiling the function to a .NET delegate and then invoking the delegate. Once the initial Javascript code has been executed, we get a reference to the global function that is created using GetGlobal.

  IronJS.Hosting.Context ctx = IronJS.Hosting.Context.Create();
  ctx.Execute("hello = function() { return 'hello from IronJS' }");
  IronJS.Box obj = ctx.GetGlobal("hello");
  Func<IronJS.Function,IronJS.Object,IronJS.Box> fun = 
       obj.Func.Compiler.compileAs<Func<IronJS.Function,IronJS.Object,IronJS.Box>>(obj.Func);
            
   IronJS.Box res = fun.Invoke(obj.Func, obj.Func.Env.Globals);
   Console.WriteLine( res.String );

One thing to notice is that the delegate signature has two extra arguments — IronJS.Function and IronJS.Object, which will take the function itself and the target object on which to call the function. This was mostly reverse engineered from the implementation of Array.Sort() which can take a sort function as an argument.

Beyond calling global methods we’ll want to control the creation of Javascript objects and call methods in different execution scopes, but I’m going to have to cover that in a future article. Node.NET requires a few more advanced embedding techniques but for now this will get you started embedding newest version of IronJS.

About these ads

Written by newcome

March 13, 2011 at 11:53 am

Posted in Uncategorized

16 Responses

Subscribe to comments with RSS.

  1. Hey!

    Author of IronJS here, just wanted to point out the IronJS.Box struct is not introduced by the DLR but is something I’ve chosen to do for IronJS specifically, normally in the DLR everything is boxed as a System.Object.

    Regards,
    Fredrik

    Fredrik Holmström

    March 18, 2011 at 2:58 am

  2. @Fredrik – I thought I saw a Box type somewhere in the DLR so I must have incorrectly assumed that the concepts were related. I’ll correct the text of the article – thanks for pointing it out.

    newcome

    March 18, 2011 at 11:34 am

  3. FYI I spotted another native JS for .NET implementation the other day at http://jurassic.codeplex.com/ which claims very good ECMAScript functionality.

    Harry M

    March 23, 2011 at 5:41 am

  4. Jurassic works well, though I think IronJS is a little faster from what I’ve read of Frederik’s benchmarking.

    Nathan Ridley

    May 25, 2011 at 12:19 pm

  5. IronJS about 2.5-3x faster then Jurassic, source: http://ironjs.files.wordpress.com/2011/04/sunspider-total.png

    Fredrik Holmström

    May 25, 2011 at 12:39 pm

  6. Both IronJS and Node.NET are extremely interesting projects I wish all the best. I’ve got interest in both because I’d like to get UglifyJS to execute within .NET.

    I’ve asked you on Twitter if you have any ideas on how to approach this, because at the moment I can’t even get Node.NET to build. You don’t seem to be using Visual Studio, at least there’s no .sln file there, plus there are external references not present (or so it seems). I would love if you could help me out a bit! :-)

    asbjornu

    June 25, 2011 at 5:20 am

  7. @asbjournu – I’m not using Visual Studio for the moment. That may change. Also you’ll need the IronJS binaries to build as well as the http parser project, which is hosted in a separate github repository. At this point the project is very experimental, and not a lot of the node API is supported yet. However, most JS functionality should work simply because IronJS supports it now. If Uglify doesn’t use node-specific features it may work.

    newcome

    June 25, 2011 at 8:47 am

  8. @newcome According to the UglifyJS developer Marijn Haverbeke, the only parts needed are the CommonJS modules and UglifyJS will execute fine. Are they implemented in Node.NET as of now? If not, how can I contribute to Node.NET so they are? I’d like to use Visual Studio (if possible), but don’t quite know how to do that in the state Node.NET is at the moment.

    I can of course fork and add a Visual Studio solution myself, but since this is your project, I’d like your input on this before I dive in. Thanks!

    asbjornu

    June 26, 2011 at 11:18 am

  9. @newcome After compiling the HTTP Parser and IronJS separately and adding the binaries to Node.NET’s build path, it seems I have compiled the wrong version of IronJS since I get a lot of compiler errors. Are there licensing issues that makes adding IronJS’ and HTTP Parser’s binaries as references to Node.NET?

    Also, why have you chosen to write the HTTP Parser in C? Wouldn’t you say having the whole code base managed is a virtue in and of itself (even though C might be faster [I think that's negligible in something as simple and once-per-request as this, though])?

    Feel free to reply to me on Twitter! :)

    asbjornu

    June 27, 2011 at 6:46 am

  10. I am not able to get “CompileAs” delegate in the dll.

    Func fun =
    obj.Func.Compiler.compileAs<Func>(obj.Func);

    Appreciate if you can provide the dlls to use to achieve above functionality.

    Bhushan

    September 5, 2012 at 3:06 am

  11. How to parse JSON(send as string parameter) to a method in JavaScript function

    Hi,

    I am new to IronJS and facing difficulty parsing JSON in JavaScript method.

    My C# Code

    string jsonString = “{\”Name\”: \”Ankur\”, \”Sex\”: \”Male\”}”;
    var o = new IronJS.Hosting.CSharp.Context();
    o.ExecuteFile(@”C:\CustomScript.js”);
    var handleJson = o.Globals.GetT(“HandleJson”);
    var result = handleJson.Call(o.Globals, jsonString).Unbox();
    Console.WriteLine(result);

    JavaScript method in CustomScript.js

    function HandleJson(jsonStr) {
    obj = JSON.parse(jsonStr);
    return obj.Name;
    }

    Everytime I do this, I get error message saying “ReferenceError: JSON is not defined”

    Guess, “JSON.parse” method is native to browser and isn’t available server side.
    I can use jQuery method obj = $.parseJSON(jsonStr); as well but don’t know how to load jQuery file at server.

    Any thoughts on what I am doing wrong or how to fix it?

    Thanks.

    Ankur Nigam

    November 1, 2012 at 5:17 am

  12. I have found the fix to it.

    JSON.parse is an unknown JS method at Server(which is why we were getting the error)… So, we need to add/load “json2.js” before CustomScript.js file and then we will be good.

    json2.js can be downloaded from following location: https://github.com/douglascrockford/JSON-js

    Following is my updated code.

    Updated C# Code

    string jsonString = “{\”Name\”: \”Ankur\”, \”Sex\”: \”Male\”}”;
    var o = new IronJS.Hosting.CSharp.Context();
    o.ExecuteFile(@”C:\json2.js”);
    o.ExecuteFile(@”C:\CustomScript.js”);
    var handleJson = o.Globals.GetT(“HandleJson”);
    var result = handleJson.Call(o.Globals, jsonString).Unbox();
    Console.WriteLine(result);

    No changes are required in CustomScript.js

    Cheers!

    Ankur Nigam

    November 1, 2012 at 8:25 am


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: