Sunday, July 24, 2005

Turns out, the best place to add my "dump" function was to the global object. Here's how I did it.

First, I created a C function named Dump, which is essentially a copy of the function of the same name found in js/src/xpconnect/shell/xpcshell.cpp. The function prototype for Dump() is:


JS_STATIC_DLL_CALLBACK(JSBool)
Dump(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)


This is pretty much boilerplate for any functions that are invoked from JS to an object created by you in C or C++. The implementation of this function is straightforward; take the character string passed and display it to stderr.

To tell the JS engine about this function, we need to fill out a simple array:


static JSFunctionSpec glob_functions[] = {
{"dump", Dump, 1},
{0}
};


JSFunctionSpec is simply a mapping from the name of a function as it is called from JavaScript (here, "dump") to the name of the function as it is implemented in C or C++ (in this case, "Dump"). The final field of the JSFunctionSpec struct is the number of arguments passed, which in this case is 1.

Notice we don't have to specify the type of the argument. JavaScript implements 3 fundamental types - boolean, numbers (with no distinction between integers and floats), and strings. However, in JavaScript, you cannot declare the type of a variable. Instead, JavaScript is able (like any other compiler) to determine the type of a variable based on lexical properties alone, e.g.,


var myVar;

myVar = 3; // var is an integer
mVar = "3"; // var is now a string


The JS engine allows embedding C/C++ applications to convert JS variables to whatever type is necessary via a set of conversion functions. In the C/C++ implementation of Dump(), I call one of these conversion functions to retrive a string that can be displayed to stderr:


String *str;

str = JS_ValueToString(cx, argv[0]);


Then, I extract from the JSString object a C-style string, which I make a copy of and sent to fputs() for display:


char *bytes = JS_GetStringBytes(str);
bytes = strdup(bytes);

fputs(bytes, stderr);
free(bytes);


Instead, had I wanted the argument to dump() to be evaluated as a signed 32-bit int, I would have called:


extern JS_PUBLIC_API(JSBool)
JS_ValueToECMAInt32(JSContext *cx, jsval v, int32 *ip);

So, no matter what type of variable the caller passes to dump() in JavaScript:


dump(3);
dump("3");


I can retrieve a character string and display it properly. Here is the complete source for Dump() in C:


JS_STATIC_DLL_CALLBACK(JSBool)
Dump(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
JSString *str;
if (!argc)
return JS_TRUE;

str = JS_ValueToString(cx, argv[0]);
if (!str)
return JS_FALSE;

char *bytes = JS_GetStringBytes(str);
bytes = strdup(bytes);

fputs(bytes, stderr);
free(bytes);
return JS_TRUE;
}


Now that I have defined the function in C/C++, all that remains is to call the JavaScript engine and tell it about the functions I have defined, associating them with the global object. This is done by calling JS_DefineFunctions():


JS_DefineFunctions(cx, m_glob, glob_functions);


That's all there is to it.