welsonjs/WelsonJS.Toolkit/WelsonJS.Launcher/JsCore.cs
Namhyeon, Go 130a6fd767 Add ChakraCore integration and native bootstrap logic
Introduces JsCore for ChakraCore P/Invoke, JsSerializer for JSON utilities via JS, and NativeBootstrap for robust native DLL loading. Updates Program.cs to initialize native dependencies at startup and registers new source files in the project file.
2025-09-26 17:04:14 +09:00

172 lines
6.7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// JsCore.cs
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2025 Catswords OSS and WelsonJS Contributors
// https://github.com/gnh1201/welsonjs
//
using System;
using System.Runtime.InteropServices;
namespace WelsonJS.Launcher
{
public sealed class JsCore : IDisposable
{
private IntPtr _runtime = IntPtr.Zero;
private IntPtr _context = IntPtr.Zero;
private bool _disposed;
public JsCore()
{
Check(JsCreateRuntime(0, IntPtr.Zero, out _runtime), nameof(JsCreateRuntime));
Check(JsCreateContext(_runtime, out _context), nameof(JsCreateContext));
Check(JsSetCurrentContext(_context), nameof(JsSetCurrentContext));
}
/// <summary>
/// Evaluates JavaScript and returns the result converted to string (via JsConvertValueToString).
/// </summary>
public string EvaluateToString(string script, string sourceUrl = "repl")
{
if (_disposed) throw new ObjectDisposedException(nameof(JsCore));
if (script is null) throw new ArgumentNullException(nameof(script));
Check(JsRunScript(script, IntPtr.Zero, sourceUrl, out var result), nameof(JsRunScript));
// Convert result -> JsString
Check(JsConvertValueToString(result, out var jsString), nameof(JsConvertValueToString));
// Extract pointer/length (UTF-16) and marshal to managed string
Check(JsStringToPointer(jsString, out var p, out var len), nameof(JsStringToPointer));
return Marshal.PtrToStringUni(p, checked((int)len));
}
/// <summary>
/// Evaluates JavaScript for side effects; discards the result.
/// </summary>
public void Execute(string script, string sourceUrl = "repl")
{
_ = EvaluateToString(script, sourceUrl);
}
private static void Check(JsErrorCode code, string op)
{
if (code != JsErrorCode.JsNoError)
throw new InvalidOperationException($"{op} failed with {code} (0x{(int)code:X}).");
}
public void Dispose()
{
if (_disposed) return;
_disposed = true;
try
{
// Unset the current context from the SAME physical thread that set it.
JsSetCurrentContext(IntPtr.Zero);
}
catch
{
// Swallow to ensure runtime is disposed.
}
finally
{
if (_runtime != IntPtr.Zero)
{
JsDisposeRuntime(_runtime);
_runtime = IntPtr.Zero;
}
_context = IntPtr.Zero;
}
GC.SuppressFinalize(this);
}
~JsCore() => Dispose();
// =========================
// P/Invoke surface (as given)
// =========================
// Essential (expanded) JsErrorCode set matching ChakraCores headers layout.
// Values are grouped by category bases (0x10000, 0x20000, ...).
public enum JsErrorCode
{
// Success
JsNoError = 0,
// Category bases (useful when inspecting ranges)
JsErrorCategoryUsage = 0x10000,
JsErrorCategoryEngine = 0x20000,
JsErrorCategoryScript = 0x30000,
JsErrorCategoryFatal = 0x40000,
// Usage errors (0x10001+)
JsErrorInvalidArgument = 0x10001,
JsErrorNullArgument = 0x10002,
JsErrorNoCurrentContext = 0x10003,
JsErrorInExceptionState = 0x10004,
JsErrorNotImplemented = 0x10005,
JsErrorWrongThread = 0x10006,
JsErrorRuntimeInUse = 0x10007,
JsErrorBadSerializedScript = 0x10008,
JsErrorInDisabledState = 0x10009,
JsErrorCannotDisableExecution = 0x1000A,
JsErrorHeapEnumInProgress = 0x1000B,
JsErrorArgumentNotObject = 0x1000C,
JsErrorInProfileCallback = 0x1000D,
JsErrorInThreadServiceCallback = 0x1000E,
JsErrorCannotSerializeDebugScript = 0x1000F,
JsErrorAlreadyDebuggingContext = 0x10010,
JsErrorAlreadyProfilingContext = 0x10011,
JsErrorIdleNotEnabled = 0x10012,
// Engine errors (0x20001+)
JsErrorOutOfMemory = 0x20001,
JsErrorBadFPUState = 0x20002,
// Script errors (0x30001+)
JsErrorScriptException = 0x30001,
JsErrorScriptCompile = 0x30002,
JsErrorScriptTerminated = 0x30003,
JsErrorScriptEvalDisabled = 0x30004,
// Fatal (0x40001)
JsErrorFatal = 0x40001,
// Misc/diagnostic (0x50000+)
JsErrorWrongRuntime = 0x50000,
JsErrorDiagAlreadyInDebugMode = 0x50001,
JsErrorDiagNotInDebugMode = 0x50002,
JsErrorDiagNotAtBreak = 0x50003,
JsErrorDiagInvalidHandle = 0x50004,
JsErrorDiagObjectNotFound = 0x50005,
JsErrorDiagUnableToPerformAction = 0x50006,
}
[DllImport("ChakraCore.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern JsErrorCode JsCreateRuntime(uint attributes, IntPtr callback, out IntPtr runtime);
[DllImport("ChakraCore.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern JsErrorCode JsCreateContext(IntPtr runtime, out IntPtr context);
[DllImport("ChakraCore.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern JsErrorCode JsSetCurrentContext(IntPtr context);
[DllImport("ChakraCore.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public static extern JsErrorCode JsRunScript(string script, IntPtr sourceContext, string sourceUrl, out IntPtr result);
[DllImport("ChakraCore.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern JsErrorCode JsConvertValueToString(IntPtr value, out IntPtr stringValue);
[DllImport("ChakraCore.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern JsErrorCode JsStringToPointer(IntPtr value, out IntPtr buffer, out UIntPtr length);
// Note: Unsetting is typically done via JsSetCurrentContext(IntPtr.Zero)
// Kept here only if your build exposes this symbol.
[DllImport("ChakraCore.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "JsSetCurrentContext")]
public static extern JsErrorCode JsUnSetCurrentContext(IntPtr zero);
[DllImport("ChakraCore.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern JsErrorCode JsDisposeRuntime(IntPtr runtime);
}
}