Yes, right here:
http://bazaar.launchpad.net/%7Ealex.iskander/create/trunk/files/head%3A/Script/


It has projects to compile on both Windows (using VC++ Express) and  
Mac (using XCode). However, it is not easily accessible (yet)  
standalone. But you can easily browse through the source. If you'd  
like, I can package it in a ZIP file and upload it to the site. It has  
a few dependencies: the smart pointer files from the Bindable library,  
in the parent folder. I've been meaning to work around this, but  
haven't yet. And, naturally, it also depends on v8.



You may want to just jump in and browse, or jump to the end of this  
post and check out some of the code using it I've included below, but  
in case you want some (perhaps too much) background:

There isn't much in the way of API documentation, as it was originally  
meant as part of the print output framework I've been developing.  
There isn't even a real example of how to use it yet in there; the  
closest (which compiles and runs, but does nothing visible) is in  
test.cpp under the Tests directory. Since I have some free time right  
now, and I have an excuse, I may spend the next hour or so fixing up  
that file with a more elaborate test.

The overall structure is relatively simple:
There is a Global Context, which can contain multiple global sub- 
contexts. The global context does not have any ties to any scripting  
language or back-end. However, you add a V8Context sub-context object  
to it.

All objects, whether native from C++ or coming from JavaScript, get  
wrapped in intermediate script object wrappers. Shared JavaScript and C 
++ primitive types, such as Boolean and Number, get their own type; C+ 
+ native objects have their own type (along with a lot of other  
classes to help manage them). The V8Context object handles  
transforming these intermediate objects of whatever type into the  
Google v8-native types, and back to the intermediate types from there.

So, when you call myBoolean = $(true), ($() is the wrapper function  
which works for all objects), a new SBooleanObject (stored in an  
SBoolean pointer) is created.

When you call myContext->set("myBoolean", myBoolean); all sub-contexts  
of your global context (including the V8 context) get told to set  
"myBoolean" to myBoolean, an SBoolean object. The V8Context object  
then "unwraps" the boolean from the SBoolean, and gives it to v8 as a  
v8::Boolean.

All of that is really pretty simple.

The most complicated part is the part dealing with C++ native types. I  
wanted the wrapping to be completely transparent, so that, for any  
type, you could still just call $(myObject) and wrap it. To do this, I  
have a few things:

1. A map of C++ types (usually determined via RTTI) to SPrototype  
objects in the global context.
2. A type tree, used to determine the most derived type known for any  
object. This allows passing a base class pointer to JavaScript and  
getting in JavaScript the derived object.
3. An SPrototype class which has a collection of children.
4. For each type wrapped by JavaScript, an SNative type accompanying it.
     4a. Each SNative derives from base SNative classes in the same  
manner the native C++ types derive from base C++ classes.

For example, if you had Base, Derived1, Derived2, D2Derived, and  
D2DDerived, and you were only scripting Derived1, Derived2, and  
D2Derived, you might declare the native types thusly:
ScriptBaseNative(Base); //have to include so we can derive Derived1  
and 2
ScriptNative(Derived1, Base);
ScriptNative(Derived2, Base);
ScriptNative(D2Derived, Derived2);

Now what? Say you have a D2DDerived (a class derived from D2Derived).  
Let us assume it is named d2dd. If you call myNative = $(d2dd), the  
engine will attempt to find an appropriate SNative wrapper (note that  
these wrappers do not handle function calls, properties, etc.; they  
are just containers).

To find this object, the system would first check its cached map  
(which I have, unfortunately, not coded yet), and if it cannot find  
the object type (currently always the case), it
loops through each C++ sub-type, checking via dynamic_cast if the  
object is of that type. If it is, it then recursively checks all sub- 
types of that type, and so on, until it finds the most derived type.  
Slow if it happens every time (as it currently does), but it will be  
much faster when the results are cached (the performance hit will only  
occur the first time an object of that type is passed to JavaScript).

When you call globalContext->set("nativeObject", myNative); the  
V8Context looks up the SPrototype object mapped to that type. If it  
isn't found, it goes back up the inheritance tree (using the native's  
BaseClass typedef recursively) and checks each base class. If nothing  
is found, it should throw an error, but I don't have any error  
handling (at all) yet, so it instead just sends V8 v8::Undefined().



Finally, let me provide you with a sample of wrapping an object (an  
ostream, constructable):
ScriptBaseNative(std::ostream);

