Getting started with SpiderApe:
The fun all starts in the source tree, in src/client/main.cpp.
There are many examples of using the features in src/ape/builtins.cpp
and src/plugins/nc/ncwrapper.*pp. A slew of sample/test JS scripts live under
src/client/scripts/....
Below we will show a couple short demonstrations...
If you just want to run arbitrary JS code, and not
add your own functions or classes:
#include <sf.net/ape/spiderape.hpp>
// If using the 2005.10.03 release of Ape, instead include:
// #include <s11n.net/ape/spiderape.hpp>
ape::MonkeyWrapper js;
jsval myjsval;
js.eval_source( "a = 10", // arbitrary JS code
myjsval, // return value
"my inlined script" // script name, for error reporting
);
Of course, eval_source() also accepts stream as an input source, and eval_file()
accepts a filename.
Conversions between JS and native types:
// Assume: JSContext * cx = some JS context, like js.context()
using namespace ape;
// Typical POD conversions...
// Assume jv1..jvN are jsval vars:
std::string foo = jsval_to_std_string( cx, jv1 );
char const * cstring = jsval_to_cstring( cx, jv2 ); // JS owns the string!
char ch = jsval_to_char( cx, jv3 ); // converts ints or one-char strings
jsval jch = char_to_jsval( cx, ch ); // converts to a one-char string
// For template-inclined users, an equivalent approach:
std::string bar = jsval_to<std::string>( cx, jv1 );
char const * cstring2 = jsval_to<char const *>( cx, jv1 );
// And best of all, we can also plug in our own objects
// to the system:
ape::NCWindow * win = jsval_to<NCWindow>( cx, jv3 );
More powerful conversions:
using namespace ape;
// For demonstration, my_function() sets i to an unspecified
// non-0 value returns the string form of the number.
std::string my_function( int & i )
{
std::ostringstream os;
i = 42;
os << i;
return os.str();
}
//... now assume we are in the native impl of a conventional JS function...
// Assume we are passed an integer.
// Bind a native integer to the argument:
int nati = 0;
scoped_binder<int> sentry( cx, argv[0], nati );
// Forward argv to my_function(), applying type conversions, and
// convert return value to JS:
*rval = std_string_to_jsval(
fwd_to_func1<std::string,int &>( my_function, cx, obj, argc, argv )
)
);
// Now nati holds the value assigned to it in my_function().
// The JS/Native binding to nati is removed when sentry goes out of scope,
// providing a simplified exception safety compared to directly using
// the lower-level bind/undbind_native() functions.
Class<>:
Here is an example of creating a new JS-side class:
#include <sf.net/ape/ape.hpp>
#include <sf.net/ape/class.hpp>
struct MyClass
{
private:
JSContext * m_cx;
JSObject * m_jo;
public:
/** Required ctor. */
MyClass( JSContext *cx, JSObject *obj, uintN argc, jsval * argv, jsval * rval )
: m_cx(cx), m_jo(obj)
{
}
// Functions we want to bind:
double multiply( int lhs, int rhs )
{ return lhs * rhs; }
std::string concat( std::string const & lhs, std::string const & rhs )
{ return lhs + rhs; }
/**
Init function to create and register the JS
class. This need not be a member, but should
be called before creating your MonkeyWrapper(s).
This registers MyClass with
ape::standard_class_initializers(), which means
that MonkeyWrappers created after this is called
will have access to MyClass.
Calling this more than once may result in
undefined behaviour.
*/
static void define_js_class()
{
// define JS class and function names:
static const char * cName = "MyClass";
static const char * fMult = "multiply";
static const char * fConcat = "concat";
static const char * fPrint = "print";
// Start the class init process:
typedef ape::Class<cName,MyClass> C;
C::impl & ci = C().Implement();
// Add members:
ci.Member<fMult,double,int,int>( &MyClass::multiply );
ci.Member<fConcat,std::string,std::string const &,
std::string const &>( &MyClass::concat );
ci.Member<fPrint>( ape::ape_print ); // support added 2006.03.16,
// ^^^ Here we bind an arbitrary JSFunction as a member function.
// Finalize the definition process:
ci.Define();
}
};
// From your main app/init routine you must then initialize MyClass ...
MyClass::define_js_class();
// All MonkeyWrappers created after define_js_class() is called
// have access to the class in script code:
var m = new MyClass();
m.print( m.multiply(3,4),
m.concat( "hi, ", "world" )
);
By adding some code to the ctor you can add additional member
functions when the object is created:
MyClass( JSContext *cx, JSObject *obj, uintN argc, jsval * argv, jsval * rval )
{
ape::scriptable sc(cx,obj);
sc.add_function( "divide", "function(a,b) { return a/b; }" );
sc.add_function( "eval", ape::ape_eval );
sc.add_function( "debug", "function(){ this.print('MyClass Debug:',arguments); }" );
}
You can also call those functions from C++ code. For example,
assume we add the following member function to MyClass:
void do_something()
{
ape::scriptable sc(this->m_cx,this->m_jo);
jsval rv;
sc.eval( "this.tmp = this.divide(4,2)", &rv );
// ^^^ 2 == jsval_to_int(rv)
sc.eval( "this.debug('divide(4,2) ==',this.tmp)", &rv );
sc.eval( "delete this.tmp", &rv );
}