Friday, February 08, 2008

having fun with js-ctypes

So, after humming and hawing until I was fresh out of hum and down to my last batch of haw, I figured the time was right to try and get js-ctypes working to start contributing more to Mozilla. I have a trunk Firefox debug build I update once a month or so and figured since I popped my build-your-own cherry about a year ago, with PyXPCOM enabled no less, I should be able to get js-ctypes going without too much crying.

Verdict: well that was easy. Check out the code from the svn repo into mozilla/extensions/, add it to extensions in .mozconfig like so:

ac_add_options --enable-extensions=default,spatialnavigation,python,js-ctypes
and Bob's your proverbial uncle. It built cleanly on my working Fx trunk build, and it was available to extensions and whatnot.

Now, I use Linux (openSUSE 10.2 currently) and Mark Finkle wrote on his blog when announcing js-ctypes, "compiles on Linux, but I haven’t got around to testing it." It turns out it built cleanly, but it didn't work. Some quick testing using a hastily thrown together extension showed that nsINativeTypes::Open() failed — it didn't load shared libraries (libc.so.6 in this case). After hitting mxr it turned out a PRLibSpec struct is initialized differently depending on the platform, and the code only covered the WIN32 case. That's bug #416119. The fix was easy enough, an #ifdef and a couple lines of code, but I'm still not sure the string voodoostuff is correct (I think the PromiseFlatCString call is redundant as NS_Convert... returns an nsCAutoString which is "flat" already).

That took care of the first problem and js-ctypes now loaded shared libraries in Linux. Score one for the newb. The next step was to try and call some function. I chose puts(), which is a part of libc and simply outputs a string of characters to stdout. Easy enough, right? Well, no. Since it uses a char* buffer for the string, I chose nsINativeTypes.STRING when declaring the function like so*:
const nsINativeTypes = Ci.nsINativeTypes;
var library = Cc["@developer.mozilla.org/js-ctypes;1"]
.createInstance(Ci.nsINativeTypes);
library.open("libc.so.6");
var puts = library.declare("puts", /* function name */
nsINativeTypes.INT32, /* return type */
nsINativeTypes.SYSV, /* call ABI */
nsINativeTypes.STRING /* argument type */
);
var ret = puts("Hello World from js-ctypes!");
Upon trying that code I was greeted with a segmentation fault. It turned out there was no implementation for the native type STRING in nsNativeMethod.cpp. Since there's no default catch-undefined-and-return-error in the switch blocks in nsNativeMethod::Execute() and declare() doesn't care what type you specify (in reality any uint16 will work), when the function was called the call to ffi_prep_cif() in nsNativeMethod::Execute() had incomplete information about the parameters and exploded. Adding support for STRING is bug #416229. Thankfully, I could reuse the logic for WSTRING but use char* instead of PRUnichar* and fix the problem without any trouble.

The customary money shot:This was a fun little exercise in building/testing/hacking on a Mozilla project and has left me wanting to do more. Including adding support for struct types and writing some unit tests. My thinking as far as testing goes is to write a shared object/library with a set of known functions that exercise all the native types covered by js-ctypes, integrating that into the build and then adding tests to the unit test framework. I have little or no idea how to do most of those things, but figuring it out will be fun.

[*] Do note that the call ABI has to be specified. That bit me for about 30 minutes before I poked through the code thoroughly and realized the example code in Mark Finkle's original post didn't reflect that.

No comments: