Fix for passing JsObjects to .NET

Dec 29, 2009 at 5:02 PM
Edited Dec 29, 2009 at 5:38 PM

First of all I wanna say that Jint is awesome. I've been messing around with it for two days now, I've had some trouble with passing JsObjects, that is objects created in javascript like: var baz = {foo: "bar"}; to .NET, I seemed to always be getting null.

So I looked around in the source, found line #1483, that looks like this: clrParameters[j] = parameters[j].Value; I then went on to check what .Value actually returns from JsObject, turns out value looks like this:

 

        public override object Value
        {
            get { return value; }
            set { this.value = value; }
        }

And this.value is set in the constructor, and never changed. Since I really needed to access true javascript objects from .NET, I though of two possible solutions:

1) Change the implementation of Value on JsObject, however since I'm not familiar with the source code very much and it seems like quite a lot depend on the value of Value, this didn't seem like a good option.

2) Change the part of public void Visit(MethodCall methodCall) that assigns parameters to calls to CLR methods, this did seem reasonable, so I started. My first idea was to convert the JsObject into a dictionary, but that was one slipper slope. The solution I ended up with was to just simply pass the entire JsObject into the CLR, by modifing line #1483 to look like this instead:

 

        if (parameters[j].Class == JsObject.TYPEOF)
        {
            clrParameters[j] = parameters[j];
        }
        else
        {
            clrParameters[j] = parameters[j].Value;
        }
 

So that I get the entire JsObject available to me in the CLR, I don't know if this is a good or bad idea really - but it was the most easily implemented one. And it seems to work fine. Did I miss something somewhere and this is already possible to do? 

Also Sebastien, thanks once again for an awesome library! Very clean and easy to follow code!

Dec 31, 2009 at 7:59 PM

I was running into similar issues trying to pass JsFunctions back to .NET code (for callbacks and such.)  I eventually stumbled upon the exact same code and ended up making the following change:

if (parameters[j] is JsFunction)
{
    clrParameters[j] = parameters[j];
}
else
{
    clrParameters[j] = parameters[j].Value;
}

It seems that my changes and yours should probaby be merged in Jint to make both of these scenarios work. 

Feb 17, 2010 at 7:11 PM

HHHAAGGG!!!!!

People! line #1483 of what file????

Feb 22, 2010 at 4:56 PM
Edited Feb 22, 2010 at 4:57 PM

Hi alltogether,

I am not sure if my problem is the same (or quite similar) as already mentioned above therefore I add it at this place. 

