Returning null from Constructor

Oct 27, 2013 at 10:13 PM
Edited Oct 27, 2013 at 10:14 PM
Hello James,

thanks for creating this incredible useful V8 wrapper!

I know that it's uncommon or not possible to return null or undefined from constructors in other languages, but in JavaScript I feel it is quote common.

When I return
return JSProperty.Empty.Value;
from my constructor
v8.DynamicGlobalObject.MyAwesomeObject = v8.CreateFunctionTemplate().GetFunctionObject(MyAwesomeObject.Ctor);
I get an unhandled NullReferenceException in the line of v8.LoadScript, where v8 is an instance of V8Engine, though i have enclosed this line with a general exception catch block.
                    Handle hResult = null;
                    try
                    {
                        hResult = v8.LoadScript(filename, "Script Executor: " + filename.Substring(filename.LastIndexOf(Path.DirectorySeparatorChar) + 1), true);
                    }
                    catch (Exception e)
                    {
                        MessageBox.Show(String.Format(Main_lang.ErrorScriptEndedException, e.Message), Main_lang.Error, MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);
                    } 
Are there any other ways to signal the client (e.g., JavaScript code) that the function call was not successful, whithout burdening the client code with much boilerplate? What about the NullReferenceException, is this a bug or just me abusing your framework? :-)

Is it possible to raise JavaScript-Exceptions from managed code?

Thanks in advance!
Coordinator
Oct 27, 2013 at 10:47 PM
Hi! Thanks for the kind words. ;)

Not sure why you are passing the constructor as a callable JS function - The CLR constructor expects to be called on new CLR allocated memory for initializing the CLR object (hence the "this" reference). My guess is invoking the function from script would would cause the V8.NET wrapper to attempt to execute the constructor as a static method - because no new .NET object is being passed to it (not that you could anyhow). Bottom line, don't do that. ;) What are you trying to do? I can help point you in the right direction.
Coordinator
Oct 27, 2013 at 11:08 PM
Also, try using the release binaries from the source in the development branch - they are more up to date with a lot of fixes.
Oct 28, 2013 at 12:32 AM
Edited Oct 28, 2013 at 12:34 AM
I like to open a file with JavaScript (within the constructor) and provide further methods to work on this file (within other methods on the created object).
    public class JsFileObject : V8ManagedObject
    {
        private File file;

        public override void Initialize()
        {
            this.AsDynamic.SomeFunctionToWorkOnFile = Engine.CreateFunctionTemplate().GetFunctionObject(SomeFunctionToWorkOnFile);
        }

        public static InternalHandle Ctor(V8Engine engine, bool isCtorCall, InternalHandle _this, params InternalHandle[] args)
        {
            [...]
           
            if(success)
            {
               JsFileObject jsfo = engine.CreateObjectTemplate().CreateObject<JsFileObject>();
               jsfo.file = [...];
               return jsfo;
            }
            else
            {
               return JSProperty.Empty.Value;
            }
        }

        public InternalHandle SomeFunctionToWorkOnFile(V8Engine engine, bool isCtorCall, InternalHandle _this, params InternalHandle[] args)
        {
            // only works if we have a valid file-handle :'-(
        }
    }
This is my V8Engine initialization code:

    try
    {
        v8.WithContextScope = () =>
        {
            v8.DynamicGlobalObject.info = v8.CreateFunctionTemplate().GetFunctionObject(GlobalUtils.info);
            v8.DynamicGlobalObject.warning = v8.CreateFunctionTemplate().GetFunctionObject(GlobalUtils.warning);
            v8.DynamicGlobalObject.alert = v8.CreateFunctionTemplate().GetFunctionObject(GlobalUtils.error);
            v8.DynamicGlobalObject.error = v8.CreateFunctionTemplate().GetFunctionObject(GlobalUtils.error);

            v8.DynamicGlobalObject.JsFileObject = v8.CreateFunctionTemplate().GetFunctionObject(JsFileObject.Ctor);

            Handle hResult = null;

            try
            {
                hResult = v8.LoadScript(filename, "Script Executor: " + filename.Substring(filename.LastIndexOf(Path.DirectorySeparatorChar) + 1), true);
            }
            catch (Exception e)
            {
                MessageBox.Show(String.Format(Main_lang.ErrorScriptEndedException, e.Message), Main_lang.Error, MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);
            } 

            result = hResult.AsString;
        };

    }
    catch (Exception e)
    {
        MessageBox.Show(String.Format(Main_lang.ErrorScriptEndedException, e.Message), Main_lang.Error, MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);
    }
The client code shall look something like this, this is why i need to return null/undefined or an equivalent:
var file= new JsFileObject(); // works
if(file == null) // how do I return null?
{
   alert("File could not be opened, try again / another file.");
}
else
{
   file.SomeFunctionToWorkOnFile();
}
Bottom line, don't do that. ;)
I looked for a suitable way to create a class with constructors and methods, which are all available in JavaScript and though this was the way to go. When not taking the null-return into case, everything works ;-)

