Passing instances created in C++ to js

64 views
Skip to first unread message

migimunz

unread,
Oct 12, 2011, 12:26:25 PM10/12/11
to v8-juice-js-dev
Hello. I would like to expose a C++ class to js, but I don't want the
js side to be able to create instances of this class. Basically, it
would be a singleton in that js context (on C++ side every context
will have one instance), when I create a context I'd put an instance
of this class in it's global object. How do I go about passing an
instance of my class created in C++ to js? I understand that I'd
probably need to specialize NativeToJS for my class, but I'm not
entirely sure how to go about this. Here's an example class, the
method writer::write is bound to the object template's prototype
later, and that all works fine when I create an instance from js and
use it.


class writer
{
public:
writer();
void write(const std::string &);

typedef cvv8::Signature<writer(
cvv8::CtorForwarder<writer *()>
)> ctors;
};

namespace cvv8
{


CVV8_TypeName_DECL((writer));
template<>
class ClassCreator_Factory<writer>
:public ClassCreator_Factory_Dispatcher< writer,
CtorArityDispatcher<writer::ctors> >
{};

template <>
struct JSToNative< writer > : JSToNative_ClassCreator< writer >
{};

}

Stephan Beal

unread,
Oct 12, 2011, 12:35:29 PM10/12/11
to v8-juic...@googlegroups.com
On Wed, Oct 12, 2011 at 6:26 PM, migimunz <fingerp...@gmail.com> wrote:
Hello. I would like to expose a C++ class to js, but I don't want the
js side to be able to create instances of this class. Basically, it
would be a singleton in that js context (on C++ side every context
will have one instance), when I create a context I'd put an instance
of this class in it's global object. How do I go about passing an
instance of my class created in C++ to js? I understand that I'd

Hi!

In short, you need a cvv8::NativeToJS implementation for your type and then call:

myv8Object->Set(..., NativeToJS(myObject))

(or equivalent - you can also expose the "JS this" object in your class if you like and bypass NativeToJS in this case)

i don't think you will be able to hide the constructor from JS because once you have added the C++ Object to JS then i can do the following from JS:

var ctor = TheNativeObject.prototype.constructor;

var newInstance = new ctor(...);

probably need to specialize NativeToJS for my class, but I'm not
entirely sure how to go about this. Here's an example class, the

That's one way to do it but for this limited use case that might be overkill.

