JSRT概观

JSRT API提供了一种将ChakraCore的嵌入程序,并且可以使用JavaScript的方法。

示例代码

目录:\test\native-tests\test-shared-basic\sample.cpp

#include "stdafx.h"
// Fixed by PR: https://github.com/Microsoft/ChakraCore/pull/2511
// #include <stdint.h>     // To work around issue #2510 temporarily:
//                         // https://github.com/Microsoft/ChakraCore/issues/2510
#include "ChakraCore.h"
#include <string>
#include <iostream>
using namespace std;
int main()
{
    JsRuntimeHandle runtime;
    JsContextRef context;
    JsValueRef result;
    unsigned currentSourceContext = 0;
    // Your script; try replace hello-world with something else
    wstring script = L"(()=>{return \'Hello world!\';})()";
    // Create a runtime.
    JsCreateRuntime(JsRuntimeAttributeNone, nullptr, &runtime);
    // Create an execution context.
    JsCreateContext(runtime, &context);
    // Now set the current execution context.
    JsSetCurrentContext(context);
    // Run the script.
    JsRunScript(script.c_str(), currentSourceContext++, L"", &result);
    // Convert your script result to String in JavaScript; redundant if your script returns a String
    JsValueRef resultJSString;
    JsConvertValueToString(result, &resultJSString);
    // Project script result back to C++.
    const wchar_t *resultWC;
    size_t stringLength;
    JsStringToPointer(resultJSString, &resultWC, &stringLength);
    wstring resultW(resultWC);
    cout << string(resultW.begin(), resultW.end()) << endl;
    system("pause");
    // Dispose runtime
    JsSetCurrentContext(JS_INVALID_REFERENCE);
    JsDisposeRuntime(runtime);
    return 0;
}

概念

了解如何使用JSRT API托管JavaScript引擎需要只知道两个关键概念:runtimesexecution contexts

在JSAPI中,JSRuntime是表示JavaScript引擎实例的顶级对象。程序通常只有一个JSRuntime,即使它有很多线程。JSRuntime是JavaScript对象的universe,一个JSRuntime里的JavaScript无法前往别的JSRuntime。

所有JavaScript代码和大多数JSAPI的调用都在JSContext中运行。JSContext是JSRuntime的子代。一个Context可以运行script。每一个Context都包含全局对象和执行堆栈,异常句柄,错误报告和一些语言选项。

创建运行时runtime

目录:\lib\Jsrt\

JsCreateRuntime

CHAKRA_API JsCreateRuntime(_In_ JsRuntimeAttributes attributes, _In_opt_ JsThreadServiceCallback threadService, _Out_ JsRuntimeHandle *runtimeHandle)
{
    return CreateRuntimeCore(attributes,
        nullptr /*optRecordUri*/, 0 /*optRecordUriCount */, false /*isRecord*/, false /*isReplay*/, false /*isDebug*/,
        UINT_MAX /*optSnapInterval*/, UINT_MAX /*optLogLength*/,
        nullptr, nullptr, nullptr, nullptr, /*TTD IO handlers*/
        threadService, runtimeHandle);
}

参数:

typedef enum _JsRuntimeAttributes
  {
      /// 没有特殊属性。
      JsRuntimeAttributeNone = 0x00000000,
      /// runtime不会对后台线程执行任何工作(例如垃圾回收)。
      JsRuntimeAttributeDisableBackgroundWork = 0x00000001,
      /// runtime需要支持可靠的script interruption,当runtime检查script interruption请求时将增加少量的运行成本
      JsRuntimeAttributeAllowScriptInterrupt = 0x00000002,
      /// Host将会调用<c>Jsidle</c>,因此idle启动。否则runtime将会稍微加强对内存的管理。
      JsRuntimeAttributeEnableIdleProcessing = 0x00000004,
      /// runtime不会生成native code
      JsRuntimeAttributeDisableNativeCodeGeneration = 0x00000008,
      /// 使用<c>eval</c> or <c>function</c>构造函数将会抛出异常.
      JsRuntimeAttributeDisableEval = 0x00000010,
      /// runtime将会开启所有的experimental属性.
      JsRuntimeAttributeEnableExperimentalFeatures = 0x00000020,
      /// 调用JsSetException也会将异常调度到script debugger(如果有的话),使调试器有机会中断异常。
      JsRuntimeAttributeDispatchSetExceptionsToDebugger = 0x00000040,
      ///以下两项 微软的文档里还没有
      ///在OOM上禁用Failfast错误
      JsRuntimeAttributeDisableFatalOnOOM = 0x00000080,
  ///运行时不会分配可执行代码页这也意味着本机代码生成将被关闭请注意,这将破坏WPA等工具中的JavaScript堆栈解码,因为它们依赖于分配唯一的thunks来解释每个函数,同样这些thunk的分配将被禁用
      JsRuntimeAttributeDisableExecutablePageAllocation = 0x00000100,

  } JsRuntimeAttributes;

