/****************************************************************************
**
** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt VS Tools.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
using System;
using System.CodeDom.Compiler;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Automation;
using Microsoft.CSharp;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Threading;
using Task = System.Threading.Tasks.Task;
namespace QtVsTest.Macros
{
///
/// Macros are snippets of C# code provided by a test client at runtime. They are compiled
/// on-the-fly and may run once after compilation or stored and reused later by other macros.
/// Macros may also include special statements in comment lines starting with '//#'. These will
/// be expanded into the corresponding code ahead of C# compilation.
///
class Macro
{
///
/// Global variable, shared between macros
///
class GlobalVar
{
public string Name { get; set; }
public string Type { get; set; }
public string InitialValueExpr { get; set; }
public FieldInfo FieldInfo { get; set; }
public PropertyInfo InitInfo { get; set; }
public bool IsCallOutput { get; set; }
}
///
/// Reference to Visual Studio SDK service
///
class VSServiceRef
{
public string Name { get; set; }
public string Interface { get; set; }
public string Type { get; set; }
public FieldInfo RefVar { get; set; }
public Type ServiceType { get; set; }
}
///
/// Name of reusable macro
///
private string Name { get; set; }
///
/// True if macro compilation was successful
///
public bool Ok { get; private set; }
///
/// Result of macro compilation and execution
///
public string Result { get; private set; }
///
/// True if macro will run immediately after compilation
///
public bool AutoRun { get; private set; }
///
/// True if Visual Studio should be closed after macro execution
///
public bool QuitWhenDone { get; private set; }
AsyncPackage Package { get; }
EnvDTE80.DTE2 Dte { get; }
IntPtr MainWindowHWnd { get; }
AutomationElement UiRoot => AutomationElement.RootElement;
AutomationElement _UiVsRoot;
AutomationElement UiVsRoot
{
get
{
if (_UiVsRoot == null)
_UiVsRoot = AutomationElement.FromHandle(MainWindowHWnd);
return _UiVsRoot;
}
}
JoinableTaskFactory JoinableTaskFactory { get; }
CancellationToken ServerLoop { get; }
string Message { get; set; }
static MacroParser Parser { get; set; }
MacroLines MacroLines { get; set; }
private List SelectedAssemblies { get; } = new List
{
typeof(Macro).Assembly.FullName,
typeof(EnvDTE.DTE).Assembly.FullName,
typeof(AutomationElement).Assembly.FullName,
"System.Core",
};
IEnumerable RefAssemblies { get; set; }
private List Namespaces { get; } = new List
{
"System",
"System.Linq",
"System.Reflection",
"Task = System.Threading.Tasks.Task",
"System.Windows.Automation",
"EnvDTE",
"EnvDTE80",
};
private Dictionary ServiceRefs { get; } = new Dictionary
{
{
"Dte", new VSServiceRef
{ Name = "Dte", Interface = "DTE2", Type = "DTE" }
},
};
private Dictionary GlobalVars { get; } = new Dictionary
{
{
"Result", new GlobalVar
{ Type = "string", Name = "Result", InitialValueExpr = "string.Empty" }
},
};
string CSharpMethodCode { get; set; }
string CSharpClassCode { get; set; }
CompilerResults CompilerResults { get; set; }
Assembly MacroAssembly { get; set; }
Type MacroClass { get; set; }
FieldInfo ResultField { get; set; }
Func Run { get; set; }
const BindingFlags PUBLIC_STATIC = BindingFlags.Public | BindingFlags.Static;
const StringComparison IGNORE_CASE = StringComparison.InvariantCultureIgnoreCase;
static readonly ConcurrentDictionary Macros
= new ConcurrentDictionary();
static readonly ConcurrentDictionary Globals
= new ConcurrentDictionary();
///
/// Macro constructor
///
/// QtVSTest extension package
/// Task factory, enables joining with UI thread
/// Server loop cancellation token
public Macro(
AsyncPackage package,
EnvDTE80.DTE2 dte,
IntPtr mainWindowHWnd,
JoinableTaskFactory joinableTaskFactory,
CancellationToken serverLoop)
{
Package = package;
JoinableTaskFactory = joinableTaskFactory;
ServerLoop = serverLoop;
Dte = dte;
MainWindowHWnd = mainWindowHWnd;
ErrorMsg("Uninitialized");
}
///
/// Compile macro code
///
/// Message from client containing macro code
public async Task CompileAsync(string msg)
{
if (MacroLines != null)
return Warning("Macro already compiled");
try {
Message = msg;
if (!ParseMessage())
return false;
if (!CompileMacro())
return false;
if (!CompileClass())
return false;
await GetServicesAsync();
return true;
} catch (Exception e) {
return ErrorException(e);
}
}
///
/// Run macro
///
public async Task RunAsync()
{
if (!Ok)
return;
try {
InitGlobals();
await Run();
await SwitchToWorkerThreadAsync();
Result = ResultField.GetValue(null) as string;
if (string.IsNullOrEmpty(Result))
Result = MACRO_OK;
} catch (Exception e) {
ErrorException(e);
}
UpdateGlobals();
}
///
/// Parse message text into sequence of macro statements
///
///
bool ParseMessage()
{
if (Parser == null) {
var parser = MacroParser.Get();
if (parser == null)
return ErrorMsg("Parser error");
Parser = parser;
}
var macroLines = Parser.Parse(Message);
if (macroLines == null)
return ErrorMsg("Parse error");
MacroLines = macroLines;
return NoError();
}
///
/// Expand macro statements into C# code
///
///
bool CompileMacro()
{
if (UiVsRoot == null)
return ErrorMsg("UI Automation not available");
var csharp = new StringBuilder();
foreach (var line in MacroLines) {
if (QuitWhenDone)
return ErrorMsg("No code allowed after #quit");
if (line is CodeLine) {
var codeLine = line as CodeLine;
csharp.Append(codeLine.Code + "\r\n");
continue;
}
if (!GenerateStatement(line as Statement, csharp))
return false;
}
if (csharp.Length > 0)
CSharpMethodCode = csharp.ToString();
AutoRun = string.IsNullOrEmpty(Name);
if (AutoRun)
Name = "Macro_" + Path.GetRandomFileName().Replace(".", "");
else if (!SaveMacro(Name))
return ErrorMsg("Macro already defined");
foreach (var sv in ServiceRefs.Values.Where(x => string.IsNullOrEmpty(x.Type)))
sv.Type = sv.Interface;
var selectedAssemblyNames = SelectedAssemblies
.Select(x => new AssemblyName(x))
.GroupBy(x => x.FullName)
.Select(x => x.First());
var allAssemblies = AppDomain.CurrentDomain.GetAssemblies()
.GroupBy(x => x.GetName().Name)
.ToDictionary(x => x.Key, x => x.AsEnumerable(),
StringComparer.InvariantCultureIgnoreCase);
var refAssemblies = selectedAssemblyNames
.GroupBy(x => allAssemblies.ContainsKey(x.Name))
.SelectMany(x => x.Key
? x.SelectMany(y => allAssemblies[y.Name])
: x.Select(y =>
{
try {
return Assembly.Load(y);
} catch {
return null;
}
}));
RefAssemblies = refAssemblies
.Where(x => x != null)
.Select(x => x.Location);
return NoError();
}
bool GenerateStatement(Statement s, StringBuilder csharp)
{
switch (s.Type) {
case StatementType.Quit:
QuitWhenDone = true;
break;
case StatementType.Macro:
if (csharp.Length > 0)
return ErrorMsg("#macro must be first statement");
if (!string.IsNullOrEmpty(Name))
return ErrorMsg("Only one #macro statement allowed");
if (s.Args.Count < 1)
return ErrorMsg("Missing macro name");
Name = s.Args[0];
break;
case StatementType.Thread:
if (s.Args.Count < 1)
return ErrorMsg("Missing thread id");
if (s.Args[0].Equals("ui", IGNORE_CASE)) {
csharp.Append(
/** BEGIN generate code **/
@"
await SwitchToUIThread();"
/** END generate code **/ );
} else if (s.Args[0].Equals("default", IGNORE_CASE)) {
csharp.Append(
/** BEGIN generate code **/
@"
await SwitchToWorkerThread();"
/** END generate code **/ );
} else {
return ErrorMsg("Unknown thread id");
}
break;
case StatementType.Reference:
if (!s.Args.Any())
return ErrorMsg("Missing args for #reference");
SelectedAssemblies.Add(s.Args.First());
foreach (var ns in s.Args.Skip(1))
Namespaces.Add(ns);
break;
case StatementType.Using:
if (!s.Args.Any())
return ErrorMsg("Missing args for #using");
foreach (var ns in s.Args)
Namespaces.Add(ns);
break;
case StatementType.Var:
if (s.Args.Count < 1)
return ErrorMsg("Missing args for #var");
string typeName, varName;
if (s.Args.Count == 1) {
typeName = "object";
varName = s.Args[0];
} else {
typeName = s.Args[0];
varName = s.Args[1];
}
var initValue = s.Code;
if (varName.Where(c => char.IsWhiteSpace(c)).Any())
return ErrorMsg("Wrong var name");
GlobalVars[varName] = new GlobalVar
{
Type = typeName,
Name = varName,
InitialValueExpr = initValue
};
break;
case StatementType.Service:
if (s.Args.Count <= 1)
return ErrorMsg("Missing args for #service");
var serviceVarName = s.Args[0];
if (serviceVarName.Where(c => char.IsWhiteSpace(c)).Any())
return ErrorMsg("Invalid service var name");
if (ServiceRefs.ContainsKey(serviceVarName))
return ErrorMsg("Duplicate service var name");
ServiceRefs.Add(serviceVarName, new VSServiceRef
{
Name = serviceVarName,
Interface = s.Args[1],
Type = s.Args.Count > 2 ? s.Args[2] : s.Args[1]
});
break;
case StatementType.Call:
if (s.Args.Count < 1)
return ErrorMsg("Missing args for #call");
var calleeName = s.Args[0];
var callee = GetMacro(calleeName);
if (callee == null)
return ErrorMsg("Undefined macro");
csharp.AppendFormat(
/** BEGIN generate code **/
@"
await CallMacro(""{0}"");"
/** END generate code **/ , calleeName);
foreach (var globalVar in callee.GlobalVars.Values) {
if (GlobalVars.ContainsKey(globalVar.Name))
continue;
GlobalVars[globalVar.Name] = new GlobalVar
{
Type = globalVar.Type,
Name = globalVar.Name,
IsCallOutput = true
};
}
break;
case StatementType.Wait:
if (string.IsNullOrEmpty(s.Code))
return ErrorMsg("Missing args for #wait");
var expr = s.Code;
uint timeout = uint.MaxValue;
if (s.Args.Count > 0 && !uint.TryParse(s.Args[0], out timeout))
return ErrorMsg("Timeout format error in #wait");
if (s.Args.Count > 2) {
var evalVarType = s.Args[1];
var evalVarName = s.Args[2];
csharp.AppendFormat(
/** BEGIN generate code **/
@"
{0} {1} = default({0});
await WaitExpr({2}, () => {1} = {3});"
/** END generate code **/ , evalVarType,
evalVarName,
timeout,
expr);
} else {
csharp.AppendFormat(
/** BEGIN generate code **/
@"
await WaitExpr({0}, () => {1});"
/** END generate code **/ , timeout,
expr);
}
break;
case StatementType.Ui:
if (!GenerateUiStatement(s, csharp))
return false;
break;
}
csharp.AppendLine();
return true;
}
public AutomationElement UiFind(AutomationElement uiContext, object[] path)
{
var uiIterator = uiContext;
foreach (var item in path) {
var itemType = item.GetType();
var scope = (uiIterator == UiRoot) ? TreeScope.Children : TreeScope.Subtree;
if (itemType.IsAssignableFrom(typeof(string))) {
// Find element by name
var name = (string)item;
uiIterator = uiIterator.FindFirst(scope,
new PropertyCondition(AutomationElement.NameProperty, name));
} else if (itemType.IsAssignableFrom(typeof(string[]))) {
// Find element by name and type
var itemParams = (string[])item;
uiIterator = uiIterator.FindFirst(scope,
new AndCondition(itemParams.Select((x, i) =>
(i == 0) ? new PropertyCondition(
AutomationElement.NameProperty, x) :
(i == 1) ? new PropertyCondition(
AutomationElement.LocalizedControlTypeProperty, x) :
(i == 2) ? new PropertyCondition(
AutomationElement.AutomationIdProperty, x) :
Condition.FalseCondition).ToArray()));
}
if (uiIterator == null)
throw new Exception(
string.Format("Could not find UI element \"{0}\"", item));
}
return uiIterator;
}
static readonly IEnumerable UI_TYPES = new[]
{
"Dock", "ExpandCollapse", "GridItem", "Grid", "Invoke", "MultipleView", "RangeValue",
"Scroll", "ScrollItem", "Selection", "SelectionItem", "SynchronizedInput", "Text",
"Transform", "Toggle", "Value", "Window", "VirtualizedItem", "ItemContainer"
};
bool GenerateUiGlobals(StringBuilder csharp)
{
csharp.Append(@"
public static Func UiFind;
public static AutomationElement UiRoot;
public static AutomationElement UiVsRoot;
public static AutomationElement UiContext;");
return true;
}
bool InitializeUiGlobals()
{
if (MacroClass == null)
return false;
MacroClass.GetField("UiFind", PUBLIC_STATIC)
.SetValue(null, new Func(UiFind));
MacroClass.GetField("UiRoot", PUBLIC_STATIC)
.SetValue(null, UiRoot);
MacroClass.GetField("UiVsRoot", PUBLIC_STATIC)
.SetValue(null, UiVsRoot);
MacroClass.GetField("UiContext", PUBLIC_STATIC)
.SetValue(null, UiVsRoot);
return true;
}
bool GenerateUiStatement(Statement s, StringBuilder csharp)
{
if (s.Args.Count == 0)
return ErrorMsg("Invalid #ui statement");
if (s.Args[0].Equals("context", IGNORE_CASE)) {
//# ui context [ VSROOT | DESKTOP ] [_int_] => _string_ [, _string_, ... ]
//# ui context HWND [_int_] => _int_
if (s.Args.Count > 3 || string.IsNullOrEmpty(s.Code))
return ErrorMsg("Invalid #ui statement");
bool uiVsRoot = (s.Args.Count > 1 && s.Args[1] == "VSROOT");
bool uiDesktop = (s.Args.Count > 1 && s.Args[1] == "DESKTOP");
bool uiHwnd = (s.Args.Count > 1 && s.Args[1] == "HWND");
string context;
if (uiVsRoot)
context = string.Format("UiFind(UiVsRoot, new object[] {{ {0} }})", s.Code);
else if (uiDesktop)
context = string.Format("UiFind(UiRoot, new object[] {{ {0} }})", s.Code);
else if (uiHwnd)
context = string.Format("AutomationElement.FromHandle((IntPtr)({0}))", s.Code);
else
context = string.Format("UiFind(UiContext, new object[] {{ {0} }})", s.Code);
int timeout = 3000;
if (s.Args.Count > 1 && !uiVsRoot && !uiDesktop && !uiHwnd)
timeout = int.Parse(s.Args[1]);
else if (s.Args.Count > 2)
timeout = int.Parse(s.Args[2]);
csharp.AppendFormat(@"
await WaitExpr({0}, () => UiContext = {1});", timeout, context);
} else if (s.Args[0].Equals("find", IGNORE_CASE)) {
//# ui find [all] [_var_name_] [_timeout_] => <_scope_>, <_condition_>
var args = new Queue(s.Args.Skip(1));
bool findAll = false;
if (args.Any() && args.Peek().Equals("all", IGNORE_CASE)) {
findAll = true;
args.Dequeue();
}
string funcName = findAll ? "FindAll" : "FindFirst";
string varType = findAll ? "AutomationElementCollection" : "AutomationElement";
string varName = null;
if (args.Any() && !char.IsDigit(args.Peek()[0]))
varName = args.Dequeue();
if (findAll && string.IsNullOrEmpty(varName))
return ErrorMsg("Invalid #ui statement");
int timeout = 3000;
if (args.Any() && char.IsDigit(args.Peek()[0]))
timeout = int.Parse(args.Dequeue());
if (varName == null) {
varName = "UiContext";
} else {
csharp.Append($@"
{varType} {varName} = null;");
}
csharp.Append($@"
await WaitExpr({timeout}, () => {varName} = UiContext.{funcName}({s.Code}));");
} else if (s.Args[0].Equals("pattern", IGNORE_CASE)) {
//# ui pattern <_TypeName_> <_VarName_> [ => _string_ [, _string_, ... ] ]
//# ui pattern Invoke [ => _string_ [, _string_, ... ] ]
//# ui pattern Toggle [ => _string_ [, _string_, ... ] ]
if (s.Args.Count < 2)
return ErrorMsg("Invalid #ui statement");
string typeName = s.Args[1];
string varName = (s.Args.Count > 2) ? s.Args[2] : string.Empty;
if (!UI_TYPES.Contains(typeName))
return ErrorMsg("Invalid #ui statement");
string uiElement;
if (!string.IsNullOrEmpty(s.Code))
uiElement = string.Format("UiFind(UiContext, new object[] {{ {0} }})", s.Code);
else
uiElement = "UiContext";
string patternTypeId = string.Format("{0}PatternIdentifiers.Pattern", typeName);
string patternType = string.Format("{0}Pattern", typeName);
if (!string.IsNullOrEmpty(varName)) {
csharp.AppendFormat(@"
var {0} = {1}.GetCurrentPattern({2}) as {3};",
varName,
uiElement,
patternTypeId,
patternType);
} else if (typeName == "Invoke" || typeName == "Toggle") {
csharp.AppendFormat(@"
({0}.GetCurrentPattern({1}) as {2}).{3}();",
uiElement,
patternTypeId,
patternType,
typeName);
} else {
return ErrorMsg("Invalid #ui statement");
}
} else {
return ErrorMsg("Invalid #ui statement");
}
return true;
}
const string SERVICETYPE_PREFIX = "_ServiceType_";
const string INIT_PREFIX = "_Init_";
string MethodName { get { return string.Format("_Run_{0}_Async", Name); } }
bool GenerateClass()
{
var csharp = new StringBuilder();
foreach (var ns in Namespaces) {
csharp.AppendFormat(
/** BEGIN generate code **/
@"
using {0};"
/** END generate code **/ , ns);
}
csharp.AppendFormat(
/** BEGIN generate code **/
@"
namespace QtVsTest.Macros
{{
public class {0}
{{"
/** END generate code **/ , Name);
foreach (var serviceRef in ServiceRefs.Values) {
csharp.AppendFormat(
/** BEGIN generate code **/
@"
public static {2} {1};
public static readonly Type {0}{1} = typeof({3});"
/** END generate code **/ , SERVICETYPE_PREFIX,
serviceRef.Name,
serviceRef.Interface,
serviceRef.Type);
}
foreach (var globalVar in GlobalVars.Values) {
csharp.AppendFormat(
/** BEGIN generate code **/
@"
public static {1} {2};
public static {1} {0}{2} {{ get {{ return ({3}); }} }}"
/** END generate code **/ , INIT_PREFIX,
globalVar.Type,
globalVar.Name,
!string.IsNullOrEmpty(globalVar.InitialValueExpr)
? globalVar.InitialValueExpr
: string.Format("default({0})", globalVar.Type));
}
csharp.Append(
/** BEGIN generate code **/
@"
static string MACRO_OK { get { return ""(ok)""; } }
static string MACRO_ERROR { get { return ""(error)""; } }
static string MACRO_WARN { get { return ""(warn)""; } }
static string MACRO_ERROR_MSG(string msg)
{ return string.Format(""{0}\r\n{1}"", MACRO_ERROR, msg); }
static string MACRO_WARN_MSG(string msg)
{ return string.Format(""{0}\r\n{1}"", MACRO_WARN, msg); }
public static Func GetAssembly;
public static Func SwitchToUIThread;
public static Func SwitchToWorkerThread;
public static Func CallMacro;
public static Func, Task> WaitExpr;"
/** END generate code **/ );
if (!GenerateResultFuncs(csharp))
return false;
if (!GenerateUiGlobals(csharp))
return false;
csharp.AppendFormat(
/** BEGIN generate code **/
@"
public static async Task {0}()
{{
{1}
}}
}} /*class*/
}} /*namespace*/"
/** END generate code **/ , MethodName,
CSharpMethodCode);
CSharpClassCode = csharp.ToString();
return true;
}
///
/// Generate and compile C# class for macro
///
///
bool CompileClass()
{
if (!GenerateClass())
return false;
var dllUri = new Uri(Assembly.GetExecutingAssembly().EscapedCodeBase);
var dllPath = Uri.UnescapeDataString(dllUri.AbsolutePath);
var macroDllPath = Path.Combine(Path.GetDirectoryName(dllPath), Name + ".dll");
if (File.Exists(macroDllPath))
File.Delete(macroDllPath);
var cscParams = new CompilerParameters()
{
GenerateInMemory = false,
OutputAssembly = macroDllPath
};
cscParams.ReferencedAssemblies.AddRange(RefAssemblies.ToArray());
var cSharpProvider = new CSharpCodeProvider();
CompilerResults = cSharpProvider.CompileAssemblyFromSource(cscParams, CSharpClassCode);
if (CompilerResults.Errors.Count > 0) {
if (File.Exists(macroDllPath))
File.Delete(macroDllPath);
return ErrorMsg(string.Join("\r\n",
CompilerResults.Errors.Cast()
.Select(x => $"{x.Line}: {x.ErrorText}")
.Append(CSharpClassCode)
.Union(RefAssemblies)));
}
MacroAssembly = AppDomain.CurrentDomain.Load(File.ReadAllBytes(macroDllPath));
MacroClass = MacroAssembly.GetType(string.Format("QtVsTest.Macros.{0}", Name));
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
if (File.Exists(macroDllPath))
File.Delete(macroDllPath);
foreach (var serviceVar in ServiceRefs.Values) {
serviceVar.RefVar = MacroClass.GetField(serviceVar.Name, PUBLIC_STATIC);
var serviceType = MacroClass.GetField(SERVICETYPE_PREFIX + serviceVar.Name, PUBLIC_STATIC);
serviceVar.ServiceType = (Type)serviceType.GetValue(null);
}
ResultField = MacroClass.GetField("Result", PUBLIC_STATIC);
foreach (var globalVar in GlobalVars.Values) {
globalVar.FieldInfo = MacroClass.GetField(globalVar.Name, PUBLIC_STATIC);
if (!globalVar.IsCallOutput) {
globalVar.InitInfo = MacroClass
.GetProperty(INIT_PREFIX + globalVar.Name, PUBLIC_STATIC);
}
}
Run = (Func)Delegate.CreateDelegate(typeof(Func),
MacroClass.GetMethod(MethodName, PUBLIC_STATIC));
MacroClass.GetField("GetAssembly", PUBLIC_STATIC)
.SetValue(null, new Func(GetAssembly));
MacroClass.GetField("SwitchToUIThread", PUBLIC_STATIC)
.SetValue(null, new Func(SwitchToUIThreadAsync));
MacroClass.GetField("SwitchToWorkerThread", PUBLIC_STATIC)
.SetValue(null, new Func(SwitchToWorkerThreadAsync));
MacroClass.GetField("CallMacro", PUBLIC_STATIC)
.SetValue(null, new Func(CallMacroAsync));
MacroClass.GetField("WaitExpr", PUBLIC_STATIC)
.SetValue(null, new Func, Task>(WaitExprAsync));
if (!InitializeUiGlobals())
return false;
return NoError();
}
Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
if (args.RequestingAssembly == null || args.RequestingAssembly != MacroAssembly)
return null;
var fullName = new AssemblyName(args.Name);
var assemblyPath = RefAssemblies
.Where(x => Path.GetFileNameWithoutExtension(x).Equals(fullName.Name, IGNORE_CASE))
.FirstOrDefault();
if (string.IsNullOrEmpty(assemblyPath))
return null;
if (!File.Exists(assemblyPath))
return null;
return Assembly.LoadFrom(assemblyPath);
}
public static Assembly GetAssembly(string name)
{
return AppDomain.CurrentDomain.GetAssemblies()
.Where(x => x.GetName().Name == name)
.FirstOrDefault();
}
public async Task SwitchToUIThreadAsync()
{
await JoinableTaskFactory.SwitchToMainThreadAsync(ServerLoop);
}
public async Task SwitchToWorkerThreadAsync()
{
await TaskScheduler.Default;
}
public async Task CallMacroAsync(string macroName)
{
var callee = GetMacro(macroName);
if (callee == null)
throw new FileNotFoundException("Unknown macro");
callee.InitGlobals();
await callee.Run();
callee.UpdateGlobals();
// Refresh caller local copies of globals
InitGlobals();
}
public async Task WaitExprAsync(int timeout, Func