class writer
...
namespace cvv8
{

template <>
struct JSToNative< writer > : JSToNative_ClassCreator< writer >
{};

The NativeToJS conversion can be implemented any number of ways. For your particular use case i would recommend something like...

In your writer class expose a public v8::Persistent<v8::Handle>. Would would need to fully specialize/implement the ClassCreator_Factory to proxy the ctor, and then (in the factory's Create() function) assign newWriter->theHandle to the handle passed to the factory by ClassCreator.

Something like this (largely copied from addons/jspdo/jspdo.cpp, just mentally replace my types with writer):

    template <>
    class ClassCreator_Factory<cpdo::driver>
    {
    public:
        typedef cpdo::driver * ReturnType;
        static ReturnType Create( v8::Persistent<v8::Object> jsSelf, v8::Arguments const & argv );
        static void Delete( cpdo::driver * obj );
    };

    cpdo::driver * ClassCreator_Factory<cpdo::driver>::Create( v8::Persistent<v8::Object> jsSelf,
                                                               v8::Arguments const & argv )
    {
        ...
       writer * w = new writer;
       w->jsSelf = jsSelf;
       return w;
    }

Your NativeToJS impl could then be implemented like this:

template <>
struct NativeToJS<writer>{
          v8::Handle<v8::Value> operator()( writer const * v ) const
        {
            if( v ) return v->jsSelf;
            else return v8::Null();
        }
};


That "should work", i think.

--
----- stephan beal
http://wanderinghorse.net/home/stephan/

Stephan Beal

unread,
Oct 12, 2011, 2:02:03 PM10/12/11
to v8-juic...@googlegroups.com
On Wed, Oct 12, 2011 at 6:35 PM, Stephan Beal <sgb...@googlemail.com> wrote:
i don't think you will be able to hide the constructor from JS because once you have added the C++ Object to JS then i can do the following from JS:

var ctor = TheNativeObject.prototype.constructor;

There is a way around that, actually....

there's an example of this (or something close to it) in addons/whio/cvv8-whio.cpp. The StreamBase class is "abstract", meaning clients cannot instantiate them. Because i cannot hide the ctor from JS, i implemented the Create() factory function to always returns NULL (which will cause a JS-side exception to be thrown). Another way to do this is to hack the constructor/factory function to only work when called with a specific number/type of arguments. Somewhere i've got an example of this but can't find it. Basically it works like this:

Internally create your singleton like this:

writer * w = new writer;
Handle<Value> args[] = {
v8::Undefined(), v8::Undefined(), v8::Undefined(), v8::Undefined(),
v8::External::New(w)
};
Handle<Object> obj = ClassCreator<T>::Instance().NewInstance( 5, args );

globalObject->Set(..., obj); // add singleton to JS.


In your factory (ClassCreator_Factory<T>::Create()), check if (argv.Length()==5 and argv[4].IsExternal()). If it is, return the native from argv[4] from the constructor, else return NULL.

It's a bit of a hack, but i've used that before to prohibit client script code from ever being able to use the ctor for singletons.

Stephan Beal

unread,
Oct 12, 2011, 2:25:35 PM10/12/11
to v8-juic...@googlegroups.com
On Wed, Oct 12, 2011 at 8:02 PM, Stephan Beal <sgb...@googlemail.com> wrote:
number/type of arguments. Somewhere i've got an example of this but can't find it. 

Hi, again...

i found the example:

The JS ctor:


the internal call to the ctor:


By doing that i've hidden the PseudoFile ctor behind EPFS::open(), which calls the PseudoFile ctor and returns the new object. i cannot actually hide the ctor from the client (who can use aPseudoFileInstance.prototype.constructor to fetch it), but the arguments required by the ctor are not published, so only someone who digs around and groks the C++ code will know how to use it from script code. And if someone does that i'll change the impl to break their code ;).

That's not modelling a singleton, but a similar approach can be used for singletons.

Nemanja Stojanovic

unread,
Oct 12, 2011, 2:33:36 PM10/12/11
to v8-juic...@googlegroups.com
That's alright, the important thing is to make sure the user won't accidentally do new system(); or something like that. The part that was problematic for me was passing an already created instance to js. I will try all those solutions when I come home in a few hours, unfortunately I can't do it from here.

Thank you all very much for answering so fast!

Stephan Beal

unread,
Oct 12, 2011, 2:58:35 PM10/12/11
to v8-juic...@googlegroups.com
On Wed, Oct 12, 2011 at 8:33 PM, Nemanja Stojanovic <fingerp...@gmail.com> wrote:
That's alright, the important thing is to make sure the user won't accidentally do new system(); or something like that.

You can hide the _name_ from client scripts, but you can't hide the JS reference, unfortunately. i once had a class whose name was not exposed, but then i discovered the prototype.constructor workaround, which make the hiding kind of useless (and the name turned out to be useful for use with instanceof).
 
The part that was problematic for me was passing an already created instance to js. I will try all those solutions when I come home in a few hours, unfortunately I can't do it from here.

There are several ways to do it, so just pick what works best for you :). i'd be interested to know which approach you choose (and why), for future reference and potential use in my own code.
 
Thank you all very much for answering so fast!

problem == 0. It's just what i do...

That said, don't be offended if i don't always answer so quickly - i just happened to be in front of gmail when your question came in, and was "between bugfixes" at the time.

Happy Hacking!

Stephan Beal

unread,
Oct 12, 2011, 3:02:31 PM10/12/11
to v8-juic...@googlegroups.com
On Wed, Oct 12, 2011 at 8:33 PM, Nemanja Stojanovic <fingerp...@gmail.com> wrote:
That's alright, the important thing is to make sure the user won't accidentally do new system(); or something like that.

actually... you can't prohibit a user from calling (new anyFunctionName(...)), but IF the function is a bound native for which you wrote the binding code you can do this in the binding impl:

if(argv.IsConstructCall()) {
    return cvv8::Toss("May not be called as a constuctor!");
}

maybe i should add that to the cvv8 function binding impls by default??? (Opinions?)

Nemanja Stojanovic

unread,
Oct 12, 2011, 9:29:35 PM10/12/11
to v8-juic...@googlegroups.com
Hello,
Thank you for your suggestions, they solved my problem.
this is how I did it, I wrote a static method that returns the current instance of my singleton (which is not really a singleton but js doesn't need to know that). Then in ClassCreator_Factory specialization, I don't return a new instance, but call instance() to get the current one :

template<>
class ClassCreator_Factory<engine>
{
public:
static engine *Create(PObjectHandle, const v8::Arguments &)
{
return &engine::instance();
}
static void Delete( engine * )
{
}
};

Then I bound a method : 
cvv8::ClassCreator<engine> engine_class(cvv8::ClassCreator<engine>::Instance());
engine_class("loadLevel", cvv8::MethodToInCa<engine, void(const std::string &), &engine::load_level>::Call);

Then I just used the ClassCreator to create a new instance and set it to my context's global object, naming it "engine". Now, even if someone wanted to call engine.constructor explicitly, he'd just get the same instance (tested, works as expected). Thank you for your help, and sorry it took me so long to respond.

Best regards,
Nemanja Stojanovic

Stephan Beal

unread,
Oct 13, 2011, 3:45:17 AM10/13/11
to v8-juic...@googlegroups.com
On Thu, Oct 13, 2011 at 3:29 AM, Nemanja Stojanovic <fingerp...@gmail.com> wrote:
Thank you for your suggestions, they solved my problem.
this is how I did it, I wrote a static method that returns the current instance of my singleton (which is not really a singleton but js doesn't need to know that). Then in ClassCreator_Factory specialization, I don't return a new instance, but call instance() to get the current one :

That's good to hear - i'm glad you came across that solution as well, because i forgot to mention it.

Then I just used the ClassCreator to create a new instance and set it to my context's global object, naming it "engine". Now, even if someone wanted to call engine.constructor explicitly, he'd just get the same instance (tested, works as expected). Thank you for your help, and sorry it took me so long to respond.

No problem - i was sleeping, anyway. i'm glad it worked out for you.

Nemanja Stojanovic

unread,
Oct 13, 2011, 1:37:05 PM10/13/11
to v8-juic...@googlegroups.com
Hello,

I have one more question regarding ClassCreator_Factory::Delete. When exactly is it called? When the GC disposes of the js object bound to the C++ object? Most of the classes I want to expose to JS should exist independently, I would like the engine to manage their lifetime. I'm wondering, is it safe to leave ClassCreator_Factory::Delete empty? And also, is it safe to manually delete those classes when needed? The js_self handle is disposed of in the destructor, but the js code might still hold a handle to the object, and someone might still try to call a method or something.

Let's say I have a class game_object, which is updated by the engine and all that. Suddenly, the js code decides it's time for that game_object to die, and calls obj.kill(). The object will now be out of the list of objects, but should it be deleted? Or should I leave it there and wait 'till Delete gets called? 

How would you suggest I manage the lifetime of such objects?

Thank you,
Nemanja Stojanovic

Stephan Beal

unread,
Oct 13, 2011, 2:03:41 PM10/13/11
to v8-juic...@googlegroups.com
On Thu, Oct 13, 2011 at 7:37 PM, Nemanja Stojanovic <fingerp...@gmail.com> wrote:
exactly is it called? When the GC disposes of the js object bound to the C++ object?

IFFFFFF GC disposes (often it does not!).

what i always do is add the following binding to each ClassCreator-bound type:

typedef ClassCreator<T> CC;
CC & cc(CC::Instance());
cc("destroy", CC::DestroyObjectCallback);

(Though "destroy" sometimes gets a different name.) That function will effectively delete the native immediately and detach its ptr from its JS object in such a way that using the JS-side object later will fail. .g.:

var x = new MyNative();
x.destroy();
x.nonNativeFunction(); // okay (no native 'this' required)
x.nonClassMethodNativeFunction(); // okay (no native 'this' required)
x.boundClassMethod(); // will throw a JS exception

 
Most of the classes I want to expose to JS should exist independently, I would like the engine to manage their lifetime. I'm wondering, is it safe to leave ClassCreator_Factory::Delete empty?

That _will_ cause a leak (unless you're serving a singleton - in that case Delete() should normally do nothing!). The Delete() op _is_ the clean-up routine for that class. v8 (GC) triggers the destruction, but delegates the cleanup of the native half of the binding to ClassCreator, which routes it through the Delete() op for the specific type. So you can still rely on GC to clean up, but Delete() should certainly free any instance-specific resources for the binding (normally by simply calling 'delete' on the argument passed to it, but when binding C structs we need to call a type-specific destructor or free()).

And also, is it safe to manually delete those classes when needed? The js_self handle is disposed of in the destructor, but the js code might still hold a handle to the object, and someone might still try to call a method or something.

The bindings are set up to throw a JS exception in that case. When gc finalizes an object (or the CC::DestroyObjectCallback() is called), ClassCreator clears out the "internal field" where the native pointer is stored. Future calls to bound _member_ functions via that JS handle will try to extract it, find a NULL pointer, and then throw a JS exception. Calls to non-member bound functions generally will _not_ fail the same way because they (normally) do not have/need a native 'this' object. Those which do need a native 'this' can check for it like this:

(inside an InvocationCallback impl...)

T * self = CastFromJS<T>( argv.This() );
if(!self) { ... error ... }

calls obj.kill(). The object will now be out of the list of objects, but should it be deleted? Or should I leave it there and wait 'till Delete gets called? 

In my experience, any type-specific destructor, like GameObject.kill(), Stream.close(), ByteArray.finalize(), etc., should also free the native pointer (exception: singletons or shared instances whose lifetimes are managed somewhere else in native code).

How would you suggest I manage the lifetime of such objects?

Most of the native types i wrap (all but 1 or 2 of them) have very specific destruction requirements, and therefore i always add a binding for an explicit destructor function. e.g. when wrapping a db, all Statements it creates _must_ be finalized _before_ the db handle itself is closed (or undefined behaviour results). Relying on GC in such a case is just plain dangerous. For other types, like a ByteArray, relying on GC is not dangerous, it just leaks some memory until GC kicks in (IF it ever kicks in - v8 often _never_ GCs for short-running apps).

Whether or not waiting on GC is okay for your GameObject depends partly on its destruction requirements. For example, if GameObject is owned by a parent GameEngine object, then the GameObject should (must?) be cleaned up before the GameEngine is destroyed. Relying on GC would give undefined results because it cannot know which order to destroy them. Note that v8 does not automatically run GC at shutdown, it just exits and relies on the OS to clean up any remaining resources. This is not generally problematic for types which do _not_ require a destructor call for proper behaviour (e.g. a ByteArray class) but is problematic for types which require a destructor call for well-defined behaviour (like a db or file handle, or any type which might hold pointers to such resources).

Nemanja Stojanovic

unread,
Oct 13, 2011, 2:57:29 PM10/13/11
to v8-juic...@googlegroups.com
Thank you!
That really helps a lot!
Because I want these native objects deleted only at certain points in time, I've decided to use .kill() and ::Delete() to set a "dead" bit so that the engine can kill the object when it sees fit. Just tested it and it all works as expected. 
I didn't know what to expect of v8 GC, thank you for explaining.

Best regards,
Nemanja Stojanovic

Stephan Beal

unread,
Oct 13, 2011, 3:07:22 PM10/13/11
to v8-juic...@googlegroups.com
On Thu, Oct 13, 2011 at 8:57 PM, Nemanja Stojanovic <fingerp...@gmail.com> wrote:
decided to use .kill() and ::Delete() to set a "dead" bit so that the engine can
kill the object when it sees fit. Just tested it and it all works as expected. 

When you say "engine", do you mean v8 (JS engine) or your game engine? i've been assuming you mean JS engine, but it sounds now like you mean your game engine? v8 won't call Delete() until either v8 GCs the object or script code explicitly calls CC::DestroyObjectCallback() (or equivalent), so when Delete() is called you can be certain that one of those two things happened. If you don't add a binding for DestroyObjectCallback() then only GC will trigger Delete().

I didn't know what to expect of v8 GC, thank you for explaining.

i didn't know what to expect either until i experimented a lot with it :/. Unfortunately, the docs are a bit sparse. i hope i've been able to help.

Nemanja Stojanovic

unread,
Oct 13, 2011, 3:11:40 PM10/13/11
to v8-juic...@googlegroups.com
You've been a great help! And yes, I meant the game engine, sorry for the confusion :| . 
Heh, I thought you were one of v8 devs, actually. 

Stephan Beal

unread,
Oct 13, 2011, 3:43:09 PM10/13/11
to v8-juic...@googlegroups.com
On Thu, Oct 13, 2011 at 9:11 PM, Nemanja Stojanovic <fingerp...@gmail.com> wrote:
Heh, I thought you were one of v8 devs, actually. 

If i were one of the devs, v8 would be documented :).

Nemanja Stojanovic

unread,
Oct 13, 2011, 4:25:48 PM10/13/11
to v8-juic...@googlegroups.com
Well maybe you should be :P
Oh, and I also needed something to just "serialize" objects and PODs from js and back (mainly to load json configuration files), without actually binding js objects to C++ objects, literally like passing PODs by value between them. I also needed it to be very simple because I'll probably have a bunch of small but nested PODs I need to serialize to and from json. I realize I could use a separate json serialization lib, but I thought I might want to use it with my other v8 code. I wrote a small library to do exactly that, so if you happen to need something like that,here it is : 


Thank you for all your help, 
Nemanja Stojanovic

Stephan Beal

unread,
Oct 13, 2011, 5:08:34 PM10/13/11
to v8-juic...@googlegroups.com
On Thu, Oct 13, 2011 at 10:25 PM, Nemanja Stojanovic <fingerp...@gmail.com> wrote:
Well maybe you should be :P

i posted on the list 2 or 3 times that if they'd give my employer a contract for it, i'd WTFM for them, but no answers. About 2.5 years ago i looked into the dev process so i could start documenting it, but it's just too complex to be worth the effort. If it was just "checkout, change, commit", i would do it, but it's it's got custom dev tools and a complex process to follow, and i'm not willing to put the effort into it.
 
Oh, and I also needed something to just "serialize" objects and PODs from js and back (mainly to load json configuration files), without actually binding js objects to C++ objects, literally like passing PODs by value between them. I

i use JSToNative and NativeToJS for such cases, so that CastToJS(myPODType) will return a v8 Object or Array or Value or whatever closely maps to that native type.

other v8 code. I wrote a small library to do exactly that, so if you happen to need something like that,here it is : 

Great - a generic solution is always good. Serialization of C++ objects is a bit of a hobby of mine, actually: http://s11n.net/

The JSToNative/NativeToJS APIs are effectively serialization interfaces, and seem to be functionally very similar to what i'm reading in the README for v8serialize. Your two-arg interfaces are "more correct" IMO, and are essentially identical to the interfaces i use in the s11n project.

The next version of s11n (tentatively being called s15n) will be purely JSON-based (instead of data format agnostic like it is now). i'm convinced that JSON is a better overall serialization format than XML and many other conventional solutions, primarily because it keeps rich type information (XML is all strings, and any conversion requires grammar-specific types and rules to convert to/from strings).
 
Thank you for all your help, 

problem == 0

Happy Hacking!

Stephan Beal

unread,
Oct 13, 2011, 5:11:49 PM10/13/11
to v8-juic...@googlegroups.com
On Thu, Oct 13, 2011 at 11:08 PM, Stephan Beal <sgb...@googlemail.com> wrote:
On Thu, Oct 13, 2011 at 10:25 PM, Nemanja Stojanovic <fingerp...@gmail.com> wrote:
Oh, and I also needed something to just "serialize" objects and PODs from js and back (mainly to load json configuration files), without actually binding js objects to C++ objects, literally like passing PODs by value between them. I

BTW: if you are ever looking for a non-v8 C++ JSON library for doing that, see:


which is basically a JSON version of your v8serialize.

Nemanja Stojanovic

unread,
Oct 13, 2011, 5:30:07 PM10/13/11
to v8-juic...@googlegroups.com
Heh I just saw that link on s11n.net and started reading about it, looks pretty awesome. I just pulled the source, going to check it out now.

I see you also wrote SpiderApe? You seem interested in js, mind if I ask what you used v8 and SpiderMonkey for? 

I was actually looking for a json serialization lib, actually, this might just be what I'm looking for. 

Nemanja Stojanovic

unread,
Oct 13, 2011, 6:03:42 PM10/13/11
to v8-juic...@googlegroups.com
Also, I'm going throught nosjob code, trying to figure out where you defined the Atom, can't really find it anywhere :| . Also if you'd like I'd be glad to test it on Windows, I have to switch to it later to do my homework anyway (specifically requires Visual Studio).

Stephan Beal

unread,
Oct 14, 2011, 4:01:13 AM10/14/11
to v8-juic...@googlegroups.com
On Fri, Oct 14, 2011 at 12:03 AM, Nemanja Stojanovic <fingerp...@gmail.com> wrote:
Also, I'm going throught nosjob code, trying to figure out where you defined the Atom, can't really find it anywhere :| . Also if you'd like I'd be glad to test it on Windows, I have to switch to it later to do my homework anyway (specifically requires Visual Studio).

The declarations are in include/wh/nosjob/nosjob.hpp and the Atom base class is defined in src/nosjob.cpp. Most of the higher-level classes are in their own cpp files. AFAIK the sources are platform-portable - it just uses the STL and a couple bits of portable 3rd-party code (included in the tree).

Stephan Beal

unread,
Oct 14, 2011, 4:01:18 AM10/14/11
to v8-juic...@googlegroups.com
On Thu, Oct 13, 2011 at 11:30 PM, Nemanja Stojanovic <fingerp...@gmail.com> wrote:
I see you also wrote SpiderApe? You seem interested in js, mind if I ask what you used v8 and SpiderMonkey for? 

It's a little known fact that the only apps i use them in is test/demo/plugin code. i very, very rarely write applications except test apps - 99% of my code is library-level and back-end stuff. Most libs i write just because enjoy writing them, not because i actually have a use for them. My initial reason for writing SpiderApe was to add scripting support to a shell-app framework i was working on (before i found SpiderMonkey i had written my own C-like script language, but it was very limited), but i had so much fun writing the JS binding code that i ended up abandoning my shell framework. When i saw that google had created a JS engine, i had to try it out, and so v8juice was born (using type conversion code copied/adapted from SpiderApe).
 
I was actually looking for a json serialization lib, actually, this might just be what I'm looking for. 

If you give it a try, let me know what's missing/what it needs. The two significant features it's missing which libs11n has are polymorphic deserialization and automatic loading of new types from DLLs. i have code for doing both, just don't haven't needed them yet in that particular library.

:)

Nemanja Stojanovic

unread,
Oct 14, 2011, 5:38:09 PM10/14/11
to v8-juic...@googlegroups.com
Sounds like the library is pretty much usable, from what you said in the beginning, I thought the development had just begun :) . 

