Chakra實戰:UWP與js交互(C#)

PatrickBeam 8年前發布 | 51K 次閱讀 C# .NET開發 JavaScript

來自: 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年的第一篇文章,如此長篇大論,藥丸
 本文由用戶 PatrickBeam 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!