/// <summary> /// Executes assembly in this thread. /// Handles exceptions. /// </summary> /// <param name="asmFile">Full path of assembly file.</param> /// <param name="args">To pass to Main.</param> /// <param name="pdbOffset">0 or offset of portable PDB in assembly file.</param> /// <param name="flags"></param> /// <param name="fullPathRefs">Paths of assemblies specified using full path.</param> public static void Run(string asmFile, string[] args, int pdbOffset, RAFlags flags = 0, string fullPathRefs = null) { ADebug.PrintIf(pdbOffset == 0, "pdbOffset 0"); bool inEditorThread = 0 != (flags & RAFlags.InEditorThread); bool findLoaded = inEditorThread; _LoadedScriptAssembly lsa = default; Assembly asm = findLoaded ? lsa.Find(asmFile) : null; if (asm == null) { #if true //var p1 = APerf.Create(); var alc = System.Runtime.Loader.AssemblyLoadContext.Default; //SHOULDDO: try to unload. It seems AssemblyLoadContext supports it. Not tested. I guess it would create more problems than is useful. //p1.Next(); using (var stream = AFile.WaitIfLocked(() => File.OpenRead(asmFile))) { //p1.Next(); if (pdbOffset > 0) { var b = new byte[pdbOffset]; stream.Read(b, 0, b.Length); using var msAsm = new MemoryStream(b); b = new byte[stream.Length - pdbOffset]; stream.Read(b, 0, b.Length); using var msDeb = new MemoryStream(b); //p1.Next('f'); asm = alc.LoadFromStream(msAsm, msDeb); //p1.Next(); } else { asm = alc.LoadFromStream(stream); } } //p1.NW(); //APerf.Next('a'); if (fullPathRefs != null) { var fpr = fullPathRefs.SegSplit("|"); alc.Resolving += (System.Runtime.Loader.AssemblyLoadContext alc, AssemblyName an) => { //AOutput.Write(an, an.Name, an.FullName); foreach (var v in fpr) { var s1 = an.Name; int iName = v.Length - s1.Length - 4; if (iName <= 0 || v[iName - 1] != '\\' || !v.Eq(iName, s1, true)) { continue; } if (!AFile.ExistsAsFile(v)) { continue; } //try { return(alc.LoadFromAssemblyPath(v)); //} catch(Exception ex) { ADebug.Print(ex.ToStringWithoutStack()); break; } } return(null); }; } //ADebug.PrintLoadedAssemblies(true, true); //AOutput.Write(asm); #else byte[] bAsm, bPdb = null; using (var stream = AFile.WaitIfLocked(() => File.OpenRead(asmFile))) { bAsm = new byte[pdbOffset > 0 ? pdbOffset : stream.Length]; stream.Read(bAsm, 0, bAsm.Length); try { if (pdbOffset > 0) { bPdb = new byte[stream.Length - pdbOffset]; stream.Read(bPdb, 0, bPdb.Length); } else { var s1 = Path.ChangeExtension(asmFile, "pdb"); if (AFile.ExistsAsFile(s1)) { bPdb = File.ReadAllBytes(s1); } } } catch (Exception ex) { bPdb = null; ADebug.Print(ex); } //not very important } //APerf.Next('f'); //APerf.First(); asm = Assembly.Load(bAsm, bPdb); #endif //APerf.Next('A'); //APerf.NW(); //without AV 7 ms. With Windows Defender 10 ms, but first time 20-900 ms. if (findLoaded) { lsa.Add(asmFile, asm); } //never mind: it's possible that we load a newer compiled assembly version of script than intended. } try { var entryPoint = asm.EntryPoint ?? throw new InvalidOperationException("assembly without entry point (function Main)"); bool useArgs = entryPoint.GetParameters().Length != 0; if (useArgs) { if (args == null) { args = Array.Empty <string>(); } } //APerf.Next('1'); if (!inEditorThread) { Util.Log_.Run.Write("Task started."); } //APerf.Next('2'); if (useArgs) { entryPoint.Invoke(null, new object[] { args }); } else { entryPoint.Invoke(null, null); } if (!inEditorThread) { Util.Log_.Run.Write("Task ended."); } } catch (TargetInvocationException te) { var e = te.InnerException; if (!inEditorThread) { Util.Log_.Run.Write($"Unhandled exception: {e.ToStringWithoutStack()}"); } if (0 != (flags & RAFlags.DontHandleExceptions)) { throw e; } //AOutput.Write(e); AScript.OnHostHandledException(new UnhandledExceptionEventArgs(e, false)); } //see also: TaskScheduler.UnobservedTaskException event. // tested: the event works. // tested: somehow does not terminate process even with <ThrowUnobservedTaskExceptions enabled="true"/>. // Only when ADialog.Show called, the GC.Collect makes it to disappear but the process does not exit. // note: the terminating behavior also can be set in registry or env var. It overrides <ThrowUnobservedTaskExceptions enabled="false"/>. }
/// protected AScript() { s_instance = this; }