return:

CreateRuntimeCore

JsErrorCode CreateRuntimeCore(_In_ JsRuntimeAttributes attributes,
    _In_opt_ const char* optTTUri, size_t optTTUriCount, bool isRecord, bool isReplay, bool isDebug,
    _In_ UINT32 snapInterval, _In_ UINT32 snapHistoryLength,
    _In_opt_ TTDOpenResourceStreamCallback openResourceStream, _In_opt_ JsTTDReadBytesFromStreamCallback readBytesFromStream,
    _In_opt_ JsTTDWriteBytesToStreamCallback writeBytesToStream, _In_opt_ JsTTDFlushAndCloseStreamCallback flushAndCloseStream,
    _In_opt_ JsThreadServiceCallback threadService, _Out_ JsRuntimeHandle *runtimeHandle)
{
    VALIDATE_ENTER_CURRENT_THREAD();  //验证当前输入的线程,初始化线程

    PARAM_NOT_NULL(runtimeHandle); //检查当前的句柄地址是否为空
    *runtimeHandle = nullptr;    //将句柄值设置为0


    //检测传入的arributes的值,根据参数所要求的相应属性来设置runtime
    JsErrorCode runtimeResult = GlobalAPIWrapper_NoRecord([&]() -> JsErrorCode {
        const JsRuntimeAttributes JsRuntimeAttributesAll =
            (JsRuntimeAttributes)(
            JsRuntimeAttributeDisableBackgroundWork |
            JsRuntimeAttributeAllowScriptInterrupt |
            JsRuntimeAttributeEnableIdleProcessing |
            JsRuntimeAttributeDisableEval |
            JsRuntimeAttributeDisableNativeCodeGeneration |
            JsRuntimeAttributeDisableExecutablePageAllocation |
            JsRuntimeAttributeEnableExperimentalFeatures |
            JsRuntimeAttributeDispatchSetExceptionsToDebugger |
            JsRuntimeAttributeDisableFatalOnOOM
#ifdef ENABLE_DEBUG_CONFIG_OPTIONS
            | JsRuntimeAttributeSerializeLibraryByteCode
#endif
        );

        Assert((attributes & ~JsRuntimeAttributesAll) == 0);
        if ((attributes & ~JsRuntimeAttributesAll) != 0)
        {
            return JsErrorInvalidArgument;
        }
        CreateFileMapping(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0, 0, nullptr);
        AllocationPolicyManager * policyManager = HeapNew(AllocationPolicyManager, (attributes & JsRuntimeAttributeDisableBackgroundWork) == 0);  //设置管理内存分配的一系列函数
        bool enableExperimentalFeatures = (attributes & JsRuntimeAttributeEnableExperimentalFeatures) != 0;
        ThreadContext * threadContext = HeapNew(ThreadContext, policyManager, threadService, enableExperimentalFeatures);

        if (((attributes & JsRuntimeAttributeDisableBackgroundWork) != 0)
#ifdef ENABLE_DEBUG_CONFIG_OPTIONS
            && !Js::Configuration::Global.flags.ConcurrentRuntime
#endif
            )
        {
            threadContext->OptimizeForManyInstances(true);
#if ENABLE_NATIVE_CODEGEN
            threadContext->EnableBgJit(false);
#endif
        }

        if (!threadContext->IsRentalThreadingEnabledInJSRT()
#ifdef ENABLE_DEBUG_CONFIG_OPTIONS
            || Js::Configuration::Global.flags.DisableRentalThreading
#endif
            )
        {
            threadContext->SetIsThreadBound();
        }

        if (attributes & JsRuntimeAttributeAllowScriptInterrupt)
        {
            threadContext->SetThreadContextFlag(ThreadContextFlagCanDisableExecution);
        }

        if (attributes & JsRuntimeAttributeDisableEval)
        {
            threadContext->SetThreadContextFlag(ThreadContextFlagEvalDisabled);
        }

        if (attributes & JsRuntimeAttributeDisableNativeCodeGeneration)
        {
            threadContext->SetThreadContextFlag(ThreadContextFlagNoJIT);
        }

        if (attributes & JsRuntimeAttributeDisableExecutablePageAllocation)
        {
            threadContext->SetThreadContextFlag(ThreadContextFlagNoJIT);
            threadContext->SetThreadContextFlag(ThreadContextFlagNoDynamicThunks);
        }

        if (attributes & JsRuntimeAttributeDisableFatalOnOOM)
        {
            threadContext->SetThreadContextFlag(ThreadContextFlagDisableFatalOnOOM);
        }

#ifdef ENABLE_DEBUG_CONFIG_OPTIONS
        if (Js::Configuration::Global.flags.PrimeRecycler)
        {
            threadContext->EnsureRecycler()->Prime();
        }
#endif

        bool enableIdle = (attributes & JsRuntimeAttributeEnableIdleProcessing) == JsRuntimeAttributeEnableIdleProcessing;
        bool dispatchExceptions = (attributes & JsRuntimeAttributeDispatchSetExceptionsToDebugger) == JsRuntimeAttributeDispatchSetExceptionsToDebugger;

        JsrtRuntime * runtime = HeapNew(JsrtRuntime, threadContext, enableIdle, dispatchExceptions);                             //创建JsrtRuntime
        threadContext->SetCurrentThreadId(ThreadContext::NoThread);
        *runtimeHandle = runtime->ToHandle();    //获取JsrtRuntime的句柄值
#ifdef ENABLE_DEBUG_CONFIG_OPTIONS
        runtime->SetSerializeByteCodeForLibrary((attributes & JsRuntimeAttributeSerializeLibraryByteCode) != 0);
#endif

        return JsNoError;
    });
    ...

    return runtimeResult;
}

