Couldn't you use a Signature to avoid all the manual type checking?
On Mon, Jun 27, 2011 at 4:55 AM, Stephan Beal <[email protected]> wrote:
> Hi, all!
>
> The past years i've spent many, many hours trying to implement compile- and
> runtime-type-safe conversions from JS to Native space and back. For a long
> time i used external std::maps to hold information needed for tracing the
> type of a given (void*) (the one v8 holds). However, there's been an answer
> staring me in the face all along which i've missed entirely. Since this
> approach is very generic, easy, and self-contained (i.e. not dependent on
> anyone else's code), i thought i'd post it...
>
> When we bind natives, we typically reserve 1 internal field in the
> ObjectTemplate and store the native there in the constructor. i have never
> yet encountered a use case which would benefit from more than one internal
> field. Until today. We can use a second internal field to hold an arbitrary
> opaque value (unique per wrapped type) to identify the type held in that
> object. For example:
>
>
> Here's our type ID:
>
> static const int MyTypeID = 0; // value is irrelevant
>
> We can use an arbitrary type, as long as MyTypeID's address is unique per
> wrapped type.
>
> During construction we:
>
> obj->SetPointerInInternalField( 0, (void *)&MyTypeID ); // cast is
> unfortunate but necessary
> obj->SetPointerInInternalField( 1, myNewNative );
>
> During extraction (some call it unwrapping) we:
>
> if( &MyTypeID != obj->GetPointerInInternalField(0) ) {
> ... this is not one of ours. Fail the conversion now...
> }
>
>
> This is easy to genericize in templates and functors. Here's the complete
> code (and docs) for a functor which converts a v8::Handle<v8::Value> to (T*)
> using this type-safe approach...
>
>
> /**
> A concrete JSToNative implementation (to be used as a base class)
> which does a type-safe conversion from JS to (T*).
>
> T is the bound native type. A pointer of this type is expected
> in the object-internal field specified by ObjectFieldIndex.
>
> Each value to be converted is checked for an internal field
> at the index specified by TypeIdFieldIndex. If (and only if)
> that field contains the value TypeID is the conversion
> considered legal. If they match but the native value stored
> in field ObjectFieldIndex is NOT-a-T then results are undefined.
> Note that the TypeID value is compared by pointer address, not
> by its string contents (which may legally be NULL).
> */
> template <typename T,
> char const * & TypeID,
> int InternalFieldCount = 2,
> int TypeIdFieldIndex = 0,
> int ObjectFieldIndex = 1,
> bool SearchPrototypeChain = false>
> struct JSToNative_ObjectWithInternalFieldsTypeSafe
> {
> private:
> typedef char AssertIndexRanges
> [(InternalFieldCount>=2)
> && (TypeIdFieldIndex != ObjectFieldIndex)
> && (ObjectFieldIndex>=0)
> && (ObjectFieldIndex>0)
> && (ObjectFieldIndex < InternalFieldCount)
> ? 1 : -1];
> public:
> typedef T * ResultType;
> /**
> If h is-a Object and it meets the requirements described
> in this class' documentation, then this function returns
> the result of static_cast<ResultType>(void*), using the (void*)
> extracted from the Object. If the requirements are not met
> then NULL is returned.
>
> See the class docs for more details on the requirements
> of the passed-in handle.
>
> If SearchPrototypeChain is true and the object does not
> contain a native then the prototype chain is searched
> recursively. This is generally only required when bound
> types are subclassed from JS code.
> */
> ResultType operator()( v8::Handle<v8::Value> const & h ) const
> {
> if( h.IsEmpty() || ! h->IsObject() ) return NULL;
> else
> {
> void const * tid = NULL;
> void * ext = NULL;
> v8::Handle<v8::Value> proto(h);
> while( !ext && !proto.IsEmpty() && proto->IsObject() )
> {
> v8::Local<v8::Object> const & obj( v8::Object::Cast(
> *proto ) );
> tid = (obj->InternalFieldCount() != InternalFieldCount)
> ? NULL
> : obj->GetPointerFromInternalField(
> TypeIdFieldIndex );
> ext = (tid == TypeID)
> ? obj->GetPointerFromInternalField(
> ObjectFieldIndex )
> : NULL;
> if( ! ext )
> {
> if( ! SearchPrototypeChain ) break;
> else proto = obj->GetPrototype();
> }
> }
> return ext ? static_cast<ResultType>(ext) : NULL;
> }
> }
> };
>
>
> Note that i've made the TypeID template type (char const *&) instead of
> (void const *). This is for reasons internal to my library (i store the
> JS-side names of classes there). It could be changed to (void const *) but
> because we cannot cast pointers in template parameter lists, a (void const
> *) template parameter is rarely useful in my experience. For those who don't
> know this: using string literals as template parameters does not work in an
> intuitive manner but we can use (char const *&) to store strings in
> templates at compile-time, e.g.:
>
> struct Strings {
> static char const * MyClassName;
> };
> char const * Strings::MyClassName = "MyClass";
>
> typedef
> JSToNative_ObjectWithInternalFieldsTypeSafe<MyType,Strings::MyClassName>
> Converter; // this is legal
>
> Note, however, that file-local static vars cannot be used as template
> parameters, which is why we generally need (for templating purposes) a
> structure to put our static strings in.
>
> Anyway...
>
> Happy Hacking!
>
> --
> ----- stephan beal
> http://wanderinghorse.net/home/stephan/
>
> --
> v8-users mailing list
> [email protected]
> http://groups.google.com/group/v8-users
--
v8-users mailing list
[email protected]
http://groups.google.com/group/v8-users