目录
一. AvalonEdit文本器
1.功能实现
直接用Github上的源码进行实现,icsharpcode/AvalonEdit:The WPF-based text editor component used in SharpDevelop,源码地址见参考资料1,以下是文本器的使用方法
/// <summary>
/// 脚本显示初始化
/// </summary>
private void ScriptInit()
{
//文本编辑器定义
_editor.FontFamily = new System.Windows.Media.FontFamily("Consolas");
_editor.FontSize = 12;
_editor.Completion = MyScriptProvider.Instance(new ScriptProvider(), ScriptProvider.GetRelativeAssemblies())._csharpCompletion;
_editor.HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch;
_editor.VerticalAlignment = System.Windows.VerticalAlignment.Stretch;
if (!string.IsNullOrWhiteSpace(_moduleObj.InCode))
{
_editor.Text = _moduleObj.InCode;
}
string fileName = "TEST";
_editor.FileName = fileName;
_editor.Document.FileName = fileName;
elementHost.Child = _editor;
elementHost.Dock = DockStyle.Fill;
this.splitContainer4.Panel1.Controls.Add(elementHost);
//设置高亮
_editor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition("C#");
}
注意:
1)文本器CodeTextEditor是基于Control写的,如果是Form窗体需要用ElementHost容器包一下才能使用
2)关闭编译器窗体对象时,需要主动销毁CodeTextEditor和ElementHost,否则即使是托管内存,C#自动销毁特别缓慢,容易造成软件系统因为占用内存较大而卡顿(个人不建议定时器GC回收,原因是对高速项目,GC很容易引起系统短时间卡顿,而带来不可预知的问题,因此对占用较大的托管资源要主动销毁)
3)如果是Wpf的Window调用CodeTextEditor,如果在xaml语言里面写的,就不需要主动释放控件资源
主动释放控件资源代码:
private void button_Cancel_Click(object sender, EventArgs e)
{
//主动释放托管内存
_editor.Clear();
_editor = null;
elementHost.Controls.Clear();
elementHost.Dispose();
elementHost = null;
GC.Collect();
this.Close();
}
2. 高亮
editor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition("C#");
3. 代码提示
源码中代码提示功能有限,需要结合NRefactory包,实现功能更强大的代码提示,具体参考代码见参考资料2,不过参考资料2中的代码提示,每次输入字符/数字都需要重新查询所有的依赖项内容,造成较大的资源开销,关键代码修改如下(这样,只有输入点.时才会重新查询方法项目)
/// <summary>
/// 文本器提示结果,当result为空时才获取结果
/// </summary>
CodeCompletionResult resultsWhole = null;
private void ShowCompletion(string enteredText, bool controlSpace)
{
if (!controlSpace)
Debug.WriteLine("Code Completion: TextEntered: " + enteredText);
else
Debug.WriteLine("Code Completion: Ctrl+Space");
if (Completion == null) {
Debug.WriteLine("Code completion is null, cannot run code completion");
return;
}
if (completionWindow == null) {
CodeCompletionResult results = null;
try {
var offset = 0;
var doc = GetCompletionDocument(out offset);
if (results == null || results.CompletionData.Count == 0 || enteredText == ".")
{
results = Completion.GetCompletions(doc, offset, controlSpace);
}
else
{
results = resultsWhole;
}
} catch (Exception exception) {
Debug.WriteLine("Error in getting completion: " + exception);
}
if (results == null)
return;
if (completionWindow == null && results != null && results.CompletionData.Any()) {
// Open code completion after the user has pressed dot:
completionWindow = new CompletionWindow(TextArea);
completionWindow.CloseWhenCaretAtBeginning = controlSpace;
completionWindow.StartOffset -= results.TriggerWordLength;
//completionWindow.EndOffset -= results.TriggerWordLength;
IList<ICompletionData> data = completionWindow.CompletionList.CompletionData;
foreach (var completion in results.CompletionData.OrderBy(item => item.Text)) {
data.Add(completion);
}
if (results.TriggerWordLength > 0) {
//completionWindow.CompletionList.IsFiltering = false;
completionWindow.CompletionList.SelectItem(results.TriggerWord);
}
completionWindow.Show();
completionWindow.Closed += (o, args) => completionWindow = null;
}
}//end if
}//end method
4. 动态编译
CSharpCodeProvider只支持Framework,Roslyn可以同时支持Framwork和netcore,在编译时需要注意内存管理问题(和上述文本器的控件资源主动释放类似),程序集的依赖项List<MetadataReference>特别多时,每次打开脚本对象都会生成这些依赖项,关闭时Net不提供主动释放功能,只能由CLR的自动释放机制管理,而且每次打开脚本对象生成这些依赖项也会占用一定的CPU资源,影响打开速度体验等,所以选择使用静态变量!
private static List<MetadataReference> _references = new List<MetadataReference>();
共用一个依赖项最多的集合,即可,关键代码如下:
1)依赖项初始化
/// <summary>
/// 编译前的初始化,只需要一次
/// </summary>
/// <returns></returns>
public bool RefrenceInit(ref List<MetadataReference> _references)
{
_references = new List<MetadataReference>();
try
{
// 元数据引用
if (_references == null || _references.Count == 0)
{
_references = new List<MetadataReference>
{
MetadataReference.CreateFromFile(typeof(System.Uri).Assembly.Location),
MetadataReference.CreateFromFile(typeof(System.Linq.Enumerable).Assembly.Location),
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
MetadataReference.CreateFromFile(typeof(System.Data.DataTable).Assembly.Location),
MetadataReference.CreateFromFile(typeof(System.Drawing.Bitmap).Assembly.Location),
MetadataReference.CreateFromFile(typeof(System.IO.File).Assembly.Location),
MetadataReference.CreateFromFile(typeof(System.Windows.Forms.MessageBox).Assembly.Location),
MetadataReference.CreateFromFile(typeof(MegaVision.Core.MyMethods).Assembly.Location),
MetadataReference.CreateFromFile(typeof(System.Net.HttpWebRequest).Assembly.Location),
MetadataReference.CreateFromFile(Assembly.Load("System.Runtime").Location),
MetadataReference.CreateFromFile(Assembly.Load("System.Collections").Location),
};
}
Assembly as1 = Assembly.LoadFrom(AppDomain.CurrentDomain.BaseDirectory + "mscorlib.dll");
MetadataReference metadataReference = _references.Find((o => o.Display.Contains("mscorlib.dll")));
if (_references.Find((o => o.Display.Contains("mscorlib.dll"))) == null)
{
_references.Add(MetadataReference.CreateFromFile(as1.Location));
}
}
catch (Exception ex)
{
return false;
//Log.Error($"C#脚本编译前初始化出错:{ ex.Message}");
}
return true;
}
2) 编译函数
/// <summary>
/// 编译代码
/// </summary>
/// <returns></returns>
public bool Compile()
{
if (string.IsNullOrEmpty(Source))
{
return false;
}
this._objAssembly = null;
this._objectClass = null;
// 表达式树
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(Source);
// 随机程序集名称
string assemblyName = Path.GetRandomFileName();
//第三方dll程序集的引用
string myDllPath = AppDomain.CurrentDomain.BaseDirectory + "dll\\RunDll\\"; //在这加载自定义的程序集
if (Directory.Exists(myDllPath))//判断是否存在
{
//判断是否是UI.dll
foreach (var dllFile in Directory.GetFiles(myDllPath))
{
try
{
FileInfo fi = new FileInfo(dllFile);
//判断是否是xxxxxxx.dll
if (!fi.Name.EndsWith(".dll")) continue;
Assembly as2 = Assembly.LoadFrom(fi.FullName);
if (_references.Find((o => o.Display == fi.FullName)) == null)
{
_references.Add(MetadataReference.CreateFromFile(as2.Location));
}
}
catch (Exception ex)
{
}
}
}
//options
CSharpCompilationOptions defaultCompilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary).WithOptimizationLevel(OptimizationLevel.Release).WithPlatform(Platform.AnyCpu);
if (System.Diagnostics.Debugger.IsAttached)
{
//生成调试信息
defaultCompilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary).WithOptimizationLevel(OptimizationLevel.Debug).WithPlatform(Platform.AnyCpu);
}
// 创建编译对象
_compilation = CSharpCompilation.Create(
assemblyName,
syntaxTrees: new[] { syntaxTree },
references: _references,
options: defaultCompilationOptions);
using (var ms = new MemoryStream())
{
try
{
// 将编译好的IL代码放入内存流
EmitResult result = _compilation.Emit(ms);
// 编译失败,提示
_compiled = true;
int errorNum = 0;
StringBuilder sb1 = new StringBuilder();
if (!result.Success)
{
IEnumerable<Diagnostic> failures = result.Diagnostics.Where(diagnostic =>
diagnostic.IsWarningAsError ||
diagnostic.Severity == DiagnosticSeverity.Error);
foreach (Diagnostic diagnostic in failures)
{
Console.Error.WriteLine("{0}: {1}", diagnostic.Id, diagnostic.GetMessage());
_compiled = false;
errorNum++;
if (_isHideInvalidCode)
{
//sb1.Append($"{diagnostic.Id}:【第{diagnostic.Location.SourceSpan.Start - ScriptTemplate.RowCntPreExpression}行:{diagnostic.GetMessage()}】\r\n");
sb1.Append($"{diagnostic.Id}:{diagnostic.GetMessage()}】\r\n");
}
else
{
//sb1.Append($"{diagnostic.Id}:【第{diagnostic.Location.SourceSpan.Start}行:{diagnostic.GetMessage()}】\r\n");
sb1.Append($"{diagnostic.Id}:{diagnostic.GetMessage()}】\r\n");
}
}
}
else
{
// 编译成功,从内存中加载编译好的程序集
ms.Seek(0, SeekOrigin.Begin);
this._objAssembly = Assembly.Load(ms.ToArray());
}
ms.Close();
ms.Dispose();
if (errorNum > 0)
{
_errorText = $"检测出{errorNum}个错误:" + "\r\n" + sb1.ToString();
}
else
{
_errorText = "";
}
if (_compiled)
{
//_objAssembly = assembly;
this._objectClass = this._objAssembly.CreateInstance("MyScript");
if (this._objectClass != null)
{
this._objectClass.GetType().InvokeMember("ProcessID", BindingFlags.SetProperty, null, this._objectClass, new object[] { ProcessID }); //指定ProcessID属性值
this._objectClass.GetType().InvokeMember("ProcessName", BindingFlags.SetProperty, null, this._objectClass, new object[] { ProcessName }); //指定ProcessName属性值
this._objectClass.GetType().InvokeMember("ModuleName", BindingFlags.SetProperty, null, this._objectClass, new object[] { ModuleName }); //指定ModuleName属性值
}
else
{
_compiled = false;
}
}
return _compiled;
}
catch (Exception exc)
{
return false;
}
}
#endif
}
二. 运行效果展示
实现功能:
1. 编辑脚本内容
2. 编译脚本
3.运行脚本
三. 源码链接
CSDN资源
(9条消息) AvalonEdit文本器+NRefactory代码提示+Roslyn动态编译-C#文档类资源-CSDN文库
---------------------------------------------------------------------------------------------------------------------
四. 参考资料
动态编译器的两个github源码,可以学习下:
1. AvalonEdit源码:
GitHub - icsharpcode/AvalonEdit: The WPF-based text editor component used in SharpDevelop
2. AvalonEdit(源码的代码提示部分用NRefactory进行修改)+Roslyn实现动态编译器的demo
GitHub - lukebuehler/NRefactory-Completion-Sample: A small but full featured prototype how to do code completion with NRefactory