可以看到在创建JsrtRunTime的时候先创建了ThreadContext,而在\lib\Runtime\Base\ThreadContext.cpp里则看到了JsrtRunTime更多的成员属性,像是ThreadContext::GlobalInitialize()等。

TTD(Time Travel Debugging)

是一个工具。可以用来记录正在执行的进程,可以使用TTD对象来查找加载特定代码模块的时间或查找所有异常。

目录:\lib\Common\CommonDfines.h

////////
//Time Travel flags
//Include TTD code in the build when building for Chakra (except NT/Edge) or for debug/test builds
#if defined(ENABLE_SCRIPT_DEBUGGING) && (!defined(NTBUILD) || defined(ENABLE_DEBUG_CONFIG_OPTIONS))
#define ENABLE_TTD 1
#else
#define ENABLE_TTD 0
#endif

创建执行上下文(Execution context)

目录:\lib\Jsrt\jsrt.cpp

JsCreateContext:

CHAKRA_API JsCreateContext(_In_ JsRuntimeHandle runtimeHandle, _Out_ JsContextRef *newContext)
{
    return GlobalAPIWrapper([&](TTDRecorder& _actionEntryPopper) -> JsErrorCode {
        PARAM_NOT_NULL(newContext);
        VALIDATE_INCOMING_RUNTIME_HANDLE(runtimeHandle);

        bool inRecord = false;
        bool activelyRecording = false;
        bool inReplay = false;

#if ENABLE_TTD
        JsrtRuntime * runtime = JsrtRuntime::FromHandle(runtimeHandle);  
        //检测Runtime的句柄是否有效
        // 目录: \lib\Jsrt\JsrtRuntime.h 
        // static JsrtRuntime * FromHandle(JsRuntimeHandle runtimeHandle)
        // {
        //      JsrtRuntime * runtime = static_cast<JsrtRuntime *>(runtimeHandle);
        //      runtime->threadContext->ValidateThreadContext();
        //      return runtime;
        //}

        //目录: \lib\Runtime\Base\ThreadContext.cpp

        //void ThreadContext::ValidateThreadContext()
        //{
        //      #if DBG
        //    // verify the runtime pointer is valid.
        //{
        //      BOOL found = FALSE;
        //      AutoCriticalSection autocs(ThreadContext::GetCriticalSection());
        //      ThreadContext* currentThreadContext = GetThreadContextList();
        //      while (currentThreadContext)
        //      {
        //          if (currentThreadContext == this)
        //          {
        //              return;
        //          }
        //          currentThreadContext = currentThreadContext->Next();
        //      }
        //      AssertMsg(found, "invalid thread context");
        //}
        //       #endif
        //}



        ThreadContext * threadContext = runtime->GetThreadContext();  // 获取当前执行环境runtime   ThreadContext * GetThreadContext() { return this->threadContext; }
        if(threadContext->IsRuntimeInTTDMode() && threadContext->TTDContext->GetActiveScriptContext() != nullptr)   
        {
            Js::ScriptContext* currentCtx = threadContext->TTDContext->GetActiveScriptContext();//创建ScriptContext
            inRecord = currentCtx->IsTTDRecordModeEnabled();  
            activelyRecording = currentCtx->ShouldPerformRecordAction();
            inReplay = currentCtx->IsTTDReplayModeEnabled();
        }
#endif

        return CreateContextCore(runtimeHandle, _actionEntryPopper, inRecord, activelyRecording, inReplay, newContext);
    });
}

