mirror of
https://github.com/gnh1201/welsonjs.git
synced 2025-10-25 18:11:16 +00:00
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.
This commit is contained in:
parent
849470555a
commit
130a6fd767
171
WelsonJS.Toolkit/WelsonJS.Launcher/JsCore.cs
Normal file
171
WelsonJS.Toolkit/WelsonJS.Launcher/JsCore.cs
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
// 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 ChakraCore’s 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);
|
||||
}
|
||||
}
|
||||
293
WelsonJS.Toolkit/WelsonJS.Launcher/JsSerializer.cs
Normal file
293
WelsonJS.Toolkit/WelsonJS.Launcher/JsSerializer.cs
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
// JsSerializer.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.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
namespace WelsonJS.Launcher
|
||||
{
|
||||
public sealed class JsSerializer : IDisposable
|
||||
{
|
||||
private readonly JsCore _core;
|
||||
private readonly bool _ownsCore;
|
||||
|
||||
public JsSerializer() : this(new JsCore(), true) { }
|
||||
|
||||
public JsSerializer(JsCore core, bool ownsCore)
|
||||
{
|
||||
if (core == null) throw new ArgumentNullException("core");
|
||||
_core = core;
|
||||
_ownsCore = ownsCore;
|
||||
}
|
||||
|
||||
public bool IsValid(string json)
|
||||
{
|
||||
if (json == null) throw new ArgumentNullException("json");
|
||||
string script =
|
||||
"(function(){try{JSON.parse(" + Q(json) + ");return '1';}catch(_){return '0';}})()";
|
||||
string r = _core.EvaluateToString(script);
|
||||
return r == "1";
|
||||
}
|
||||
|
||||
public string Minify(string json)
|
||||
{
|
||||
if (json == null) throw new ArgumentNullException("json");
|
||||
string script = "JSON.stringify(JSON.parse(" + Q(json) + "))";
|
||||
return _core.EvaluateToString(script);
|
||||
}
|
||||
|
||||
public string Pretty(string json, int space)
|
||||
{
|
||||
if (json == null) throw new ArgumentNullException("json");
|
||||
space = Clamp(space, 0, 10);
|
||||
string script = "JSON.stringify(JSON.parse(" + Q(json) + "),null," + space.ToString(CultureInfo.InvariantCulture) + ")";
|
||||
return _core.EvaluateToString(script);
|
||||
}
|
||||
|
||||
public string Normalize(string json)
|
||||
{
|
||||
return Minify(json);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts a value by a simple path of property names (numeric segment as string = array index).
|
||||
/// Returns the selected value as a JSON string.
|
||||
/// </summary>
|
||||
public string Extract(string json, params string[] path)
|
||||
{
|
||||
if (path == null) path = new string[0];
|
||||
object[] mixed = new object[path.Length];
|
||||
for (int i = 0; i < path.Length; i++) mixed[i] = path[i];
|
||||
return Extract(json, mixed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts by a mixed path. Segments can be strings (object keys) or integers (array indices).
|
||||
/// Returns the selected value as a JSON string (e.g., a JS string returns with quotes).
|
||||
/// Usage: Extract(json, "items", 0, "name")
|
||||
/// </summary>
|
||||
public string Extract(string json, params object[] path)
|
||||
{
|
||||
if (json == null) throw new ArgumentNullException("json");
|
||||
if (path == null) path = new object[0];
|
||||
|
||||
string jsPath = BuildJsPath(path);
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.Append("(function(){var v=JSON.parse(").Append(Q(json)).Append(");");
|
||||
sb.Append("var p=").Append(jsPath).Append(";");
|
||||
sb.Append("for(var i=0;i<p.length;i++){var k=p[i];");
|
||||
sb.Append("if(Array.isArray(v) && typeof k==='number'){ v=v[k]; }");
|
||||
sb.Append("else { v=(v==null?null:v[k]); }}");
|
||||
sb.Append("return JSON.stringify(v);})()");
|
||||
return _core.EvaluateToString(sb.ToString());
|
||||
}
|
||||
|
||||
public string Serialize(object value, int space)
|
||||
{
|
||||
space = Clamp(space, 0, 10);
|
||||
string expr = BuildJsExpression(value, new HashSet<object>(ReferenceEqualityComparer.Instance), 0);
|
||||
string script = "JSON.stringify((" + expr + "),null," + space.ToString(CultureInfo.InvariantCulture) + ")";
|
||||
return _core.EvaluateToString(script);
|
||||
}
|
||||
|
||||
private static int Clamp(int v, int min, int max)
|
||||
{
|
||||
if (v < min) return min;
|
||||
if (v > max) return max;
|
||||
return v;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode a .NET string as a JS double-quoted string literal.
|
||||
/// </summary>
|
||||
private static string Q(string s)
|
||||
{
|
||||
if (s == null) return "null";
|
||||
var sb = new StringBuilder(s.Length + 16);
|
||||
sb.Append('"');
|
||||
for (int i = 0; i < s.Length; i++)
|
||||
{
|
||||
char ch = s[i];
|
||||
switch (ch)
|
||||
{
|
||||
case '\\': sb.Append(@"\\"); break;
|
||||
case '"': sb.Append("\\\""); break;
|
||||
case '\b': sb.Append(@"\b"); break;
|
||||
case '\f': sb.Append(@"\f"); break;
|
||||
case '\n': sb.Append(@"\n"); break;
|
||||
case '\r': sb.Append(@"\r"); break;
|
||||
case '\t': sb.Append(@"\t"); break;
|
||||
default:
|
||||
if (char.IsControl(ch))
|
||||
{
|
||||
sb.Append("\\u");
|
||||
sb.Append(((int)ch).ToString("X4"));
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(ch);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
sb.Append('"');
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a JS array literal representing the path.
|
||||
/// Numeric segments are emitted as numbers; others as strings.
|
||||
/// </summary>
|
||||
private static string BuildJsPath(object[] segments)
|
||||
{
|
||||
if (segments == null || segments.Length == 0) return "[]";
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.Append('[');
|
||||
for (int i = 0; i < segments.Length; i++)
|
||||
{
|
||||
if (i > 0) sb.Append(',');
|
||||
|
||||
object seg = segments[i];
|
||||
|
||||
// Treat integral types as numbers for array indexing
|
||||
if (seg is sbyte || seg is byte ||
|
||||
seg is short || seg is ushort ||
|
||||
seg is int || seg is uint ||
|
||||
seg is long || seg is ulong)
|
||||
{
|
||||
sb.Append(Convert.ToString(seg, CultureInfo.InvariantCulture));
|
||||
}
|
||||
else
|
||||
{
|
||||
string str = (seg == null) ? string.Empty : Convert.ToString(seg, CultureInfo.InvariantCulture);
|
||||
sb.Append(Q(str));
|
||||
}
|
||||
}
|
||||
sb.Append(']');
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static bool IsNumeric(object v)
|
||||
{
|
||||
if (v == null) return false;
|
||||
Type t = v.GetType();
|
||||
t = Nullable.GetUnderlyingType(t) ?? t;
|
||||
return t == typeof(byte) || t == typeof(sbyte) ||
|
||||
t == typeof(short) || t == typeof(ushort) ||
|
||||
t == typeof(int) || t == typeof(uint) ||
|
||||
t == typeof(long) || t == typeof(ulong) ||
|
||||
t == typeof(float) || t == typeof(double) ||
|
||||
t == typeof(decimal);
|
||||
}
|
||||
|
||||
private static bool IsImmutableLike(object v)
|
||||
{
|
||||
return v is string || v is bool ||
|
||||
v is byte || v is sbyte ||
|
||||
v is short || v is ushort ||
|
||||
v is int || v is uint ||
|
||||
v is long || v is ulong ||
|
||||
v is float || v is double || v is decimal ||
|
||||
v is Guid || v is DateTime || v is DateTimeOffset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a safe JS expression for a .NET value (no engine calls here).
|
||||
/// Engine will stringify the produced expression via JSON.stringify.
|
||||
/// </summary>
|
||||
private static string BuildJsExpression(object value, HashSet<object> seen, int depth)
|
||||
{
|
||||
if (depth > 64) return "null"; // depth guard
|
||||
|
||||
if (value == null) return "null";
|
||||
|
||||
// Primitives
|
||||
if (value is string) return Q((string)value);
|
||||
if (value is bool) return ((bool)value) ? "true" : "false";
|
||||
if (IsNumeric(value))
|
||||
return Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||
|
||||
// Common value-like types → stringify as JS strings
|
||||
if (value is Guid)
|
||||
return Q(((Guid)value).ToString());
|
||||
if (value is DateTime)
|
||||
return Q(((DateTime)value).ToUniversalTime().ToString("o", CultureInfo.InvariantCulture));
|
||||
if (value is DateTimeOffset)
|
||||
return Q(((DateTimeOffset)value).ToUniversalTime().ToString("o", CultureInfo.InvariantCulture));
|
||||
if (value is byte[])
|
||||
return Q(Convert.ToBase64String((byte[])value));
|
||||
|
||||
// Prevent circular refs for reference types
|
||||
if (!IsImmutableLike(value) && !seen.Add(value))
|
||||
return "null";
|
||||
|
||||
// IDictionary (string keys only)
|
||||
if (value is IDictionary)
|
||||
{
|
||||
var map = (IDictionary)value;
|
||||
var sb = new StringBuilder();
|
||||
sb.Append('{');
|
||||
bool first = true;
|
||||
foreach (DictionaryEntry kv in map)
|
||||
{
|
||||
string key = kv.Key as string;
|
||||
if (key == null) continue; // JSON keys must be strings
|
||||
if (!first) sb.Append(',');
|
||||
first = false;
|
||||
sb.Append(Q(key)).Append(':')
|
||||
.Append(BuildJsExpression(kv.Value, seen, depth + 1));
|
||||
}
|
||||
sb.Append('}');
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
// IEnumerable → array
|
||||
if (value is IEnumerable)
|
||||
{
|
||||
var seq = (IEnumerable)value;
|
||||
var sb = new StringBuilder();
|
||||
sb.Append('[');
|
||||
bool first = true;
|
||||
foreach (object item in seq)
|
||||
{
|
||||
if (!first) sb.Append(',');
|
||||
first = false;
|
||||
sb.Append(BuildJsExpression(item, seen, depth + 1));
|
||||
}
|
||||
sb.Append(']');
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
// Fallback → ToString() as JS string
|
||||
string s = value.ToString();
|
||||
return Q(s ?? string.Empty);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_ownsCore)
|
||||
_core.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reference equality comparer for cycle detection (works on .NET Framework).
|
||||
/// </summary>
|
||||
private sealed class ReferenceEqualityComparer : IEqualityComparer<object>
|
||||
{
|
||||
private ReferenceEqualityComparer() { }
|
||||
public static readonly ReferenceEqualityComparer Instance = new ReferenceEqualityComparer();
|
||||
bool IEqualityComparer<object>.Equals(object x, object y) { return object.ReferenceEquals(x, y); }
|
||||
int IEqualityComparer<object>.GetHashCode(object obj)
|
||||
{
|
||||
return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
147
WelsonJS.Toolkit/WelsonJS.Launcher/NativeBootstrap.cs
Normal file
147
WelsonJS.Toolkit/WelsonJS.Launcher/NativeBootstrap.cs
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
// NativeBootstrap.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.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace WelsonJS.Launcher
|
||||
{
|
||||
public static class NativeBootstrap
|
||||
{
|
||||
// Win32 APIs
|
||||
[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
private static extern IntPtr LoadLibrary(string lpFileName);
|
||||
|
||||
[DllImport("kernel32", SetLastError = true)]
|
||||
private static extern bool SetDefaultDllDirectories(int DirectoryFlags);
|
||||
|
||||
[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "AddDllDirectory")]
|
||||
private static extern IntPtr AddDllDirectory(string newDirectory);
|
||||
|
||||
[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
private static extern bool SetDllDirectory(string lpPathName);
|
||||
|
||||
private const int LOAD_LIBRARY_SEARCH_DEFAULT_DIRS = 0x00001000;
|
||||
|
||||
/// <summary>
|
||||
/// Tries to load native libraries in the following order:
|
||||
/// 1) %APPDATA%\{appDataSubdirectory}\{dllName}
|
||||
/// 2) Application base directory\{dllName}
|
||||
///
|
||||
/// Must be called before any P/Invoke usage.
|
||||
/// </summary>
|
||||
public static void Init(IEnumerable<string> dllNames, string appDataSubdirectory, ICompatibleLogger logger)
|
||||
{
|
||||
if (dllNames == null) throw new ArgumentNullException(nameof(dllNames));
|
||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
string appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
string appDataPath = string.IsNullOrEmpty(appDataSubdirectory)
|
||||
? appData
|
||||
: Path.Combine(appData, appDataSubdirectory);
|
||||
|
||||
string asmLocation = Assembly.GetEntryAssembly()?.Location
|
||||
?? Assembly.GetExecutingAssembly().Location
|
||||
?? AppContext.BaseDirectory;
|
||||
string appBaseDirectory = Path.GetDirectoryName(asmLocation) ?? AppContext.BaseDirectory;
|
||||
|
||||
var triedPaths = new List<string>();
|
||||
|
||||
foreach (string dllName in dllNames)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(dllName))
|
||||
continue;
|
||||
|
||||
// 1) %APPDATA% subdirectory
|
||||
string candidate1 = Path.Combine(appDataPath, dllName);
|
||||
triedPaths.Add(candidate1);
|
||||
if (TryLoad(candidate1, logger)) return;
|
||||
|
||||
// 2) Application base directory
|
||||
string candidate2 = Path.Combine(appBaseDirectory, dllName);
|
||||
triedPaths.Add(candidate2);
|
||||
if (TryLoad(candidate2, logger)) return;
|
||||
}
|
||||
|
||||
string message = "Failed to load requested native libraries.\n" +
|
||||
"Tried:\n " + string.Join("\n ", triedPaths);
|
||||
logger.Error(message);
|
||||
throw new FileNotFoundException(message);
|
||||
}
|
||||
|
||||
private static bool TryLoad(string fullPath, ICompatibleLogger logger)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(fullPath))
|
||||
{
|
||||
logger.Info($"Not found: {fullPath}");
|
||||
return false;
|
||||
}
|
||||
|
||||
string directoryPath = Path.GetDirectoryName(fullPath) ?? AppContext.BaseDirectory;
|
||||
if (!TryRegisterSearchDirectory(directoryPath, logger))
|
||||
{
|
||||
logger.Warn($"Could not register search directory: {directoryPath}");
|
||||
}
|
||||
|
||||
logger.Info($"Loading: {fullPath}");
|
||||
IntPtr handle = LoadLibrary(fullPath);
|
||||
if (handle == IntPtr.Zero)
|
||||
{
|
||||
int err = Marshal.GetLastWin32Error();
|
||||
logger.Warn($"LoadLibrary failed for {fullPath} (Win32Error={err}).");
|
||||
return false;
|
||||
}
|
||||
|
||||
logger.Info($"Successfully loaded: {fullPath}");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Warn($"Exception while loading {fullPath}: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryRegisterSearchDirectory(string directoryPath, ICompatibleLogger logger)
|
||||
{
|
||||
try
|
||||
{
|
||||
bool ok = SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);
|
||||
if (!ok)
|
||||
{
|
||||
int e = Marshal.GetLastWin32Error();
|
||||
logger.Warn($"SetDefaultDllDirectories failed (Win32Error={e}), fallback to SetDllDirectory.");
|
||||
return SetDllDirectory(directoryPath);
|
||||
}
|
||||
|
||||
IntPtr cookie = AddDllDirectory(directoryPath);
|
||||
if (cookie == IntPtr.Zero)
|
||||
{
|
||||
int e = Marshal.GetLastWin32Error();
|
||||
logger.Warn($"AddDllDirectory failed (Win32Error={e}), fallback to SetDllDirectory.");
|
||||
return SetDllDirectory(directoryPath);
|
||||
}
|
||||
|
||||
logger.Info($"Registered native DLL search directory: {directoryPath}");
|
||||
return true;
|
||||
}
|
||||
catch (EntryPointNotFoundException)
|
||||
{
|
||||
logger.Warn("DefaultDllDirectories API not available. Using SetDllDirectory fallback.");
|
||||
return SetDllDirectory(directoryPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Warn($"Register search directory failed for {directoryPath}: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ namespace WelsonJS.Launcher
|
|||
{
|
||||
internal static class Program
|
||||
{
|
||||
private const string _appDataSubDirectory = "WelsonJS";
|
||||
private static readonly ICompatibleLogger _logger;
|
||||
|
||||
public static Mutex _mutex;
|
||||
|
|
@ -22,7 +23,13 @@ namespace WelsonJS.Launcher
|
|||
|
||||
static Program()
|
||||
{
|
||||
// set up logger
|
||||
_logger = new TraceLogger();
|
||||
|
||||
// load native libraries
|
||||
NativeBootstrap.Init(new string[] {
|
||||
"ChakraCore.dll"
|
||||
}, _appDataSubDirectory, _logger);
|
||||
}
|
||||
|
||||
[STAThread]
|
||||
|
|
|
|||
|
|
@ -88,6 +88,9 @@
|
|||
<ItemGroup>
|
||||
<Compile Include="ICompatibleLogger.cs" />
|
||||
<Compile Include="IResourceTool.cs" />
|
||||
<Compile Include="JsCore.cs" />
|
||||
<Compile Include="JsSerializer.cs" />
|
||||
<Compile Include="NativeBootstrap.cs" />
|
||||
<Compile Include="ResourceTools\IpQuery.cs" />
|
||||
<Compile Include="ResourceTools\Settings.cs" />
|
||||
<Compile Include="ResourceTools\Completion.cs" />
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user