Btw, I'm still having a fight with v8 over here. If I had js code call a function which creates a new context, what would happen? I guess I'm asking what happens with the old context when I enter a new one, does the old one finish what it was doing? 

Also, I hope it's not a problem to ask so many question in this thread, seeing how all but the first question were pretty much off topic.

Stephan Beal

unread,
Oct 14, 2011, 5:41:48 PM10/14/11
to v8-juic...@googlegroups.com
On Fri, Oct 14, 2011 at 11:38 PM, Nemanja Stojanovic <fingerp...@gmail.com> wrote:
Sounds like the library is pretty much usable, from what you said in the beginning, I thought the development had just begun :) .

i wouldn't knowingly send you a link to non-working code :).
 
Btw, I'm still having a fight with v8 over here. If I had js code call a function which creates a new context, what would happen? I guess I'm asking what happens with the old context when I enter a new one, does the old one finish what it was doing? 

i have no clue. i've never used more than one context in an app. i know that Cohen and Anton have - maybe they're listening in and can comment on that.

 
Also, I hope it's not a problem to ask so many question in this thread, seeing how all but the first question were pretty much off topic.

No problem at all. If you want to start separate threads, go right ahead - i have no preference.
 

Nemanja Stojanovic

unread,
Oct 14, 2011, 5:45:43 PM10/14/11
to v8-juic...@googlegroups.com
Thank you. From what I read from google's docs (and comments in the header) it looks like it just pushes and pops the currently active context on some internal stack, so I don't think it should be a problem. Still, I have no idea what would happen to the old context if a new one was created and entered in a callback called by the first context. I'm going to try and see I guess :)

Anton Yemelyanov

unread,
Oct 14, 2011, 6:23:52 PM10/14/11
to v8-juic...@googlegroups.com
I never used more then one context, but my assumption would be that the callback would be executed in the context it was created in.

ASY
Reply all
Reply to author
Forward
0 new messages