Chakra实战:UWP与js交互(C#)

PatrickBeam 9年前

来自: http://hjc.im/uwp-chakra-js-1/

几个月前在翻MSDN时发现Microsoft已经允许在Windows Store Apps(即UWP)里使用Chakra的API了。这意味着大家终于可以光明正大地在app中调用Javascript。//另外UWP允许JIT了所以你自己移植个V8上去其实也行在8.x时代,Chakra是被标记为Desktop only的API,想要在Store apps里使用js,要么整个App使用HTML/js编写,要么使用WebView调用。前者显然不符合主要使用C#/XAML编写UI的前提,后者

麻烦的要死,

不好用。

UWP写起来真舒服

(棒读)

使用Chakra之前需要较为深入地了解Chakra API,COM和JavaScript。

其实不了解直接照抄代码拿着用也没什么不好,就是出了错之后不好排除。

使用C#调用Chakra API

UWP是可以直接使用chakra.dll大部分函数的,除去 JsStartProfiling JsStopProfiling JsEnumerateHeap 和 JsIsEnumeratingHeap 四个。

然而Microsoft并没有在SDK里提供C#/WinRT API,所以需要用P/Invoke进行基本的封装。这里以 Microsoft官方示例 为准。

将上述的Native.cs以及该目录下所有文件都加入项目。

使用C#调用Javascript

主要步骤:

1.使用 JsCreateRuntime 创建一个Javascript运行时(runtime)

JavaScriptRuntime runtime;    Native.ThrowIfError(Native.JsCreateRuntime(JavaScriptRuntimeAttributes.None, null, out runtime));  

2.使用 JsCreateContext 在这个运行时内创建一个上下文(context)

JavaScriptContext context;    Native.ThrowIfError(Native.JsCreateContext(runtime, out context));  

3.使用 JsSetCurrentContext 将该上下文设置到当前线程

Native.ThrowIfError(Native.JsSetCurrentContext(context));  

4.(可选)使用 JsStartDebugging 开启调试

Native.ThrowIfError(Native.JsStartDebugging());  

5.使用 JsRunScript 运行Javascript

JavaScriptValue result;    JavaScriptSourceContext currentSourceContext = JavaScriptSourceContext.FromIntPtr(IntPtr.Zero);    if (Native.JsRunScript(script, currentSourceContext, ""/*如果需要调试,需要在此处指定源码绝对路径*/, out result) != JavaScriptErrorCode.NoError)    {      JavaScriptException exception;      Native.ThrowIfError(Native.JsGetAndClearException(out exception));      //在此处理异常  }  JavaScriptValue stringResult;    UIntPtr stringLength;    Native.ThrowIfError(Native.JsConvertValueToString(result, out stringResult));    Native.ThrowIfError(Native.JsStringToPointer(stringResult, out returnValue, out stringLength));    var ret = Marshal.PtrToStringUni(returnValue);//处理返回值  

6.其它用途(如 JsCallFunction 等)

需要注意的是,如果当前使用的上下文已经被设定到一个线程(第三步),那么该上下文仅能用于这个线程。当这个线程不再需要这个上下文时,需要将其设置为NULL(第七步)。

7.将当前线程的jsrt上下文设置为NULL

Native.ThrowIfError(Native.JsSetCurrentContext(new JavaScriptContext()));  

8.(可选)在其它线程使用这个jsrt上下文(从第三步开始重复)9.使用完成后销毁这个runtime

Native.ThrowIfError(Native.JsDisposeRuntime(runtime));  

类型转换

JavaScript的类型与C#是不同的,而在Chakra API中使用JsValueRef(即C#中封装的JavaScriptValue)来表示一个值。

常用的类型主要有Undefined, Null, Number, String, Boolean, Object, Function, Array等。C#与Javascript交互时,需要将JavaScriptValue与 .NET 的类型互相转换。

JavaScriptValue本身封装了集中简单类型的转换,例如Number与System.Double: JavaScriptValue.FromDouble() 与 JavaScriptValue.ToDouble()

判断JavaScriptValue的类型使用 JsGetValueType 函数

JavaScriptValueType type;    Native.ThrowIfError(Native.JsGetValueType(val, out type));    switch (type)    {      //对特定类型进行处理  }

WinRT类型的转换

任何 WinRT类型 (写C#时可粗略理解为放在winmd里的类型)均继承自IInspectable,可以直接将其对象使用 JsInspectableToObject 转换成JavaScriptValue使用。

同样如果 确定一个JavaScriptValue代表的对象继承自IInspectable ,也可以使用 JsObjectToInspectable 将其转换为System.Object。

数组类型的转换

JavaScript的数组并没有实现IInspectable接口,因此它不能直接使用COM交互,需要手动读取其值并且进行转换。

举例:转换为.NET的List

List<T> JsArrayToList<T>(JavaScriptValue arrayval)  {      var _retList = new List<T>();        JavaScriptValueType type;      Native.ThrowIfError(Native.JsGetValueType(arrayval, out type));      if (type != JavaScriptValueType.Array)          return null;        JavaScriptValue lengthvalue;      Native.ThrowIfError(Native.JsGetProperty(              arrayval,              JavaScriptPropertyId.FromString("length"),              out lengthvalue));        int length;      Native.ThrowIfError(Native.JsNumberToInt(lengthvalue, out length));        for (int i = 0; i < length; i++)      {          JavaScriptValue elem;          Native.ThrowIfError(Native.JsGetIndexedProperty(              arrayval,              JavaScriptValue.FromInt32(i),              out elem));            JavaScriptValueType elemtype;          Native.ThrowIfError(Native.JsGetValueType(elem, out elemtype));            if (elemtype == JavaScriptValueType.Object)          {              object insp;              var err = Native.JsObjectToInspectable(elem, out insp);              if (err == JavaScriptErrorCode.NoError &&                      insp.GetType() == typeof(T))                  _retList.Add((T)insp);          }        }      return _retList;  }

函数调用

首先调用 JsGetGlobalObject 获取当前上下文的全局对象,使用 JsGetProperty 获得函数的对象,再使用 JsCallFunction 调用函数。函数的参数需要全部转换成JavaScriptValue,同时将返回值从JavaScriptValue转换成所需要的类型。

JavaScriptValue CallFunction(string functionName, params JavaScriptValue[] parameters)  {      JavaScriptValue _globalObject;      Native.ThrowIfError(Native.JsGetGlobalObject(out _globalObject));      var functionId = JavaScriptPropertyId.FromString(functionName);      var function = _globalObject.GetProperty(functionId);      return function.CallFunction(parameters);  }

使用Javascript调用WinRT

在UWP中使用Javascript而不是Python等别的脚本语言做扩展,原因之一就是JavaScript调用Windows Runtime Component(WinRT组件)非常方便。无论是对于系统API还是自己创建的WinRT组件,都可以用 JsProjectWinRTNamespace 轻松地映射到Javascript中。映射后的使用方式,与直接使用HTML/js编写UWP时调用WinRT API相同。需要注意的是,有WebHostHiddenAttribute的WinRT类仍然无法被js使用。

如果想将WinRT对象映射为js的全局对象,也可以先使用 JsInspectableToObject 将其转换为JavaScriptValue,使用 JsGetGlobalObject 获得全局对象, JsSetProperty 将WinRT类型的对象设置为全局对象的属性。

调试

集成VS调试方便是Chakra的另一大优点。将VS的C#项目内的调试器类型设置为"Script"(如图),并在代码中调用 JsStartDebugging ,并且指定js源代码文件位置(见上文),在VS中打开相应js文件,附加调试器,即可开始调试脚本。

2016年的第一篇文章,如此长篇大论,药丸 </div>