Embedding JavaScript into C# with Rhino and IKVM

Describes a technique to call JavaScript from C#, and then to allow the JavaScript to call back into C#.

Introduction

The web is full of various discussions on how to embed C# into JavaScript. Most of these approaches are flawed because they rely on the deprecated Microsoft.JScript APIs. Other approaches, like hosting JavaScript in a WebBrowser control, aren't portable. In my particular situation, I need an embedded JavaScript engine that will work on Windows, Mac, and Linux. It has to work equally well in the .NET and Mono runtimes, and ideally, shouldn't require recompiling for each Operating System. I ended up using the IKVM tool to convert Rhino, a JavaScript interpreter written in Java, into a CLR DLL.

Background

Various searches for C# and JavaScript often come up with examples that use the Microsoft.JScript namespace and the VSA API. After experimenting with these on Mono on Mac, I decided to move on. These APIs were deprecated a long time ago, and support seemed somewhat flakey. There are also some rumors of JScript support re-emerging with some fancy new scripting framework related to Silverlight; but it seems that the project is canned. I concluded that using the JScript APIs built into .NET and Mono would not meet my needs.

There are a few well-established JavaScript libraries written in C and C++. These are mature, but using them in C# would present a few problems. The primary problem is that I would have to include a version of the library for each Operating System that I wish to target, which would make my deployment scenario too complicated. In addition, PInvoke introduces its own set of complexities that can become a time sink. It is possible to compile C and C++ into a .NET DLL; however, in order to make the DLL work on Mac and Linux, they would need to be adapted to compile with the /clr:pure option. While this would yield the best performance, it's just too time consuming for now. (Anyone wants to figure out how to get V8 to compile with /clr:pure?)

I got my final clue from Joshua Tauberer's blog: Embedding a JavaScript interpreter with Mono. He describes the process of introducing Rhino into a C application by using embedded Mono and the ikvmc Java bytecode to CLR converter. According to him, it is a quick and painless process!

Why Not Use Some Other .NET Scripting Language?

In my search for a reliable approach to call JavaScript from C#, I came across many plugs for various .NET scripting technologies, like Lua, Boo, IronPython, and IronRuby. In my situation, JavaScript is preferable for the following reasons:

I don't want my users to have to learn a new language. JavaScript is well-known.

Most of the time that I'm calling into JavaScript, it will contain data originating from a browser's AJAX call. The results of my JavaScript will return to the AJAX callback. Keeping with JavaScript simplifies JSON serialization and type mapping issues.

Preparing and Referencing Rhino

Converting from Java Bytecode to CLR

A version of IKVM is included with Mono, but I had to download IKVM in order to get it to work correctly in .NET / Visual Studio. This is because DLLs generated by IKVM depend on additional DLLs to provide Java classes. I used version 0.40.0.1.