Any recommendations for the "null-return" or in general?

Thanks so much!
Coordinator
Oct 28, 2013 at 12:47 AM
Hi PhilRykoff,

First thing, "v8.WithContextScope" no longer exists in the new binaries in the development branch, so you may wish to get the new ones (there are errors in the current beta release on the main page - I'll be updating that soon).

Ok, I see what you are trying to do now, I was off base actually. I would first suggest that if you don't need any specific features of "V8ManagedObject" you should stick with inheriting from "V8NativeObject" (more efficient).

In the JavaScript world (or V8 at least), 'null' and 'undefined' are treated like actual values. To that end, you need to create those as values like anything else, so you instead return {V8Engine}.CreateNullValue(). Return that instead and you should be back on track. ;)
Coordinator
Oct 28, 2013 at 12:51 AM
Edited Oct 28, 2013 at 12:52 AM
Oops, one other thing - what you are trying to do is actually invalid in the JavaScript world, and will NEVER work. JavaScript rules state that if you use "new func()", and return an object, then the object returned will replace the new supplied instance (given by default); but if anything else is returned (such as null), then JavaScript will use the default given object AS IS (ignoring your null, which will be empty in your case). Bottom line, you cannot execute "new" and get "null".
Coordinator
Oct 28, 2013 at 1:10 AM
Edited Oct 28, 2013 at 1:13 AM
Ok, after some thought, what you should probably do instead is one of these:
  1. Create a static method on your 'JsFileObject' class to check if a file exists BEFORE trying to create a file object, or...
  2. Return an instance of your object with an error/success property.
That said, why not just let the system bind your class for you?

public class JsFileObject: IV8NativeObject
    {
        private File file;

        public bool Success;

        public void Initialize()
        {
            // Success = ....;
        }

        void Dispose() { } // warning: may be call on the worker thread (instead of the GC)

        public /*return type*/ SomeFunctionToWorkOnFile(/*optional*/V8Engine engine/*, other params*/)
        {
        }
    }

// ... register the type (this line is actually optional, unless you want more control) ...
{V8Engine}.RegisterType<JsFileObject>(/*see available parameters, if needed, such as ...*/null, true, ScriptMemberSecurity.Locked);

// ... register the type in the global scope to be accessed in script (this will implicitly register the type with default security if the line above is removed) ...
{V8Engine}.GlobalObject.SetProperty(typeof(JsFileObject));
...then simply use your same JS code, but check for the 'Success' property.
Coordinator
Oct 28, 2013 at 1:15 AM
Edited Oct 28, 2013 at 1:19 AM
Also, try registering your "GlobalUtils" type using the same methods above also, then in JS just type "GlobalUtils.yourfunc(...)"; or if you want those functions/methods in the global scope, then just do so in JavaScript instead, or on the CLR side with the registered 'GlobalUtils' object (i.e. v8.GlobalObject.func = v8.GlobalObject.GlobalUtils.func).
Oct 28, 2013 at 2:51 PM
Hello James,

thanks so much for your helpful hints!

Only one question left ;-)

Is it possible to throw Exceptions from managed code?

Thanks
Phil
Coordinator
Oct 28, 2013 at 3:15 PM
Edited Oct 28, 2013 at 3:16 PM
Yes, by calling "v8.CreateError(...)". ;) You can't reliably pass CLR exception errors across managed->native->managed boundaries (and expect it to work well in all platforms). As such, I utilize V8's ability to detect and throw errors within the engine by returning error "values".

The process is like this:
  1. You execute script that eventually calls back into the managed world.
  2. You return a value created from "v8.CreateError()".
  3. The V8 native proxy passes it on to V8, and V8 generates its own "implicit" exception error (internally) that I detect upon return from running the script.
  4. If an error is detected, then a fucntion is called to extract and format the error information, and place it into the returned proxy handle.
  5. The '{Handle}.IsError' property will return true if an error occured, and the handle value will be a string (the error message).
TIP: You can set 'throwExceptionOnError' to true to have the call to "v8.Execute()" throw an error for you. You can also call "{Handle}.ThrowOnError();" as well. ;)
Oct 28, 2013 at 6:24 PM
Hello James,

is CreateError for throwing JavaScript Exceptions from managed code?

I have the same use case as this guy:

http://stackoverflow.com/questions/1366599/throwing-a-javascript-exception-from-c-code-using-google-v8

I wouldn't want the exception to be handled anywhere in the managed code itself, just throw it into JavaScript land, so that the interpretation fails if no JavaScript try ... catch is in place.

Thanks!
Phil
Coordinator
Oct 28, 2013 at 6:43 PM
Yes - when you create an error value, that value is actually a "v8::ThrowException" instance, so you'll be returning that as the response. I haven't tried it myself, but I would fathom it would work as expected within a JS try...catch block. ;) (It is a native V8 engine error after all) Let me know how it goes. :)