7.6 CLR/Javascript(SpiderMonkey)

Limax提供一个clrjs项目,粘合C#与Javascript,支持C#与Javascript代码之间的互操作,用于实现C#脚本模式框架下的JavaScriptHandle。这一章介绍clrjs提供的功能。


  • 7.6.3 类型映射

    C#,Javascript两种语言的类型系统没有兼容性可言,所以类型映射无法用一张明确的表格进行描述。分3种规则讨论。

    • 规则1:C#向Javascript传递(C#通过eval向Javascript传递;Javascript访问C#时,构造返回对象,方法返回值,委托返回值,读取字段,读取属性)

      这种情况下,C#类型信息可以明确获知。

      • null,解释为javascript的null

      • DBNull.Value,解释为javascript的undefined

      • bool以及装箱类型Boolean,解释为javascript的boolean类型

      • byte C#向Javascript传递,sbyte,short,ushort,int,以及装箱类型Byte,SByte,Int16,UInt16,Int32解释为javascript的int类型。

      • float,double,decimal以及装箱类型Single,Double,Decimal,解释为javascript的double类型。

      • uint以及装箱类型UInt32,如果值小于0x80000000解释为javascript的int类型,否则解释为javascript的double类型。

      • char,string以及装箱类型Char,String解释为javascript的string。需要注意,字符串传递给javascript之后,已经变成javascript字符串,不可能调用C#的字符串方法进行访问。如果执行js.eval("print(<0>.Length)", "abc");, 将输出undefined,改为js.eval("print(<0>.length)", "abc");, 才能获得正确结果3。语法上比较怪异, 小心。

      • JsObject,JsArray,JsFunction之外类型的C#对象,在javascript中创建一个特殊对象与之关联,通过SetPrivate引用该C#对象,防止在C#虚拟机中被GC,特别的,如果类型为委托类型,则获取委托对象的Invoke方法作为实际需要关联的对象,这样委托对象就能被正确调用了。

      • JsObject,JsArray,JsFunction是之前返回给C#的类型,这时取回之前关联的Javascript对象。


    • 规则2:Javascript向C#返回值(eval的返回)

      这种情况下,只能根据Javascript当前已知的信息返回, C#获取这样的返回值以后, 必须严格检查类型然后使用, 不小心就可能导致类型转换异常, System.InvalidCastException。

      • null,返回null

      • undefined,返回DBNull.Value

      • Boolean,返回Boolean

      • string,返回String

      • int,返回Int32

      • double,返回Double

      • 通过SetPrivate引用了C#对象的特殊javascript对象,返回引用的C#对象。

      • 其它javascript对象,创建一个JsObject对象引用它,防止在javascript虚拟机中被GC,该JsObject被返回。

      • 除数组和函数外的Javascript对象使用JsObject返回,JsObject实现了IDictionary接口,通过该接口可以访问对应javascript对象的属性。JsObject的按照Javascript规范访问,而不是IDictionary规范。例如,IDictionary重复Add抛异常,Javascript重复Add执行替换;JsObject上Remove属性,跟在属性上Add一个DBNull.Value效果完全一样。

      • Javascript数组使用JsArray返回,JsArray继承自JsObject,实现了IList接口,通过该接口可以访问对应的javascript数组的成员。JsArray按照Javascript规范访问,而不是IList规范,简单说来,不会生成System.ArgumentOutOfRangeException异常,即不存在数组越界的问题。JsArray实现的IList接口与继承自JsObject对象中的ICollection接口,存在属性和方法同名的问题,使用了new规则override,所以按照JsArray方式与按照JsObject方式可能导致不同结果,可以回顾之前的例子。

      • Javascript函数使用,使用JsFunction委托返回,该委托包装了一个继承自JsObject的对象操作该Javascript函数。

      • 注意, (int)js.eval("3.14"); 这样的转换必然导致System.InvalidCastException, 原因在于返回的javascript值是double类型, 返回类型为System.Double, 这样的装箱类型无法像原生类型double一样直接转换为int, 正确的写法应该是(int)(double)js.eval("3.14");一旦出现类型转换异常, 可以在返回对象上调用getType()方法检查实际类型, 判断是否符合需求, 进而实现正确的转换。

      • Javascript规范不断更新中, 为对象属性规定了很多特定访问控制属性, 比如设置为readonly的属性, 修改不可能成功。例如, 执行代码, js.eval("print=100; print('readonly')");将输出readonly。这种情况需要小心, 不过一般来说,不是系统内建的一些对象,方法,类型,不会存在这类问题。


    • 规则3:Javacript向C#传递(构造参数,方法调用参数,委托调用参数,字段设置,属性设置)

      首先按照规则2,转换为C#对象,然后根据期待的参数类型在C#中进行转换,细节参见章节《脚本语言访问C#规范》


  • 7.6.4 异常规范

    • C#与Javascript均支持异常框架,两种语言都可以进行异常捕捉。

    • C#调用Javascript,Javascript中抛出了异常,没有捕捉,异常抛出到C#中。

    • Javascript调用C#,C#中抛出了异常,没有捕捉,异常抛出到Javascript中。

    • Javascript调用C#,C#中抛出了异常,没有捕捉,异常抛出到Javascript中。

    • 异常每进入一次C#,则用limax.script.Js.ScriptException包装一次。

    • ScriptException将Javascript异常串作为异常message,C#异常作为内部异常,进行包装。

    • Javascript异常串由文件位置信息,异常message和StackTrace共同拼接而成。对于Javascript而言抛出的Error才能包含位置信息,和StackTrace。

    • Javascript中,捕获的异常对象上toString,可以获得完整信息,包括来自C#的异常信息。


  • 7.6.5 线程安全

    • SpiderMonkey库不允许虚拟机跨线程运行。javascript操作对象上执行的任何操作, 包括eval, 以及eval返回的JsObject, JsArray, JsFunction上的操作, 必须在创建javascript操作对象的线程中执行, 否则抛出异常limax.script.Js.ThreadContextException。


  • 7.6.6 注意事项

    • 1. C#中的泛型对象不可往javascript虚拟机中传递, 否则将抛出异常。原因在于, System::Runtime::InteropServices::Marshal::GetNativeVariantForObject, System::Runtime::InteropServices::Marshal::GetObjectForNativeVariant这两个关键方法的泛型版本从.NET4.5.1之后才开始提供,为了兼容较老的.NET版本,该项目使用.NET4.0开发(.NET3.5应该也可以使用)。

    • 2. 可以创建多个Javascript操作对象,从一个Javascript操作对象获取的JsObject,JsArray,JsFunction不能够传入另一个Javascript操作对象,这个显而易见;其它的C#对象传入多个Javascript操作对象,没有限制。多个Javascript操作对象,如果确有数据交换需求,可以通过JSON实现,Javascript本身即提供了完整支持,限制是对象中不能存在环。


上一页 下一页