JSDB     Home    License    Download    Tutorial    Reference    Projects    Feedback    History

Embedding SpiderMonkey (Mozilla's JavaScript Interpreter) with C++ on Windows

Common mistakes

JS_AddRoot takes a pointer to a pointer, as in JS_AddRoot(cx,&obj)

Embedding JSDB

It's not hard to embed JSDB in your application. First, get the JSDB source code, and add all the source files to your project.

Here's a minimal JSDB program:
struct JSDBEnvironment;
#include "jsdb.h"

int main(int argc, char **argv)
{
 CoInitialize(0);
 TBLEnv TableCache;
 { /* cleanup in the right order */
   JSDBEnvironment JSDB(&TableCache);
   JSDB.Startup(512*1024, 8192, 0);
   JSDB.in = new FileStream("stdin",FileStream::OMText);
   JSDB.out = new FileStream("stdout",FileStream::OMText);

   JSDB.ExecScript("print('Hello, World!')",0,0);

   delete JSDB.in;
   delete JSDB.out;
 }
 CoFreeUnusedLibraries();
 CoUninitialize();
 return 0;
}

Embedding SpiderMonkey

If you want to build your own JS environment, you'll want to imitate parts of JSDB.

Getting started

  1. Get the SpiderMonkey source code from www.mozilla.org/js.
  2. It works with most C compilers.
  3. Make sure to use 4-byte alignment. and define XP_PC;WIN32;EXPORT_JS_API.
  4. If you're using CodeWarrior or Borland C++, also define _declspec=__declspec.
  5. Compile all the source files to a library, JS.LIB. Some people have tried compiling to DLLs, but mostly, they've regretted it.

Writing a shell

SpiderMonkey's basic shell program is jssh.c. JSDB goes beyond that with jsdb.cpp. I'm assuming that you have a basic set of classes to handle file I/O, and you probably want to embed some other classes that you've designed.

First,  you'll want to define a global object that all of your native methods will have access to:

struct JSDBEnvironment
 {
   Stream* out;
   Stream* in;
   void* System;
   JSRuntime *rt;
   JSContext *cx;
   JSObject *global;
   //exported classes
   JSObject *Table, *Stream, *Record, *Form,
            *Mail, *Server, *Image,
            *ODBC, *Archive, *ActiveX;
   TBLEnv* TableEnv;
   bool reportWarnings;
   bool errorOnFailure;
   bool restart;
   bool shouldStop;
   bool (*checkInterrupt)(JSDBEnvironment* Env);
   int SafeMode;
   int Magic; //uninitialized
   bool AllowExec;

   SYSTEMTIME startTime;

   JSDBEnvironment(TBLEnv* table)
    {
     TableEnv = table;
     out = in = NULL;
     System = NULL;
     reportWarnings = errorOnFailure = false;
     shouldStop = false;
     AllowExec = false;
     restart = false;
     rt = NULL;
     cx = NULL;
     global = NULL;
     Table = Stream = Record = Form = Mail = Server = NULL ;
     Archive = ActiveX = Image = ODBC = NULL;
     SafeMode = 0;
     checkInterrupt = NULL;
    }
 };



Provide stdin/stdout for the script

Opaque pointer for the callback function


The JS global object

Object prototypes for embedded classes
These allow us to do run-time type
identification in native methods.
Access the ODBC environment (optional)
Flags for native methods.
Exit if constructor fails?
Use a progress dialog to break out
of a long loop.

Running untrusted code? Use SafeMode to
block access to the file system.
Allow ShellExec() in this program?

For profiling


Next, we want to deal with the classes you embed. How do you want cleanup to work? What if a C++ object owns another object, and both are exposed to the JS engine? You don't want them to be deleted in the wrong order. So you need a smart pointer system.

class JSPointerBase
{public:
 virtual void Close() = 0;
 TList<JSPointerBase> Children; /// does not autodelete
 JSPointerBase* Parent;
 void AddChild(JSPointerBase *c);
 void RemoveChild(JSPointerBase *c);
 void InvalidateChildren();

 JSPointerBase(JSPointerBase* p);
 ~JSPointerBase();
};

template<class T> class JSPointer :public JSPointerBase
{public: T* P; bool AutoDelete;
   JSPointer(JSPointerBase* parent,T*p, bool ad);
   ~JSPointer();
   operator T*() {return P;}

   /*! Notifies the object that it has been orphaned.
       Does not call Parent->RemoveChild() or delete anything.
       Deletes the pointer and invalidates
   */
   void Close();
};

template<class T>
void JSPointer<T>::Close()
{
 if (P && AutoDelete) delete P;
 P=0;
 if (Parent)
  Parent->RemoveChild(this);
}

template<class T>
JSPointer<T>::JSPointer(JSPointerBase* parent,T*p, bool ad) :
  JSPointerBase(parent), P(p), AutoDelete(ad)
{
}

template<class T> JSPointer<T>::~JSPointer()
{
 InvalidateChildren();
 if (Parent)
  Parent->RemoveChild(this);

 if (AutoDelete && P)
  delete P;
}
 

Pure virtual base class
Use your favorite list template here.
Parent is notified when child is deleted.

RemoveChild sets child->Parent=0
Parent is going away, kill all children.





This is your smart pointer container.
Should I delete the pointer when I'm done?











Deletes the object pointer, but keeps
the envelope alive. Ex: file.close()

The guts of the smart pointers are implemented as follows


JSPointerBase::JSPointerBase(JSPointerBase* p)
 :Parent(p),Children(false)
 {
  if (Parent) Parent->AddChild(this);
 }

void JSPointerBase::AddChild(JSPointerBase *c)
{
 Children.Add(c);
}

void JSPointerBase::InvalidateChildren()
{
 FOREACH(JSPointerBase*c,Children)
  if (c)
  {
   c->Parent = NULL;
   c->Close();
  }
 DONEFOREACH
 Children.Flush(); //does not delete the pointers
}

void JSPointerBase::RemoveChild(JSPointerBase *c)
{
 FOREACHBACK(JSPointerBase*d,Children)
  if (d && c) if (c == d)
   {
    c->Parent = NULL;
    Children.AddAt(NULL,i);
    return;
   }
 DONEFOREACH
}

JSPointerBase::~JSPointerBase()
{
}
 













FOREACH is a macro to iterate collections.











If you're going to remove items from a list,
iterate in reverse order
.

That's a little unwieldy to use, so you'll probably want to use some macros to get and set your objects' private pointers

#define GETPRIVATE(type) (((JSPointer<type>*)JS_GetPrivate(cx,obj))->P)

#define GETPOINTER (((JSPointerBase*)JS_GetPrivate(cx,obj)))


#define GETOBJ(type,name) type * name = NULL;\
 JSPointer<type> *ptr_ ## name = (JSPointer<type>*)JS_GetPrivate(cx,obj); \
 if (ptr_ ## name) name = *ptr_ ## name;\
 if (!name) return JS_FALSE\

#define GETENV JSDBEnvironment* Env = \
 (JSDBEnvironment*)JS_GetContextPrivate(cx)

#define SETPRIVATE(obj,x,t,ad,parent) \
 JS_SetPrivate(cx,obj,new JSPointer<x>(parent,t,ad))

#define DELPRIVATE(x) \
JSPointer<x> * t = \
   (JSPointer<x>*)JS_GetPrivate(cx,obj);\
 if (t) delete t; JS_SetPrivate(cx,obj,NULL)

#define CLOSEPRIVATE(x) \
JSPointer<x> * t = \
   (JSPointer<x>*)JS_GetPrivate(cx,obj);\
 if (t) t->Close()

In a native method, use these
macros to retrieve the object's
private pointer.

This version also checks for
errors.



Access the global object.


Called by the initializer. Parent
is a smart pointer.

Called by the finalizer.




Called by close()


Now, how do you actually embed a class? You need an initializer,
JSBool
Image_Image(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
    jsval *rval)
{
 if (argc != 2)
 {
   JS_ReportError(cx,"Wrong number of parameters");
   return JS_FALSE;
 }

 long w,h;

 JS_ValueToInt32(cx,argv[x],&w)
 JS_ValueToInt32(cx,argv[x],&h)

 if (w < 1 || w > 4096 || h < 1 || h > 4096)
 {
   JS_ReportError(cx,"Wrong number of parameters");
   return JS_FALSE;
 }

 GIFImage * t = new GIFImage(w,h);

 SETPRIVATE(obj,GIFImage,t,true,NULL);
 return JS_TRUE;
}

The initializer function



Validate the input values.


Throws an exception.









Throws an exception.


Create the private object.

Set the private object.
Done with the initializer.

a finalizer,

void Image_JSFinalize(JSContext *cx, JSObject *obj)
{
 DELPRIVATE(GIFImage);
}

Automatic cleanup!

a property getter,
#define RETINT(x) {*rval = INT_TO_JSVAL(x); return JS_TRUE;}

JSBool
Image_JSGet(JSContext *cx, JSObject *obj, jsval id, jsval *rval)
{
 GETOBJ(GIFImage,t);
 int x = JSVAL_TO_INT(id);

 if (JSVAL_IS_INT(id))
  switch (x)
  {
   case 0: RETINT(t->iw);
   case 1: RETINT(t->ih);
   case 2: RETINT(t->nextcolor);
   case 3: RETINT(t->Size());
   case 4: RETSTR("Image");
   default: return JS_FALSE;
  }
 else return JS_FALSE;
}







Get the private object pointer.
The C++ name is GIFImage




Return the appropriate parameter.



I always have a 'className' property.
You can guess what RETSTR does.



a function to turn a C++ object into a JS object,
#define MAKENEW(name) \
obj = JS_NewObject(cx, JS_GetClass(Env->name),Env->name, NULL);\
JS_DefineFunctions(cx,obj,name ## _functions);\
JS_DefineProperties(cx,obj,name ## _properties)

JSObject*
Image_Object(JSContext *cx, GIFImage* t,bool autodelete,JSPointerBase* Parent)
{
 GETENV;
 JSObject* obj;
 MAKENEW(Image);

 SETPRIVATE(obj,GIFImage,t,autodelete,Parent);
 return obj;
}


A handy macro. Remember those
prototypes that we had in the
environment object? This is
where they get used.


Create a blank JS object,
turn it into the appropriate
class, and set the native
methods.

a class definition,
static JSPropertySpec Image_properties[] = {
    {"width", 0,   JSPROP_EXPORT,Image_JSGet},
    {"height", 1,   JSPROP_EXPORT,Image_JSGet},
    {"colors", 2,   JSPROP_EXPORT,Image_JSGet},
    {"size", 3,   JSPROP_EXPORT,Image_JSGet},
    {"className",4, JSPROP_EXPORT,Image_JSGet},
    {0}
};

static JSFunctionSpec Image_functions[] = {
    {"setBGColor",     Image_BGColor, 3},
    {"color",     Image_Color, 3},
    {"line", Image_Line,3},
    {"arc",    Image_Arc, 6},
    {"fill",    Image_Fill, 3},
    {"slice",    Image_PieSlice, 6},
    {"write",    Image_Write,1},
    {"getp",    Image_Getp,2},
    {"setp",    Image_Setp,3},
    {"print",  Image_Print,4},
    {"interlace", Image_Interlace,0},
    {0}
};

static JSFunctionSpec Image_fnstatic[] = {
    {"help",  Image_HELP,    0},
    {0}
};

static JSClass Image_class = {
    "Image", JSCLASS_HAS_PRIVATE,         //Image_JSGet
    JS_PropertyStub,  JS_PropertyStub, JS_PropertyStub,   JS_PropertyStub,
    JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, Image_JSFinalize
};

Nothing really special here.

a function to fetch the class definition
JSClass* Image_Class()
 {
  return &Image_class;
 }

Use this to determine the class of an object. For instance, you might want to do
JSObject* j0 = JSVAL_TO_OBJECT(argv[0]);
if (j0 && JS_InstanceOf(cx, j0, Image_Class(), 0))
 { GIFImage* myimage =  GETPRIVATE(GIFImage,j0); }


and a way to tell the interpreter about this class.
void Image_InitClass(JSContext *cx, JSObject *obj)
{
 GETENV;
 Env->Image = JS_InitClass(cx, obj, NULL,Image_Class(),
              Image_Image, 0,
              Image,properties, Image_functions, NULL, Image_fnstatic);

}

#define INITCLASS(name) \
 Env->name = JS_InitClass(cx, obj, NULL, name ## _Class(),\
 name ## _ ## name, 0,\
 name ## _properties, name ## _functions,NULL,name ## _fnstatic);


This is where the environment object
prototypes get set. They are rooted
in the context for you.





I like to use this macro.

So now, we're ready to run the interpreter

int main(int argc, char **argv)
{
const char* filename = argv[1];

 TBLEnv Tables;
 TPointer<JSDBEnvironment> Env(new JSDBEnvironment(&Tables));
 Env->reportWarnings = false;
 Env->shouldStop = false;

 Env->rt = JS_NewRuntime(8L * 1024L * 1024L);

 if (!Env->rt)
  {
   return 1;
  }

 Env->cx = JS_NewContext(Env->rt, 8192);

 if (!Env->cx)
  {             
   JS_DestroyRuntime(Env->rt);
   return 2;
  }
     
 CoInitialize(NULL);
 
 Env->in = new FileStream("stdin",Stream::OMText);
 Env->out = new FileStream("stdout",Stream::OMText);

 Env.global = JS_NewObject(Env.cx, &global_class, NULL, NULL);
 JS_DefineFunctions(Env.cx, Env.global, shell_functions);
 JS_SetPrivate(Env.cx,Env.global,(void*)&Env);
 JS_SetOptions(Env.cx,JSOPTION_STRICT|JSOPTION_VAROBJFIX);
 JS_InitStandardClasses(Env->cx, Env->global);

 JS_SetContextPrivate(Env.cx, (void*)&Env);

 Image_InitClass(Env->cx, Env->global);

 FileStream in(filename,Stream::OMBinary,Stream::ReadOnly);
 MemoryStream text;
 text.Append(in);

 JS_EvaluateScript(Env->cx, Env->global,
                   text,text.size(),filename,1,&rval);

 JS_GC(Env->cx);

 JS_DestroyContext(Env->cx);
 JS_DestroyRuntime(Env->rt);
 delete Env->in;
 delete Env->out;
 Env = 0;

 CoFreeUnusedLibraries();
 CoUninitialize();
 return 0;
}
Write a console-mode program.

You'd probably want to check this for errors.

Set up the environment variables




Create the runtime






And the execution context







We'll add an ActiveX module later

Input and output streams (optional)


Make the global object

The usual setup functions



Set up the global object

Add in the classes you've defined

Read the script text from a file



Run!


Extra garbage collection to
help debug errors
The usual cleanup.





Free ActiveX resources



To see how the ActiveX embedding works, view the source. If you understand COM, you'll figure it out. If you don't, you might want to read

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/com/cmf_a2c_9bj7.asp
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/com/cmf_a2c_36qt.asp
http://www.desaware.com/Articles/IndirectL3.htm#Objects%20and%20Interfaces
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/automat/htm/chap5_82r7.asp
http://www.codeproject.com/audio/speech.asp

To enable the ActiveX object, add ActiveX_InitClass(Env->cx,Env->global) after Image_InitClass().