This project is read-only.

Adding custom type mapping from JS to CLR

Mar 21, 2014 at 4:35 PM
Edited Mar 21, 2014 at 4:37 PM
Hi,

How to map a call from JavaScript:
addTickFiredEventHandler(function(args) { ... });
to the following C# function:
void AddTickFiredEventHandler(Action<TickFiredEventArgs> handler) { ... }
automatically?

Like I've described previously, I must avoid using V8-specific types, such as InternalHandle, in the user code. Is there some way to explain V8.Net how to convert an InternalHandle object that is a function to the Action<TickFiredEventArgs> object? Sometime like a type binder, but in the other direction?
Mar 21, 2014 at 4:55 PM
Edited Mar 21, 2014 at 5:02 PM
Looked at the source code. As far as I can see there are no binders for the conversion from JS to C# types. I guess I'll have to write a custom object converter (as discussed previously). Previously I was using
engine.GlobalObject.SetProperty("someName", clrObject)
and hoped that it will wrap all members in the clrObject automatically.
Mar 21, 2014 at 11:04 PM
There is only binding of public class members to JavaScript only (and for good reason, as the other direction is bad for security reasons). You can do in V8 only what you allow, so you have to register the objects or methods as needed. The binder will scan an object type and setup a binding suited for that type the best it can, but generics can be tricky to deal with. Just remember that V8 will see everything as objects, so your "Action<>" parameter is nothing more than an object (delegate object) that contains a reference to a method, and is NOT a method in and of itself. The question is whether or not the function value passed in will CONVERT to an "Action<>" delegate OBJECT type (requires a call-back binding of some sort), and I'm not sure it will right now. I'll have to look into this further.

That said, why not just accept a "Handle handler" parameter (this is how callbacks are designed to work anyhow - not using CLR specific parameter types) and then use "handler.call()" to call back the function? This is the only work around I can think of currently. The original design of V8.NET is low level, and some binding was "tacked on" later. Natively, V8.Net expects users to create methods that accept V8 handlers for parameters (except the primitive types of course). If you need to support a CLR typed parameter, you can just make a "bridge" method that accepts "AddTickFiredEventHandler(Handle handler)" as the parameter, wraps the handle in a lambda action expression, and then passes that to "AddTickFiredEventHandler(Action<TickFiredEventArgs> handler)" instead.

Hope that helps! :)
Mar 24, 2014 at 9:13 PM
My issue as always related to the fact that I must not introduce V8.Net-specific classes anywhere beyond the scripting component. Otherwise, this will contaminate the code of other components which will also have to reference V8.Net, while they should stay independent. What I am try to do is to create generic interfaces which can wrap arbitrary scripting library, not just V8.Net. But, plainly, V8.Net was not designed to be used this way, so I have to use various workarounds. In particular for this case, I have implemented the addTickFiredEventHandler in JavaScript:
eventLoop = arguments[0];
eventLoop._handlers = [];

eventLoop._triggerHandlers = function (elapsedMs) {
    for (var index in this._handlers)
        this._handlers[index].call(null, elapsedMs);
}

eventLoop.addTickFiredHandler = function (handler) {
    this._handlers.push(handler);
}

eventLoop.removeTickFiredHandler = function (handler) {
    var handlerIndex = this._handlers.indexOf(handler);
    if (handlerIndex != -1)
        this._handlers.splice(handlerIndex, 1);
}
Then I just invoke the _triggerHandlers from the C# code regardless whether any handlers are actually registered or not.
Mar 24, 2014 at 10:50 PM
Have you considered making your own conversion to the needed type? You can simply create a utility method that will accept a function, and return a bound Action<TickFiredEventArgs> object.
/* In JS */
var action = createAction(function(){ ... }); // Returns a bound Action object.
addTickFiredEventHandler(action);
It will accept the bound object and pass along its bound CLR instance.
Mar 24, 2014 at 11:25 PM
This is an option too, but JS-based option was good enough for me. Actually I am already experimenting with creating custom V8ManagedObject wrapper because I also need to automatically support string-indexed properties. There is a class that I wrote today:
class V8NetObjectWrapper : ObjectBinder
{
    public static V8NetObjectWrapper Wrap(V8Engine engine, object obj)
    {
        var objType = obj.GetType();
        var typeBinder = engine.RegisterType(objType, null, true, ScriptMemberSecurity.Locked);
        return typeBinder.CreateObject<V8NetObjectWrapper, object>(obj);
    }

    public override InternalHandle NamedPropertyGetter(ref string propertyName)
    {
        if (!searchedStringIndexers)
            FindStringIndexers();

        InternalHandle handle = base.NamedPropertyGetter(ref propertyName);

        if (handle.IsEmpty && namedIndexerGetter != null)
        {
            object value = namedIndexerGetter.Invoke(Object, new object[] { propertyName });
            if (value != null && value.GetType().IsClass)
                value = V8NetObjectWrapper.Wrap(Engine, value);
            var v = Engine.CreateValue(value, true, ScriptMemberSecurity.Locked);
            return v;
        }

        return handle;
    }

    public override InternalHandle NamedPropertySetter(ref string propertyName, InternalHandle value, V8PropertyAttributes attributes = V8PropertyAttributes.Undefined)
    {
        if (!searchedStringIndexers)
            FindStringIndexers();

        InternalHandle handle = base.NamedPropertySetter(ref propertyName, value, attributes);

        if (handle.IsEmpty && namedIndexerSetter != null)
        {
            object convertedValue = new ArgInfo(value, null, namedPropertyType).ValueOrDefault;
            namedIndexerSetter.Invoke(Object, new object[] { propertyName, convertedValue });
            return value;
        }

        return handle;
    }

    private void FindStringIndexers()
    {
        // Find out how default properties are called in this object's type. By default they are called "Item".
        string indexerName = "Item";
        object[] defaultMemberAttributes = ObjectType.GetCustomAttributes(typeof(DefaultMemberAttribute), true);
        if (defaultMemberAttributes.Length > 0)
        {
            var attr = defaultMemberAttributes[defaultMemberAttributes.Length - 1] as DefaultMemberAttribute;
            indexerName = attr.MemberName;
        }

        // Find out if there is an indexed property with a single string argument and check if it has getter and 
        // setter.
        var stringIndexer = ObjectType.GetProperty(indexerName, new Type[] { typeof(string) });
        if (stringIndexer != null)
        {
            namedPropertyType = stringIndexer.PropertyType;
            if (stringIndexer.CanRead)
                namedIndexerGetter = stringIndexer.GetGetMethod();
            if (stringIndexer.CanWrite)
                namedIndexerSetter = stringIndexer.GetSetMethod();
        }

        searchedStringIndexers = true;
    }

    bool searchedStringIndexers = false;
    Type namedPropertyType = null;
    MethodInfo namedIndexerGetter = null;
    MethodInfo namedIndexerSetter = null;
}
Maybe you can reintegrate some of the code into the ObjectBinder. If I will use Action<SomeType> more often, I may implement an automatic mapping into this class too.
Mar 25, 2014 at 12:50 AM
Edited Mar 25, 2014 at 12:52 AM
I'll look into creating an automatic binding from function handles to delegate types.

I'll keep track of it here: https://v8dotnet.codeplex.com/workitem/1298