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
-~----------~----~----~----~------~----~------~--~---