(一)问题

   之前写Lua时,修改完代码 reload 就可以热重载代码,调试起来十分方便(重构则十分痛苦)。

   现在使用 C# 做开发,目前还没找到比较方便地进行热重载的方式。只能退而求其次,在调试上找找方法,尽量能减少编译重启的次数。

   基本原理是:动态编译生成dll,再调用 Assembly 中的方法。之前看到过一个关键词 REPL,原理肯定不同,但加上编辑器扩展或许能实现类似的交互效果。

   作用实际上不是很大,基本和打断点调试时在即时窗口中运行代码是类似的(稍微好用一些,毕竟可以执行一段多行代码)。目前主要在测试特效之类时预留接口,便可以使用不同参数动态调试,或者打印一些不太好断点的单例变量。

   2021-10 补充:用处还是挺多的。虽然不能添加和修改已有函数,但是程序中的静态方法,以及能获取到对象实例的成员方法都能调用,很适合用来调试已有的UI表现等。比如别人写了一个HUD提示功能,接入到你的模块中时,就不用每改一点代码就重新运行一次了。

(二)提前备注

  1. 每次编译都会生成不同名的dll(同名的话会报文件占用中的错误),生成目录放在项目\Temp\ 中,关闭 Unity 时会自动清空该目录

(三)执行效果

执行方法,在Console中打印变量

编译生成的Dll们

(四)代码

1. DynamicCodeHelper

编译执行代码函数,其中这一段比较重要,会引用当前 Domain 中的所有程序集,否则调用项目中的方法会报错:

foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
_compileParams.ReferencedAssemblies.Add(assembly.Location);
}

完整代码:

