This project is read-only.

Do not use finalizers on CLR objects constructed from script

Sep 17, 2013 at 11:07 PM
I'll hopefully save someone else some time trying to figure out what's going on:

If you create a class that you're exposing to script and allowing the JS environment to construct your object, do not put a finalizer on that class. The finalizer may run even though the object is still being used by the JS side. Instead, implement IDisposable and do all of your cleanup in the Dispose method.
Sep 17, 2013 at 11:50 PM
Edited Sep 17, 2013 at 11:59 PM
This is by design (there are some intellisense comments around this as well - see {V8NativeObject}.Dispose()). V8NativeObject (inherited by 'ObjectBinder', or in a custom class) blocks the finalizer in order to confirm with the native side. This requires making the underlying native V8 handle weak. A special "ObservableWeakReference<>" object was created to reestablish a strong reference upon GC of a V8.NET object. This queues the object in the worker to make the V8 handle weak. The native V8 engine will call back when ready, then the object is released. Unfortunately, even though the V8NativeObject is saved (re-registered with the GC), all other associated objects are not respected, and are released anyhow. Unfortunately, the GC doesn't seem to respect re-registering child object references from a parent object; it has to be done in the finalizer of the child object's themselves.

To resolve this, if possible, inherit from 'V8NativeObject' and use the "Initialize()" method as your constructor, and override "Dispose()" instead, which will be called on the same thread as the *native V8 callback" for disposing the object.

If you cannot inherit from 'V8NativeObject', then use the generic version: "V8NativeObject<MyClass>", where "MyClass" implements 'IV8NativeObject'. ;)

Thanks for bringing this up. I'll most likely update the code to test for 'IV8NativeObject' on bound objects and use it if found.
Sep 18, 2013 at 12:18 AM
Edited Sep 18, 2013 at 12:18 AM
'IV8NativeObject' is now supported on bound objects. The release binaries are updated under the development branch. 8)
Sep 20, 2013 at 2:48 PM
Edited Sep 20, 2013 at 2:48 PM
The one sticking point I've had on inheriting from V8NativeObject was that Initialize method. Are constructor args from script still passed to my class's constructor? Or are they somehow exposed on the base class while I'm inside the Initialize method?
Sep 20, 2013 at 3:20 PM
Edited Sep 20, 2013 at 3:23 PM
V8NativeObject represents an instance of your object, not the type, so since the object is already created, there's no need for a constructor.

In V8 (or any engine really), you need to create a function object (optionally with the class name) and set a global JS propery to that function, which is called from a script with "new" - that is where your arguments go. :)

V8 passes a flag to the callback to let you now of constructor calls, then you create, setup, and return your object. There's an example in the Console's program.cs (near the bottom)
Sep 20, 2013 at 3:34 PM
I see. It seems that there's an awful lot of work to be done to make inheriting from V8NativeObject possible. What advantage do I get from doing that? Is GC more efficient in this case?
Sep 20, 2013 at 3:48 PM
If the object only needs to be in script, there's no need for a managed side one (you can just get its handle). Remember, I'm just mimicking how V8 works. In V8, you create function objects that can create object instances, and optionally can use object templates as well when creating them.

V8NativeObject is just a nice base class for managed side class instances that want to be exposed to the JS environment. It is not bound in any way. You can optionally call "SetPropery(typeof(theClass), ...)" the have V8.Net do all the work for you (including binding the constructor as a global function). :)

It's just another tool - you have to know what you want your class to do in order to decide which route to take. What is the end goal?
Sep 20, 2013 at 4:09 PM
I have a class defined in C#. It will be created by script. Script will also set properties, register callbacks, call methods, etc. When it's no longer necessary, it will fall out of scope and need to be GCed (but I need to be notified when it's going to be collected, because the class has some things to dispose).

