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

Reply via email to