The question is how to implement  a function (being callable in JS) that returns an object (let's say from class A  =  errorMessage)   to the JS environment.

 

I can create an object of type  A  in JavaScript, but when I want to assign a value, the previously defined values are overwritten  by  "nothing" (?) .... hhmmm... 

Any idea how to handle that? 

(Can it be that the nightly build already covers that problem?)

Many thanks in advance!  Claus

 

------------------------------------------------------------------------
public class EMsg
  {
      public int intVal
      { get; set; }
      public string comment
      { get; set; }


      public EMsg()
      {
          intVal = 99;
          comment = "not yet set";
      }
  }


---------------------------------------------------------------------------------------

    ...

    je.SetFunction("getResultObject", new Jint.Delegates.Func<EMsg>(sct.getArbitraryResultObject));


    ....

                var errMsg= new EMsg();

    // here the values of the EMsg object are ok:    intVal = 99 / comment = "not yet set"

    errMsg = getResultObject();

    //   now intVal = ...  (empty)   comment = (empty)

    printS( errMsg.comment );   //  -->> does not print out anything

---------------------------------------------------------------------------------------

 

Feb 23, 2010 at 12:11 AM

I tried different approaches until I settle for these changes. 

Replace the function ConvertParameter in file jsClr.cs taken from the svn repository for this:

 

        public static object ConvertParameter(JsInstance parameter)
        {
            if ( parameter.Class != JsFunction.TYPEOF && parameter.Class != JsArray.TYPEOF && parameter.Class != JsObject.TYPEOF ) {
                    return parameter.Value;
            }
            else if (parameter == JsNull.Instance)
            {
                return null;
            }
            else
            {
                JsFunction constructor = ((JsDictionaryObject)parameter)["constructor"] as JsFunction;
                if ( constructor == null ) return parameter;
                
                switch (constructor.Name)
                {
                    case "Date":
                        return JsDateConstructor.CreateDateTime(parameter.ToNumber());
                    case "String":
                    case "RegExp":
                    case "Number":
                        return parameter.Value;
                    case "Array":
                        object[ ] array = new object[ ( ( JsObject )parameter ).Length ];
                        foreach ( KeyValuePair<string, JsInstance> key in ( JsObject )parameter ) {
                            int index = 0;
                            if ( int.TryParse( key.Key, out index ) ) {
                                array[ index ] = ConvertParameters( key.Value )[ 0 ];
                            }
                        }
                        return array;
                    case "Object":
                        if ( parameter.Class == JsFunction.TYPEOF ) return parameter;
                        Dictionary<string, object> dic = new Dictionary<string, object>( );
                        foreach ( KeyValuePair<string, JsInstance> key in ( JsObject )parameter ) {
                            dic.Add( key.Key, ConvertParameter( key.Value ) );
                        }
                        return dic;
                    default:
                        return parameter;
                }
            }
        }

 

 

        public static object ConvertParameter(JsInstance parameter)
        {
            if ( parameter.Class != JsFunction.TYPEOF && parameter.Class != JsArray.TYPEOF && parameter.Class != JsObject.TYPEOF ) {
                    return parameter.Value;
            }
            else if (parameter == JsNull.Instance)
            {
                return null;
            }
            else
            {
                JsFunction constructor = ((JsDictionaryObject)parameter)["constructor"] as JsFunction;
                if ( constructor == null ) return parameter;
                
                switch (constructor.Name)
                {
                    case "Date":
                        return JsDateConstructor.CreateDateTime(parameter.ToNumber());
                    case "String":
                    case "RegExp":
                    case "Number":
                        return parameter.Value;
                    case "Array":
                        object[ ] array = new object[ ( ( JsObject )parameter ).Length ];
                        foreach ( KeyValuePair<string, JsInstance> key in ( JsObject )parameter ) {
                            int index = 0;
                            if ( int.TryParse( key.Key, out index ) ) {
                                array[ index ] = ConvertParameters( key.Value )[ 0 ];
                            }
                        }
                        return array;
                    case "Object":

                        if ( parameter.Class == JsFunction.TYPEOF ) return parameter;

                        Dictionary<string, object> dic = new Dictionary<string, object>( );
                        foreach ( KeyValuePair<string, JsInstance> key in ( JsObject )parameter ) {
                            dic.Add( key.Key, ConvertParameter( key.Value ) );
                        }
                        return dic;
                    default:
                        return parameter;
                }
            }

        }

This works for me no problems, and I don't quite understand why using a dictionary is a slipper slope. If it is only used for objects from js to clr, it works fine for me.
This modification has nothing to do with passing a clr object into js, So it won't help ClausE.

 

 

Coordinator
Feb 23, 2010 at 11:01 AM

Hi,

it was not exaclty the same issue, but you are right, there was one thing missing. I think I have just corrected it. Can you provide me with a sample of what you are trying to achieve in order I can add it to unit tests ?

 

Thanks,

Coordinator
Feb 23, 2010 at 11:09 AM

Hi ClausE,

It depends on what your function sct.getArbitraryResultObject does.

Feb 23, 2010 at 12:17 PM

 

Hi neonp,

There was 2 things missing: In the first if, it was not testing for the object class, hence returning parameter.value (which is null) without reaching the switch statement. And in the switch statement, the code on SVN was giving the same treatment to arrays and to objects. Arrays elements are accessible by index, but object elements are accessed by the name of the property, so for objects, the int.TryParse will allways fail.

The test case is giving the next JavaScript:

 

return {
    a: 10,
    b: [
            {
                d: 5,
                e: 'another test'
            },
            {
                f: 'Get this value!'
            }
        ],
    c: 'test string'
};

How can the execution environment know:

  • The first object on the array does not have a property f
  • get the value of f, a and c
  • b is an array (not an object) and it has 2 elements

Thank you again for all this work, it really makes a difference.

 

Feb 23, 2010 at 12:41 PM
Edited Feb 25, 2010 at 8:16 AM

Hello Nicolas,
Hi all

thanks for the support! I appreciate that very much.

Actually, I tried to keep the discussion as general as possible, thus I used the "EMsg" class.


My general problem is how to get return values back from the registered functions if the return values are NOT primitives but of an own "complex" type.

The method getArbitraryResultObject just creates such a "complex" object ( an instance of the EMsg class) and returns it.

Actually I try to return something like an error code (int) together with a short explanation (string)) as an ErrorMessage (EMsg) from that method.
But this is arbitrary.
It is about how to return an own object (here of EMsg type) from an own function (here: getArbitraryEMsgObject() ).

// -----------------------------
public EMsg getArbitraryEMsgObject()
{
//--- create a new object of the class to be returned
EMsg res = new EMsg();

//--- insert some values ....
res.intValue = 99;
res.comment = "TEST TEST in TopConCLL in simple_ResultTest()";

//-- and now return the object as return value
return res;
}

// -----------------------------

In the documentation
Documentation / Exposing Javascript functions to the CLR

the mentioned example returns a LIST.

list = list.ConvertAll{System.Double}( function (x) { return x * x; });


Am I wrong?
I am not sure if I made a bigger mistake.... ?

Thanks in advance.
Claus