This is where I'm getting hung up: since the class was only used by script, I thought the GlobalObject.SetProperty(Type, ...) method was what I needed. However, this was what caused the initial post: the finalizer on that object was running too early. I thought implementing IDisposable was a better plan, but (not surprisingly now) nobody cares that it's disposable, so Dispose() never gets called. It would seem that I need to implement V8NativeObject to get Dispose notifications. However, once I do that, I'm getting errors from script that it can't find methods and properties on my class (do I have to expose those myself?).
Sep 20, 2013 at 5:22 PM
Oh, try implementing the "IV8NativeObject" interface instead for your custom class (instead of inheriting), then the binder will call the methods in the interface. :) Trying to bind an object inheriting from V8NativeObject may skip the binding and return
the handle, which may be why you are not seeing anything (or the type wasn't registered properly).
Sep 20, 2013 at 5:32 PM
Make sure to use the release build in the development branch, there was some addition to support custom classes which implement the 'IV8NativeObject' interface. The dispose method is called when the object is ready to be disposed by both sides.
Sep 20, 2013 at 5:35 PM
Edited Sep 20, 2013 at 5:44 PM
FYI: You may find this recent discussion helpful as well:
https://v8dotnet.codeplex.com/discussions/456763

In summary, you probably want this to make things easier (rather than hooking everything up yourself):
jsServer = new V8Engine();
jsServer.WithContextScope = () =>
{
    // Changed from ScriptMemberSecurity.DontDelete.
    jsServer.RegisterType(typeof(SamplePoint), null, true, ScriptMemberSecurity.Locked);
    jsServer.GlobalObject.SetProperty(typeof(SamplePoint));
    jsServer.GlobalObject.SetAccessor("print", YourPrintGetter, null, V8PropertyAttributes.Locked);
};
(where "SamplePoint" in this example would implement "IV8NativeObject" if finalization [disposal] notifications are needed)
Sep 20, 2013 at 6:02 PM
Side note: The "Intialize()" method was designed to allow configuration of new object instances only, and is only called on the managed side (never script), much like how objects in Silverlight/WPF must have default constructors. Silverlight/WPF requires default constructors because the types have to be created (from XAML) internally, just like with V8.NET, and the system calls the "OnApplyTemplate()" virtual method to setup "template" related values (for custom controls) - which is the purpose of "Initialize()". Because initializing is always managed side, it didn't initially make sense to pass it arguments, as those can be set directly once the object is created. This also makes another point - "Initialize()" is usually called outside a "new" call, and would never get arguments.

That said, in retrospect however, I could look into creating similar parameters as the JSFunction object, and just pass in null for the arguments. :)
Sep 20, 2013 at 7:21 PM
Edited Sep 20, 2013 at 7:22 PM
I had my class implement IV8NativeObject (which also meant I had to implement IV8Object). For the IV8Object methods, do I just pass the call onto the same function on the "owner"? If not, I don't see how this way is any different than just deriving from V8NativeObject (I need to create all the function objects and property bindings for my class manually). The behavior is the same as before: Initialize and Dispose are never called (nor are any of the IV8Object methods).

I'm not sure where V8NativeObject<T> comes into play. If I do GlobalObject.SetProperty(typeof(V8NativeObject<MyObject>)) instead of my class type directly, after the constructor (not Initialize) is called, the app crashes due to a NRE in InternalHandle.HasObject (_HandleProxy is null). This is the same behavior as if I have my class derive from V8NativeObject (and don't do the FunctionTemplate stuff as shown in the example app).

If I go the route of creating a FunctionTemplate and putting the .GetFunctionObject() in the GlobalObject (as shown in the example app), I still get the errors about not being able to find methods and properties that are defined on my class.

I just grabbed the latest code that passes isConstructCall and args to the Initialize method, but isConstructCall is always false, and args is always null. And I still see the errors about not finding methods and properties.

Some code:

Main.cs:
                    FunctionTemplate funcTemp = engine.CreateFunctionTemplate(typeof(SomeObj).Name);
                    engine.GlobalObject.SetProperty("SomeObj", funcTemp.GetFunctionObject((_engine, isConstructCall, _this, args) =>
                        {
                            V8NativeObject<SomeObj> req = _engine.GetObject<V8NativeObject<SomeObj>>(_this);
                            return req;
                        }));
                    engine.Execute("var xyz = new SomeObj({x: 100});  xyz.DoSomething();");
SomeObj.cs:
class SomeObj : IV8NativeObject
{
        public ObjectHandle Initialize(V8NativeObject owner, bool isConstructCall, InternalHandle[] args)
        {
            //do something here with args, but it's null
            //and do I have to manually expose the "DoSomething" method (like shown in the example app lines 608-610)?
        }

        public void Dispose()
        {
            //this never gets called (or maybe it does eventually, but since I can't get access the other methods, things blow up long before this would get collected).
        }

        public void DoSomething(string a, string b, string c)
        {
        }

        //all the IV8NativeObject stuff
}
If Main.cs does something like the following instead, Initialize and Dispose are never called (but my custom methods like "DoSomething" are called):
  engine.GlobalObject.SetProperty(typeof(SomeObj));
Sep 20, 2013 at 7:29 PM
Oops, my bad, that is no longer the case. 'IV8NativeObject' should only implement IDisposable;. I fixed it and updated the dev branch again. :)
Sep 20, 2013 at 7:35 PM
Dispose() only gets called when both the managed and native GC release the objects. I can't dispose anything before that.
Sep 20, 2013 at 7:39 PM
Edited Sep 20, 2013 at 7:39 PM
Just found a bug in the binder - it's not passing the arguments, I'm fixing it now...
Sep 23, 2013 at 2:41 PM
FYI: This binder issue is fixed in the latest dev push.
Sep 23, 2013 at 2:43 PM
Edited Sep 23, 2013 at 2:43 PM
FYI: In the console window, executing "\gctest" will test the garbage collection, which is working as expected. The "IV8NativeObject.Dispose()" method should be called when the object is ready to be disposed (finalized) on both sides.