Home License Download Tutorial Reference Projects Feedback History |
JS_AddRoot takes a pointer to a pointer, as in JS_AddRoot(cx,&obj)
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; } |
If you want to build your own JS environment, you'll want to imitate parts of JSDB.
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() |
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! |
#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. |
#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. |
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. |
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); } |
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().