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

几个月前在翻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 JsEnumerateHeapJsIsEnumeratingHeap 四个。
然而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文件,附加调试器,即可开始调试脚本。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注