Converting Rhino to work under .NET is very painless. I opened a shell into a folder with the ikvmc.exe executable. (It's part of the PATH on Mono on Mac.) Next, I put js.jar, from Rhino, into the same directory as ikvmc.exe. The command to convert is:

ikvmc.exe -target:library js.jar

I got a bunch of warnings related to XML, but I ignored them because I don't anticipate using XML functionality in JavaScript.

Referencing js.dll

In Mono, if I used the ikvmc.exe that was part of the PATH, I could import js.dll directly without problems. In .NET, I had to also include IKVM.OpenJDK.Core.dll. This is because Mono includes IKVM's DLLs in the GAC and .NET doesn't.

Using the Code

For my test, I decided to call a function declared in JavaScript from C#, call a static method declared in C# from JavaScript, and then call a non-static method declared in C# from JavaScript. In order to satisfy my needs, I had to successfully pass primitive values between C# and JavaScript. I'm not concerned with more complicated types because my application will pass structured data as JSON into and out of JavaScript.

Notice that FromCSharp uses a java.lang.Double. This is because Rhino still works with Java types, and is incapable of converting JavaScript values into the equivalent C# values. IKVM handles some translation, but it doesn't yet allow casting between double? and java.lang.Double.

There are both static and instance methods. Rhino has functionality that can automatically expose a Java object's methods to JavaScript. I couldn't get this to work under IKVM. My technique for exposing C# methods and objects to JavaScript is discussed in a few paragraphs.

The Context must always be exited. The most reliable way to do this is to wrap all use of the Context in a try block, and close it in a finally clause. The Context is only intended to be used on a single thread. See Rhino's documentation for more information.

Non-static methods, on the other hand, require a bit of work. As I stated earlier, I could not get Rhino's ability to expose a Java object to JavaScript to work under IKVM. Fortunately, IKVM and Rhino allow C# objects to be opaquely passed into JavaScript and then back to a static C# method. In this case, I want JavaScript to be able to call the non-static method CalledWithState(). To do so, I created a static method in the same class that takes an object as its argument. The static method casts the object to the desired type, and then calls the non-static CalledWithState(). I also had to create a wrapped function in JavaScript that passes the opaque object "me" to the static CalledWithState().

The JavaScript is shown above, and the code to add the opaque object and the static wrapper method is shown below:

The final part is a simple security test. Rhino, by default, allows unrestricted access to all Java APIs from within JavaScript. It appears that their intention is that Java's security APIs should be used to enforce security constraints. A different approach is to explicitly lock down JavaScript to only use functions and objects directly passed into it.

This block of code attempts to prove that a Java API cannot be used. Something that I do not understand is that evaluateString has some kind of user-unhandled exception. While the program does not crash, Visual Studio mysteriously breaks into the debugger even though there is a catch-all clause:

This block of code is the class filter assigned when entering the context. It explicitly denies use of any Java class from JavaScript. Note that interfaces imported from IKVM don't follow the .NET "I" convention:

Summary and Conclusion

Using Rhino and IKVM allows calling JavaScript from C#. The performance is okay. The process of importing a Java library into C# is mostly painless, except for some issues with exposing C# objects to JavaScript. Using Rhino and IKVM allows a C# program to work on Windows, Linux, and Mac without needing to compile a library for each platform, thus resulting in the simplest deployment scenario possible. Finally, it is my opinion that using IKVM to call a Java library is a lot easier than using PInvoke to call into a C library.

I spent a lot of time working with Jint today. It's clearly a good effort, and I'm very enthusiastic that it will evolve to be a powerful library. At this time it doesn't meet my needs, however.

The json2 reference parser causes a null reference exception when loaded into Jint.

Jint doesn't provided needed functionality that Rhino has, including the "SetClassShutter" functionality; and the ability to programmatically reflect on the Javascript environment from C# without executing Javascript.

I am, however, very enthusiastic that a future version of Jint will solve a lot of these issues and eliminate the need for IKVM and Rhino.

I feel like I just watched a high-tech genetic experiment where sheep genes have been put into cats

What I can't imagine is what kind of real-world application you would want to write with this. Are windows and controls (the UI) being created in this scenario by ... what ? ... MS C#/.NET, Java, JavaScript, Mono ?

Also, I would be "nervous" about the complexity of the different engines you have "wired" together : what if there are major changes in Rhino ? And the "test matrix" resulting from this complex mutant assemblage ... of near infinite possibilities ?

If there is a type of real-world application for which this combination offers maximum cost/benefits, I'd enjoy hearing you talk about that (aside from the idea of "write once, run anywhere").

These comments are really not "criticism," and I hope you don't take them as being so.

best, Bill

"Many : not conversant with mathematical studies, imagine that because it [the Analytical Engine] is to give results in numerical notation, its processes must consequently be arithmetical, numerical, rather than algebraical and analytical. This is an error. The engine can arrange and combine numerical quantities as if they were letters or any other general symbols; and it fact it might bring out its results in algebraical notation, were provisions made accordingly." Ada, Countess Lovelace, 1844

Well, the abstract answer is that a lot of programs like to give simple scripting abilities to their users for further customization.

I'm my case, I'm building some web server technology for rapidly developing web applications. It has to run on Linux to keep costs low, however, I would like XCopy installation to any computer with .Net or Mono. This means that I can not rely on WindowsScriptHost or other scripting libraries that might be installed on the target computer.

The custom scripting feature allows for strict input validation when data comes from the browser. The goal is to provide as much data plumbing and automatic serialization as possible from the browser to the database, but still allow some scripting to validate data coming in from the public internet.

Keeping with Javascript, as opposed to the various other .Net scripting technologies, keeps my learning curve as gentle as possible.