using Microsoft.CSharp;
using System;
using System.CodeDom.Compiler;
using System.Reflection;
using System.Text;
using UnityEngine; public class DynamicCodeHelper
{
private CSharpCodeProvider _provider;
private CSharpCodeProvider Provider
{
get
{
if (_provider == null)
{
DynamicCodeWindow.ColorDebug("[DynamicCode] Create CodeDomProvider");
_provider = new CSharpCodeProvider();
}
return _provider;
}
} private CompilerParameters _compileParams;
private CompilerParameters CompileParams
{
get
{
if (_compileParams == null)
{
DynamicCodeWindow.ColorDebug("[DynamicCode] Create CompilerParameters");
_compileParams = new CompilerParameters();
// Add ALL of the assembly references
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
_compileParams.ReferencedAssemblies.Add(assembly.Location);
}
_compileParams.GenerateExecutable = false;
_compileParams.GenerateInMemory = false;
}
_compileParams.OutputAssembly = DynamicCodeWindow.OUTPUT_DLL_DIR + "/DynamicCodeTemp" + Time.realtimeSinceStartup + ".dll";
return _compileParams;
}
} public void ExcuteDynamicCode(string codeStr, bool isUseTextAsAllContent)
{
if (codeStr == null) codeStr = "";
string generatedCode;
if (isUseTextAsAllContent)
{
generatedCode = codeStr;
}
else
{
generatedCode = GenerateCode(codeStr);
}
Debug.Log("[DynamicCode] Compile Start: " + generatedCode);
CompilerResults compileResults = Provider.CompileAssemblyFromSource(CompileParams, generatedCode);
if (compileResults.Errors.HasErrors)
{
Debug.LogError("[DynamicCode] 编译错误!");
var msg = new StringBuilder();
foreach (CompilerError error in compileResults.Errors)
{
msg.AppendFormat("Error ({0}): {1}\n",
error.ErrorNumber, error.ErrorText);
}
throw new Exception(msg.ToString());
}
// 通过反射,调用DynamicCode的实例
//AppDomain a = AppDomain.CreateDomain(AppDomain.CurrentDomain.FriendlyName);
Assembly objAssembly = compileResults.CompiledAssembly;
DynamicCodeWindow.ColorDebug("[DynamicCode] Gen Dll FullName: " + objAssembly.FullName);
DynamicCodeWindow.ColorDebug("[DynamicCode] Gen Dll Location: " + objAssembly.Location);
DynamicCodeWindow.ColorDebug("[DynamicCode] PathToAssembly: " + compileResults.PathToAssembly);
object objDynamicCode = objAssembly.CreateInstance("DynamicCode");
MethodInfo objMI = objDynamicCode.GetType().GetMethod("CodeExecute");
objMI.Invoke(objDynamicCode, null);
} private string GenerateCode(string methodCode)
{
StringBuilder sb = new StringBuilder();
sb.Append(@"using System;
using UnityEngine;
public class DynamicCode {
public void CodeExecute() {
");
sb.Append(methodCode);
sb.Append("}}");
string code = sb.ToString();
return code;
}
}

2. DynamicCodeWindow

简单的编辑器扩展,不太重要。基本上就是获取文本然后调用DynamicCodeHelper.ExcuteDynamicCode

#if UNITY_EDITOR_WIN
using UnityEditor;
using UnityEngine; /// <summary>
/// 字符串编译成DLL载入,只在编辑器中使用
/// </summary>
public class DynamicCodeWindow : EditorWindow
{
// 生成在 ..\Client\Client\Temp\DynamicCode\DynamicCodeTemp.dll
public const string OUTPUT_DLL_DIR = @"Temp\DynamicCode";
[MenuItem("TestTool/DynamicRun")]
private static void Open()
{
GetWindow<DynamicCodeWindow>();
} private static DynamicCodeHelper _instance;
private static DynamicCodeHelper Helper
{
get
{
if (_instance == null)
{
_instance = new DynamicCodeHelper();
}
return _instance;
}
}
private bool isUseTextAsAllContent;
private string content = @"Debug.Log(""Hello"");";
private void OnGUI()
{
isUseTextAsAllContent = EditorGUILayout.ToggleLeft("完全使用文本作为编译内容(手动添加using等)", isUseTextAsAllContent);
content = EditorGUILayout.TextArea(content, GUILayout.Height(200));
if (GUILayout.Button("执行代码"))
{
Run(content, isUseTextAsAllContent);
}
if (GUILayout.Button("重置内容"))
{
if (isUseTextAsAllContent)
{
content = @"using System;
using UnityEngine;
public class DynamicCode {
public void CodeExecute() {
Debug.Log(""Hello"");
}
}";
}
else
{
content = @"Debug.Log(""Hello"");";
}
}
if (GUILayout.Button("新建/打开缓存目录"))
{
if (!System.IO.Directory.Exists(OUTPUT_DLL_DIR))
{
System.IO.Directory.CreateDirectory(OUTPUT_DLL_DIR);
}
System.Diagnostics.Process.Start(OUTPUT_DLL_DIR);
}
} private static void Run(string code, bool isUseTextAsAllContent)
{
ColorDebug("[DynamicCode] Start......");
string codetmp = code;
Helper.ExcuteDynamicCode(codetmp, isUseTextAsAllContent);
ColorDebug("[DynamicCode] End......");
} public static void ColorDebug(string content)
{
Debug.Log(string.Format("<color=#ff8400>{0}</color>", content));
}
}
#endif

[Unity] 编辑器运行中动态编译执行C#代码的更多相关文章

  1. 在C#中动态编译T4模板代码

    转: http://www.wxzzz.com/1438.html 资料: https://cnsmartcodegenerator.codeplex.com/SourceControl/latest ...

  2. 转: angularjs 指令中动态编译的方法(适用于有异步请求的情况) 内嵌指令无效

    angular的坑很多 例子: 在directive的link中有一个$http请求,当请求完成后根据返回的值动态做element.append('......');这个操作, 能显示没问题,可问题是 ...

  3. Python中动态编译函数compile(source, filename, mode, ......)参数filename的作用是什么?

    动态编译函数compile调用语法如下: compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1) 其中的fi ...

  4. 在 Linux/windows下 命令行中使用和执行 PHP 代码[交互式php]

    [注释]在ubuntu下,升级php到7.1版本,虽然提示的是Interactive mode enabled, 但实际上可以直接书写命令,和interactive shell效果一样. 一:wind ...

  5. 使用PyQt(Python+Qt)+动态编译36行代码实现的计算器

    PyQt是基于跨平台的图形界面C++开发工具Qt加Python包装的一个GPL软件(GPL是GNU General Public License的缩写,是GNU通用公共授权非正式的中文翻译),Qt基于 ...

  6. JAVA中动态编译的简单使用

    一.引用库 pom文件中申明如下: <dependencies> <!-- https://mvnrepository.com/artifact/junit/junit --> ...

  7. (转)高性能JavaScript:加载和运行(动态加载JS代码)

    浏览器是如何加载JS的 当浏览器遇到一个<script>标签时,浏览器首先根据标签src属性下载JavaScript代码,然后运行JavaScript代码,继而继续解析和翻译页面.如果需要 ...

  8. 在 Linux 命令行中使用和执行 PHP 代码

    PHP是一个开源服务器端脚本语言,最初这三个字母代表的是“Personal Home Page”,而现在则代表的是“PHP:Hypertext Preprocessor”,它是个递归首字母缩写.它是一 ...

  9. 测试博文中添加可执行JS代码

    昨天申请开通了博客园的JS权限,今天来看看效果. 测试执行JS 测试执行JS // 运行

随机推荐

  1. 使用Redis分布式锁控制请求串行处理

    1.需求背景 在一些写接口的场景下,由于一些网络因素导致用户的表单重复提交,就会在相邻很短的时间内,发出多个数据一样的请求.后台接口的幂等性保证一般都是先检查数据的状态,然后决定是否进行执行写入操作, ...

  2. gin框架简介

    介绍 Gin是一个golang的微框架,封装比较优雅,API友好,源码注释比较明确,具有快速灵活,容错方便等特点 对于golang而言,web框架的依赖要远比Python,Java之类的要小.自身的n ...

  3. 利用JavaScript与正则表达式判断输入账号格式是否正确

    在学习了HTML DOM对象后,做几个小练习来巩固一下所学内容. 正则表达式: 正则表达式,又称规则表达式.(英语:Regular Expression,在代码中常简写为regex.regexp或RE ...

  4. nginx二进制安装

    目录 一:二进制安装nginx 1.下载CentOS源 2.安装CentOS源 3.下载epel源(失败显示未找到命令) 4.解决依赖 5.安装Epel源 6.安装nginx 一:二进制安装nginx ...

  5. ApacheCN Kali Linux 译文集 20211020 更新

    Kali Linux 秘籍 中文版 第一章 安装和启动Kali 第二章 定制 Kali Linux 第三章 高级测试环境 第四章 信息收集 第五章 漏洞评估 第六章 漏洞利用 第七章 权限提升 第八章 ...

  6. 「CTSC2006」歌唱王国

    概率生成函数\(g(x)=\sum_{i\geq 0}t_ix^i\),\(t_i\)表示结果为\(i\)的概率 令\(f(x)\)表示i位表示串结束时长度为i的概率,\(G(x)\)表示i位表示串长 ...

  7. Net6 DI源码分析Part5 在Kestrel内Di Scope生命周期是如何根据请求走的?

    Net6 DI源码分析Part5 在Kestrel内Di Scope生命周期是如何根据请求走的? 在asp.net core中的DI生命周期有一个Scoped是根据请求走的,也就是说在处理一次请求时, ...

  8. java实现以docx格式导出

    直接上代码:Map<String, Object> dataMap = afterLoanReportService.exportReport(startDate, endDate);// ...

  9. 为什么要配置path环境变量

    因为在jdk下bin文件夹中有很多我们在开发中要使用的工具,如java.exe,javac.exe,jar.ex等,那么我们在使用时,想要在电脑的任意位置下使用这些java开发工具,那么我们就需有把这 ...

  10. request和session获取参数的区别

    说简单点 request对象和session对象的最大区别是生命周期. request request范围较小一些,只是一个请求. request对象的生命周期是针对一个客户端(说确切点就是一个浏览器 ...