目录:\lib\Jsrt\Jsrtc.pp

CreateContextCore:

//A create context function that we can funnel to for regular and record or debug aware creation
JsErrorCode CreateContextCore(_In_ JsRuntimeHandle runtimeHandle, _In_ TTDRecorder& _actionEntryPopper, _In_ bool inRecordMode, _In_ bool activelyRecording, _In_ bool inReplayMode, _Out_ JsContextRef *newContext)
{
    JsrtRuntime * runtime = JsrtRuntime::FromHandle(runtimeHandle);
    ThreadContext * threadContext = runtime->GetThreadContext();  //获取ThreadContext

    //对ThreadContext的属性进行检测
    if(threadContext->GetRecycler() && threadContext->GetRecycler()->IsHeapEnumInProgress())
    {
        return JsErrorHeapEnumInProgress;
    }
    else if(threadContext->IsInThreadServiceCallback())
    {
        return JsErrorInThreadServiceCallback;
    }

    ThreadContextScope scope(threadContext);  //获取当前runtime

    if(!scope.IsValid())
    {
        return JsErrorWrongThread;
    }
    ...

    JsrtContext * context = JsrtContext::New(runtime);

#if ENABLE_TTD
    if(inRecordMode | inReplayMode)
    {
        Js::ScriptContext* scriptContext = context->GetScriptContext();  //创建ScriptContext
        HostScriptContextCallbackFunctor callbackFunctor((FinalizableObject*)context, (void*)runtime, &OnScriptLoad_TTDCallback, &OnBPRegister_TTDCallback, &OnBPDelete_TTDCallback, &OnBPClearDocument_TTDCallback);
    ...
    *newContext = (JsContextRef)context;
    return JsNoError;
}

可以看到在创建JsrtContext的时候,都回创建一个叫做ScriptContext的结构:

目录: \lib\Runtime\Base\ScriptContext.cpp

可以找到类似于SetGlobalObject(),GetGlobalObject()等成员函数,上面代码的调试模式也是通过ScriptContext设置,说明ScriptContext是JsrtContext的具体实现。

设置当前的执行上下文

目录:\lib\Jsrt\Jsrt.cpp

JsSetCurrentContext:

设置当前线程正确的script context

CHAKRA_API JsSetCurrentContext(_In_ JsContextRef newContext)
{
    VALIDATE_ENTER_CURRENT_THREAD(); //检查当前输入的线程,xia

    return GlobalAPIWrapper([&] (TTDRecorder& _actionEntryPopper) -> JsErrorCode {
        JsrtContext *currentContext = JsrtContext::GetCurrent();  //获取当前JsrtContext
        Recycler* recycler = currentContext != nullptr ? currentContext->GetScriptContext()->GetRecycler() : nullptr;   //返回allocators的类型

#if ENABLE_TTD
        Js::ScriptContext* newScriptContext = newContext != nullptr ? static_cast<JsrtContext*>(newContext)->GetScriptContext() : nullptr;
        Js::ScriptContext* oldScriptContext = currentContext != nullptr ? static_cast<JsrtContext*>(currentContext)->GetScriptContext() : nullptr;

        if(newScriptContext == nullptr)
        {
            if(oldScriptContext == nullptr)
            {
                ; //如果新的ScriptContext、旧的ScriptContext都为空则不需要操作
            }
            else
            {
                if(oldScriptContext->IsTTDRecordModeEnabled()) //检查TTD系统是否启动
                {
                    //already know newScriptContext != oldScriptContext so don't check again
                    if(oldScriptContext->ShouldPerformRecordAction())  //用来检查此代码是否在记录中,并且此代码是否是用户的应用程序执行的
                    {
                        oldScriptContext->GetThreadContext()->TTDLog->RecordJsRTSetCurrentContext(_actionEntryPopper, nullptr);
                    }

                    oldScriptContext->GetThreadContext()->TTDContext->SetActiveScriptContext(nullptr);
                }
            }
        }
        else
        {
            if(newScriptContext->IsTTDRecordModeEnabled())
            {
                if(newScriptContext != oldScriptContext && newScriptContext->ShouldPerformRecordAction())
                {
                    newScriptContext->GetThreadContext()->TTDLog->RecordJsRTSetCurrentContext(_actionEntryPopper, newScriptContext->GetGlobalObject());
                }

                newScriptContext->GetThreadContext()->TTDContext->SetActiveScriptContext(newScriptContext);    //如果通过了各种调试设置、并且newScriptContext为空的时候,将newScriptContext设置为活跃状态
            }
        }
#endif
        //各种检测报错抛出异常
        if (currentContext && recycler->IsHeapEnumInProgress())
        {  //A heap enumeration is currently underway in the script context.
            return JsErrorHeapEnumInProgress;
        }
        else if (currentContext && currentContext->GetRuntime()->GetThreadContext()->IsInThreadServiceCallback())
        {  //目前正在进行线程服务回调。
            return JsErrorInThreadServiceCallback;
        }

        if (!JsrtContext::TrySetCurrent((JsrtContext *)newContext))
        {  //在错误的线程上调用了Host API。
            return JsErrorWrongThread;
        }

        return JsNoError;
    });
}

执行语句

目录:\lib\Jsrt\Jsrt.cpp

JsRunScript:

执行一个Script

CHAKRA_API JsRunScript(_In_z_ const WCHAR * script, _In_ JsSourceContext sourceContext,
    _In_z_ const WCHAR *sourceUrl, _Out_ JsValueRef * result)
{
    return RunScriptCore(script, sourceContext, sourceUrl, false,
        JsParseScriptAttributeNone, false /*isModule*/, result);
}

参数:

实际上执行真正的RunScriptCore()还要经过一层:

JsErrorCode RunScriptCore(const char *script, JsSourceContext sourceContext,
    const char *sourceUrl, bool parseOnly, JsParseScriptAttributes parseAttributes,
    bool isSourceModule, JsValueRef *result)     //parseOnly == true;
{
    utf8::NarrowToWide url((LPCSTR)sourceUrl); //字符长度检查
    if (!url)
    {
        return JsErrorOutOfMemory;
    }

    return RunScriptCore(nullptr, reinterpret_cast<const byte*>(script), strlen(script),
        LoadScriptFlag_Utf8Source, sourceContext, url, parseOnly, parseAttributes,
        isSourceModule, result);
}
JsErrorCode RunScriptCore(const WCHAR *script, JsSourceContext sourceContext,
    const WCHAR *sourceUrl, bool parseOnly, JsParseScriptAttributes parseAttributes,
    bool isSourceModule, JsValueRef *result)
{  //NULL
    return RunScriptCore(nullptr, reinterpret_cast<const byte*>(script),
        wcslen(script) * sizeof(WCHAR),
        LoadScriptFlag_None, sourceContext, sourceUrl, parseOnly,
        parseAttributes, isSourceModule, result);
}

接下来是真正执行内容的RunScriptCore:

JsErrorCode RunScriptCore(JsValueRef scriptSource, const byte *script, size_t cb,
    LoadScriptFlag loadScriptFlag, JsSourceContext sourceContext,
    const WCHAR *sourceUrl, bool parseOnly, JsParseScriptAttributes parseAttributes,
    bool isSourceModule, JsValueRef *result)
{
    Js::JavascriptFunction *scriptFunction;
    CompileScriptException se;

    JsErrorCode errorCode = ContextAPINoScriptWrapper([&](Js::ScriptContext * scriptContext, TTDRecorder& _actionEntryPopper) -> JsErrorCode {
        PARAM_NOT_NULL(script);
        PARAM_NOT_NULL(sourceUrl);

        SourceContextInfo * sourceContextInfo = scriptContext->GetSourceContextInfo(sourceContext, nullptr);

        if (sourceContextInfo == nullptr)
        {
            sourceContextInfo = scriptContext->CreateSourceContextInfo(sourceContext, sourceUrl, wcslen(sourceUrl), nullptr);
        }

        const int chsize = (loadScriptFlag & LoadScriptFlag_Utf8Source) ?
                            sizeof(utf8char_t) : sizeof(WCHAR);

        //create for every source buffer passed by host
        SRCINFO si = {  
            /* sourceContextInfo   */ sourceContextInfo,
            /* dlnHost             */ 0,
            /* ulColumnHost        */ 0,
            /* lnMinHost           */ 0,
            /* ichMinHost          */ 0,
            /* ichLimHost          */ static_cast<ULONG>(cb / chsize), // OK to truncate since this is used to limit sourceText in debugDocument/compilation errors.
            /* ulCharOffset        */ 0,
            /* mod                 */ kmodGlobal,
            /* grfsi               */<
        

Hacking more

...