|
ConvertingTypes
Converting between JS and native types
An introduction to the v8::juice::convert APIJuice comes with an API for casting between JS types and native types. While v8 of course provides convenience routines like Value::IntegerValue() and Value::ToString(), that leaves us with two limitations:
And thus we let C++ templates decide it for us... #include <v8/juice/convert.h> ... namespace cv = ::v8::juice::convert; using namespace v8; int i = cv::CastFromJS<int>(someJSVal); std::string s = cv::CastFromJS<std::string>(someJSVal); double d = cv::CastFromJS<double>(someJSVal); ... Handle<Value> v1 = cv::CastToJS( myInteger ); Handle<Value> v2 = cv::CastToJS( "hi, world" ); Handle<Value> v3 = cv::CastToJS( myType ); // we can add custom conversions! The CastToJS() and CastFromJS() routines are very small functions which delegate all work to template a specialization of the class NativeToJS<> resp. JSToNative<>. Specializations are already installed for all of the base POD types (except (char const *) - see below), and clients can provide their own to do any conversion they like. An example of this is shown on the ClassBinder page. While such a framework might at first seem like syntactic sugar, it has important implications. Namely, we can write generic algorithms which are ignorant of the conversions they are doing. As an example, the library provides a pair of template functions which can act as property getters/setters for arbitrary convertible member variables. The entire implementation is about 10 lines of code, half of which is error checking. Thus, instead of writing a custom getter/setter pair for each member variable, we can get away with a single, trivial implementation. From there, we can "let the templates to the typing" (if i may now coin a phrase). For those averse to template arguments, there are a number of convenience instantiations of the JSToNative and NativeToJS template which can be used like this: int32_t i = JSToInt32(...); std::string s( JSToStdString(...) ); bool b = JSToBool(...); return DoubleToJS(42.42); but then we're again stuck with the limitation of hard-coded conversion operator names, so these aren't very useful in generic algoriths. Implementing custom conversionsAchtung: not for noobsForewarning: customizing the type conversions requires a fair amount of knowledge in how to use and abuse C++ templates. DO NOT just blindly copy/paste some example from this page without understanding what it does and how it gets triggered. One tiny little typing mistake can lead to literally hundreds of lines of compiler error messages (or weird link errors), and it often takes patience and experience to be able to decipher such problems ("dammit, it's a pointer, not a reference"). Good luck! Custom native-to-JS conversionsTo make our own types support work with CastToJS() we have to provide a template specialization for NativeToJS<>. The example shown here works in conjunction with WeakJSClassCreator to do the conversion: namespace v8 { namespace juice { namespace convert {
template <>
struct NativeToJS<MyType>
{
Handle<Value> operator()( MyType const * n ) const
{
// This impl works together with WeakJSClassCreator():
return ::v8::juice::WeakJSClassCreator<MyType>::GetJSObject( n );
}
};
} } } // namespacesThe important thing is that this specialization is available (visible in code) before any client code calls (however indirectly) CastToJS<MyType>(...). If it is not, the compiler will try to pick up another instantiation of the template. With luck it will fail loudly, but (and this depends on the implicit native conversions possible for MyType) it may pick up a different, inappropriate converter. A good place for such a routine is directly after the class' declaration, though other circumstances may require a later placement. e.g. when used together with WeakJSClassCreator, the casting specializations must come after the WeakJSClassCreatorOps<> specialization has been declared (they are all normally defined where the are declared (inline) since they are all templates). Custom JS-to-native conversionsThe converse of NativeToJS<> is of course JSToNative<>: namespace v8 { namespace juice { namespace convert {
template <>
struct JSToNative<MyType>
{
typedef MyType * result_type;
result_type operator()( Handle<Value> const & h ) const
{
// This impl works together with WeakJSClassCreator():
return ::v8::juice::WeakJSClassCreator<MyType>::GetNative( h );
}
};
} } } // namespacesThere's not much else to say about it, really. There is no need to set up T & or T const & specializations. When asked to use JSToNative<T&>, a generic X & specialization will be triggered which will actually use JSToNative<T> to get a pointer to the object, and will then (if the pointer is not 0) dereference it to form a reference. If JSToNative<T&> cannot get a native pointer, it will throw a native exception (it has no other choice except to crash). Thus client code which requires conversions to references must use try/catch blocks in order to keep potential exceptions for propagating through v8 (which can be fatal to the JS engine). While the conversions for the built-in numeric types (and certain POD-like types, e.g. std::string) return their natives by value, in practice we need JSToNative<T> to return pointers for any client-side bound types. The (char const *) problemThe following will work as expected: Handle<Value> v = CastToJS("this is a (char const *)");This won't: char const * v = CastFromJS<char const *>(myJSVal); The problem here is ownership and lifetime of the string bytes. v8 does not document them (and also can't realistically provide any requirements which we would need for this conversion). We can write a conversion routine for this, and it would probably work 99% of the time, but its results would technically be undefined. As a workaround, client code should use std::string instead (at least for CastFromJS()) or implement JSToNative<> and/or NativeToJS<> specializations for their string type of choice. The CastToJS() function is overloaded for (char const *) and "does the right thing", but CastFromJS() cannot handle such strings as transparently as it can most other types. Forwarding to functionsThe v8::juice::convert namespace contains functions named FwdToFuncN(), where N is the number of arguments they expect, which use the casting framework to marshal calls between JS and native functions. The ClassBinder class can also bind member functions. TODOs:
Forwarding to member functions/variablesNative classes which are bound using the ClassBinder class can bind member functions and member variables, as well as free functions. See that page for an example, or see ClassBinder.h for the API docs. Silly ExamplesHere are some examples of the things one can do with the type conversions API: std::list<int> li; ... populate list ... Handle<Value> v( CastToJS( li ) ); // Array of Integers That will work for any list-contained type which is supported by the casting API. Ergo, a list containing lists also works: typedef list<std::string> StringList; typedef list<StringList> ListList; ListList li; ... populate li ... Handle<Value> v( CastToJS( li ) ); // Array of Arrays of strings And that means that we can use ClassBinder to bind to member functions which take or return arguments of those types, and or member variables of those types, too. And how about: StringList sl = CastFromJS<StringList>( someJSVal ); That will result in a populated list, but only if someJSVal is-an object of Array type. If it is, each entry in the array will be cast using CastFromJS<StringList::value_type>(). See convert.h for how such conversions are implemented (search that file for ListT). |
Sign in to add a comment