Embedding a JavaScript interpreter with Mono

With the semester coming to a close and free time becoming scarce, new forms of pseudo-work procrastination were bound to pop up — the kind of procrastination where you do something constructive, just not what you’re supposed to be doing. Getting to the point, this semester I’ve been using a program called Praat, which is a tool phoneticians use to analyze sound files. Praat is good for its ability to run macro-like scripts that can automate tasks, but the programming language used in Praat was custom-made for Praat and defies normal conventions in so many ways it’s fairly frustrating to use if you’re already familiar with programming.

So I decided I would try to replace Praat’s scripting language with JavaScript, a language most programmers are either familiar with already or can pick up quickly because of its similarity to all C-like languages. A difficult task? It turns out this was one of the most rapid programming experiences I’ve ever had. I was able to do it in about three hours and less than 200 lines of code, thanks to all of the work that has gone into four open-source projects, Rhino, GNU Classpath, IKVM, and Mono.

Praat is written in C, and so the first requirement was that I find a scripting language runtime that I can embed in C. I didn’t know of any, but the one runtime that I knew was easily embeddable was Rhino, a JavaScript runtime written in Java, created by the Mozilla team. It’s possible to link native programs to Java programs, but as far as I know, it’s not particularly pretty. What is pretty is embedding the Mono runtime in native applications, so I went that route.

Step 1 was converting Rhino to .NET using IKVM, the Java bytecode-to .NET CIL converter. (Brief aside: One of my first C# projects was attempting to do that, before IKVM existed. I didn’t get very far and definitely couldn’t have made anything as comprehensive as Jeroen has done with IKVM.) IKVM relies on GNU Classpath, so hats off to them too. The conversion goes like this:

ikvmc js.jar

The program creates ikvm.dll, the equivalent .NET assembly. Easy enough.

The next step was to create a simple wrapper around the Rhino interpreter. I just adapted a sample from the Rhino docs. I also added a way for the script to call back into Praat’s C routines, using special DllImport’s to call exported functions in the main executable (rather than an external library):

[DllImport ("__Internal", EntryPoint="mono_embed_echo")]
public static extern void Echo (string message);

Okay, now how to get this integrated with Praat? In the end, the Mono runtime will get embedded in the Praat executable. The simple way to do that is to follow Mono’s embedding guidelines. The slight drawback with that method for my purposes is that the resulting program will have an external dependency on the .NET assemblies I need to run the scripts (my wrapper, Rhino as js.dll, and supporting IKVM assemblies). Normally I wouldn’t mind, but Praat compiles to a single standalone file, and to keep deployment just as simple I wanted to keep that.

Mono has a tool called mkbundle that, wonderfully enough, can take a bunch of assemblies and the runtime itself and bundle them into a single native library. mkbundle was envisioned to create a single executable file out of a Mono application to make deployment easy. That’s just what I needed, except I didn’t want mkbundle to generate a native program, but a native library that I could embed in Praat. The output of mkbundle was easy to alter to get that, but I’ve already patched mkbundle in Mono’s SVN repository to make it do this on its own. So this goes like this:

mkbundle -c --nomain -z -o mono_embed_host.c -oo mono_embed_libs.o \
PraatMonoEmbed.dll js.dll IKVM.GNU.Classpath.dll IKVM.Runtime.dll

This creates two files, mono_embed_hosts.c and mono_embed_libs.o. The .o file is a native file that has the four assembly files packaged within it. The assemblies aren’t compiled to native code; they’re just stored as data within the library. It could also have the Mono runtime itself statically linked in, so that the final product will have no external dependencies at all, although I haven’t tried it yet so right now the final product will need Mono to be installed. The .c file is a wrapper to set up the embedded Mono runtime to use the bundled assemblies. We’ll link these files with Praat.

We’re almost there. Next, we need a C routine to pass a script to the C# wrapper around Mono. This routine will initialize the Mono runtime and call a C# method passing the script as an argument. The Mono embedding guide explains how to do that. It basically goes like this:

void run_script(char *code) {
mono_mkbundle_init();
domain = mono_jit_init ("main-domain");
assembly = mono_domain_assembly_open (domain, "PraatMonoEmbed.dll");
driver_desc = mono_method_desc_new ("PraatMonoEmbed:RunScript(string)", 0);
driver = mono_method_desc_search_in_image (driver_desc, mono_assembly_get_image(assembly));
void* params[] = { mono_string_new(domain, code) };
MonoObject* ret = mono_runtime_invoke(driver, NULL, params, NULL);
}

This file is compiled with:

gcc -c mono_embed.c -o mono_embed.o `pkg-config --cflags mono`

Lastly, Praat’s build script gets modified to link in all of the above files and link to the Mono embedding library (libmono). And that’s pretty much it. Praat now has a JavaScript interpreter built-in.

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