Subj : Re: updated jsgen (maybe rc1?) - beta 8 To : netscape.public.mozilla.jseng From : Marcello Bastea-Forte Date : Tue Jan 06 2004 07:36 pm ----------------------------------------------------------------- Short rant before actual message: I spent far too many hours today working on this project. While working and trying to understand all that you've explained so far I've started and canceled emails them a number of times. At one point I thought I had found a bug in SpiderMonkey, but knew it had to be my fault. I also found out, quite by surprise, Brendan Eich's actual role in JavaScript's development, and I wanted to thank him for actually taking the time to help me as much as he did so far. ----------------------------------------------------------------- But onto my actual message. Before I go into details, I've released yet another version of jsgen: http://www.cs.unm.edu/~cello/jsgen/ As I have mentioned in previous messages, the problem is around this code (from beta 7): ----------------------------------------------------------------- JSObject *ChildClass::getJSObject(JSContext *cx) { if (!cx) return NULL; if (!_JSinternal.o) { _JSinternal.o = JS_NewObject(cx, &ChildClass::_jsClass, ParentClass::JSInit(cx,NULL), NULL); if (!JS_SetPrivate(cx, _JSinternal.o, this)) return NULL; } return _JSinternal.o; } ----------------------------------------------------------------- In order to get the JSObject* from a C++ instance, I check if there already is one stored, and if not, call JS_NewObject. The problem here is calling ParentClass::JSInit for a prototype. The JSInit function calls JS_InitClass and returns the object, but requires a JSObject* to init the class into. While trying to figure out the best way to deal with the JSObject*, I came across a bigger problem. I first noticed it by putting print(new ChildClass); in my code, I would get an error (instead of it printing out [object ChildClass]). On further testing I realized that if I called one of ParentClass' functions through a ChildClass object (functions that aren't overridden), it would crash or get an error. Even more curious was that static functions/variables from ParentClass worked fine. (Note: the following solution to the problem is entirely based on how I /think/ SpiderMonkey/JavaScript works, so if the following is based incorrect ideas, let me know.) Based on the fact that static functions worked and non-static didn't, I guessed that JS_InitClass isn't returning a regular prototype that can be used in JS_NewObject. My understanding is that JS_InitClass returns some type of static/constructor class that doesn't have the member functions or variables/fields, but can be used to construct new classes from JavaScript. But it can't be used as a prototype for JS_NewObject. (In the docs for JS_InitClass, it says "JS_InitClass returns a pointer to a JS object that is the prototype for the newly initialized class." Does this need to be clarified or am I just missing something?) At that point I tried, for the hell of it, modifying getJSObject to use JS_NewObject to build the prototype (instead of JSInit/JS_InitClass). I came up with the following *new* code (in beta 8): ----------------------------------------------------------------- JSObject *ChildClass::newJSObject(JSContext *cx) { return JS_NewObject(cx, &ChildClass::_jsClass, ParentClass::newJSObject(cx), NULL); } JSObject *JSTestChild::getJSObject(JSContext *cx) { if (!cx) return NULL; if (!_JSinternal.o) { _JSinternal.o = newJSObject(cx); if (!JS_SetPrivate(cx, _JSinternal.o, this)) return NULL; } return _JSinternal.o; } ----------------------------------------------------------------- Since JS_NewObject doesn't require a JSObject* in which to define the object, this method has an added advantage of solving the original problem (two birds with one stone!). In addition, I added the following line to the JSConstructor routine after the C++ constructor had been called: ----------------------------------------------------------------- JS_SetPrototype(cx,obj,ParentClass::newJSObject(cx)); ----------------------------------------------------------------- The question is, is this method a bad idea? The immediate problem I see is calling newJSObject all the time to generate prototypes. (That is, a prototype can theoretically be shared among all subclasses, the way I understand it, and thus only one prototype is needed.) Also, explicitly setting the prototype in the JSConstructor routine seems like bad design. Since the JS_InitClass appears to deal with constructing of new objects, I look back at my JS_InitClass call that is unchanged since beta 7: ----------------------------------------------------------------- JS_InitClass(cx, obj, ParentClass::JSInit(cx,obj), &ChildClass::_jsClass, ChildClass::JSConstructor, ... ); ----------------------------------------------------------------- Should I be putting a call to ParentClass::newJSObject(cx) instead of the JSInit here? It /seems/ to work the same. I guess I'm still a little confused over JS_InitClass and JS_NewObject. I've been examining the code for both functions trying to figure out how they work, but that tends to confuse me more. (JS_InitClass calling JS_NewObject would imply that I could use newJSObject instead of JSInit.) So, I'm at a point where on the surface everything seems to be working. There are a few bits I'm uncertain on, but I don't see any obvious alternatives to what I'm doing now. I just noticed now that the child class prototype field is printing out as undefined in the following test code: ----------------------------------------------------------------- print("start"); var foo = new JSTest; print("\tfoo.prototype="+foo.prototype); print("\tfoo.test2()="+foo.test2()); print("\tfoo.test5()="+foo.test5(1,2,3)); print("\tfoo="+foo); var bar = new JSTestChild(1) print("\tbar.prototype="+bar.prototype); print("\tbar.test2()="+bar.test2()); print("\tbar.test5()="+bar.test5(1,2,3)); print("\tbar="+bar); ----------------------------------------------------------------- Yet test5(), which is only defined in JSTest gets called fine. Output (with the previous jsgen this code would crash when trying to call test5, but prototype would be correct!?): ----------------------------------------------------------------- start Vector2dChild(0x53ce84) JSTest(0, argv)=0x53ce60 foo.prototype=undefined JSTest::test2() foo.test2()=2 JSTest::test5(1,2,3) foo.test5()=5 foo=[object JSTest] Vector2dChild(0x53d0fc) JSTest()=0x53d0d8 JSTestChild(1)=0x53d0d8 bar.prototype=undefined JSTestChild::test2() bar.test2()=2 JSTest::test5(1,2,3) bar.test5()=5 bar=[object JSTestChild] ----------------------------------------------------------------- Thanks, Marcello .