SWrap(ostreamWrapper, std::ostream)
{
public:
        //tested, in-production code for this object
        ostreamWrapper()
        {
                this->set("write", $(write));
                this->className = "OutputStream";
        }
        
        SMember(write)
        {
                for (int i = 0; i < arguments.length(); i++)
                {
                        if (arguments[i]->isString())
                                *extract(self) << arguments[i]->stringValue();
                        else if (arguments[i]->isNumber())
                                *extract(self) << arguments[i]->numberValue();
                }

                //write newline
                *extract(self) << "\n";
                
                return $();
        }

        //off the top of my head just now, and thus not actually tested
        SCreate
        {
                return new std::ofstream(arguments[0]->stringValue().data());
        }
        
        SConstruct
        {
                //if there was a smart pointer to ostream, we could increment 
it here
        }

        SDestruct
        {
                //if there was a smart pointer to ostream, we could decrement 
it here,
                //and decide if we should delete it.

                //this assumes that the object was created from JavaScript
                if (extract(self))
                        delete extract(self);
        }
};

int main(int argc, char *argv[])
{
        //create contexts
        R<GlobalContext> context = new GlobalContext();
        R<V8Context> v8context = new V8Context();
        context->addContext(v8context);
        
        //register type
        context->registerType<std::ostream>(new ostreamWrapper());
        
        //run a test script
        v8context->runScript("var stream = new OutputStream('output.out');  
stream.write('test'))");
        
        //return
        return 0;
}



And that's about it. It's overly complicated, and still a work in  
progress, but it works and fulfills my needs splendidly. One really  
nice aspect is that I implemented support for my smart pointer type by  
manually deriving SNative instead of using the macro. The macro  
usually deals in pointers; the manual class deals with the smart  
pointer instead:

/* This native is declared a-special so that we can add automatic  
destruction
  in a way that is transparent to derived objects.

  Aside from those additions, this is identical to the  
ScriptBaseNative macro.
  */
namespace Script
{
        template<>
        class SCRIPT_NATIVE<Bind::Mem> : public SNativeObject
        {
        public:
                typedef Bind::Mem BaseClass_;
                typedef Bind::Mem RepresentsClass;
                typedef SCRIPT_NATIVE<Bind::Mem> ParentNative;
                SCRIPT_NATIVE (Bind::Mem *object)
                {
                        //set internal object
                        this->nativeObject = object;
                }
                
                Bind::Mem *getObject()
                {
                        return *this->nativeObject;
                }
                
                virtual void *getID()
                {
                        return *this->nativeObject;
                }
                
                virtual int estimateNativeSize() { return sizeof(Bind::Mem); }
                
                virtual STypeInfo getType(int depth = 0)
                {
                        return STypeInfo(typeid(Bind::Mem));
                }
                
                ~SCRIPT_NATIVE()
                {       
                        this->ref();
                        if (this->_prototype) {
                                _prototype->destruct(this);
                                _prototype = NULL;
                        }
                        this->deref();
                }
        protected:
                Bind::R<Bind::Mem> nativeObject;
        };
}

Dealing with smart pointers during SConstruct and SDestruct is no  
longer required. This made things dead simple: all I have to do with a  
smart-pointable type is tell Script, as I have to anyway, that the  
type inherits from Mem (the base class for a type pointable to by a  
smart pointer). It will automatically inherit from my  
SCRIPT_NATIVE<Bind::Mem> class. This is a big benefit as I have a very  
large number of classes, all inheriting from each other and ultimately  
from Mem, that I may want to wrap with minimal difficulty.

Smart pointers were a complete necessity as a native object could be  
referenced from C++, JavaScript, or both simultaneously, and would  
need to stay in memory until neither referred to them anymore.

Hope I haven't overwhelmed you with meaningless technical details,

Alex

On Mar 11, 2009, at 9:00 PM, Stephan Beal wrote:

>
> On Wed, Mar 11, 2009 at 10:07 PM, Alex Iskander <[email protected]>  
> wrote:
> ...
>> aspects of wrapping types dead simple (the last class I wrapped "just
>> worked"), but comes with a penalty of extra overhead (and thus  
>> reduced
>> performance) and extra complexity. That method probably not what  
>> you want
>> nor need.
>
> i'd love to take a look at it. Got a link?
>
> :)
>
> -- 
> ----- stephan beal
> http://wanderinghorse.net/home/stephan/
>
> >

Alex Iskander, TPSi





--~--~---------~--~----~------------~-------~--~----~
v8-users mailing list
[email protected]
http://groups.google.com/group/v8-users
-~----------~----~----~----~------~----~------~--~---

Reply via email to