使用场景
- 当前项目编辑器中不方便存放或者提交扩展代码
- 相同的扩展功能需要在多个项目(编辑器)中使用
- 项目开发中,偶尔临时需要使用一个功能,想随时使用随时卸载
设计思路
- 使用进程注入,将一个
c/c++ dll
注入到当前运行的unity编辑器中
- 使用
c/c++ dll
调用mono
的函数接口,比如mono_get_root_domain
去获取unity的domain
动态去加载想要加载的外部的扩展c# dll
- 在扩展
c# dll
中调用 EditorUtility.RequestScriptReload();
来触发unity编辑器的重新编译,重载编辑器中的domain
实现卸载外部c# dll
的功能
- 在扩展
c# dll
中绑定EditorApplication.update
事件,用来处理主线程的操作,比如AssetDatabase.Refresh();
- 使用
jsonrpc
协议,用来调用c# dll
中的部分封装功能函数,可以实现在unity编辑器直接展示扩展窗口,或者将数据传至其他编辑器进行展示
初步实现
- 进程注入
c/c++ dll
bool DllInject::nsertDllToProcessByPid(DWORD Pid, const char* pDllName)
{
DWORD dwIDExplorer = Pid;
if (dwIDExplorer == 0)
{
MEMBOX("Get Pro ID Error!\n");
return false;
}
HANDLE hProcess = OpenProcess(0x1F0FFF, FALSE, dwIDExplorer);
if (hProcess == NULL)
{
MEMBOX("Open Process Error!\n");
return false;
}
void* pDllPath = VirtualAllocEx(hProcess, 0, strlen(pDllName) + 1, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!pDllPath)
{
MEMBOX("pRemoteThread = NULL!\n");
return false;
}
if (!WriteProcessMemory(hProcess, pDllPath, pDllName, strlen(pDllName) + 1, 0))
{
MEMBOX("WriteProcessMemory Fail!\n");
return false;
}
HMODULE h = GetModuleHandle("MessageHis.dat");
if (h != NULL)
FreeLibrary(h);
PROC AdrMyDllDir = GetProcAddress(GetModuleHandle("Kernel32"), "LoadLibraryA");
HANDLE hThread = CreateRemoteThread(hProcess, 0, 0, (LPTHREAD_START_ROUTINE)AdrMyDllDir, pDllPath, 0, 0);
if (!hThread)
{
MEMBOX("Remote thread faile.");
return false;
}
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
VirtualFreeEx(hProcess, pDllPath, strlen(pDllName) + 1, MEM_RELEASE);
CloseHandle(hProcess);
MEMBOX("Remote Inject Dll Success");
return true;
}
- 调用
mono
接口加载c# dll
bool MonoInjecter::InjectMonoAssembly()
{
log_trace("Hello %s %s", "fnGeDomainFriendlyName", fnGeDomainFriendlyName(domain));
log_trace("Hello %s %s", "fnGetRootDir", fnGetRootDir());
domain = fnGetDomainById(1);
log_trace("Hello %s %ld", "fnGetRootDomain", domain);
fnThreadAttach(domain);
log_trace("Hello %s %s", "fnGeDomainFriendlyName", fnGeDomainFriendlyName(domain));
log_trace("Hello %s %s", "fnGetRootDir", fnGetRootDir());
std::string assemblyDir;
assemblyDir.append(fnGetRootDir());
assemblyDir.append(ASSEMBLY_PATH);
assembly = fnAssemblyOpen(assemblyDir.c_str(), NULL);
if (assembly == NULL) return false;
log_trace("Hello %s %ld", "fnAssemblyOpen", assembly);
image = fnAssemblyGetImage(assembly);
if (image == NULL) return false;
log_trace("Hello %s %ld", "fnAssemblyGetImage", image);
klass = fnClassFromName(image, PAYLOAD_NAMESPACE, PAYLOAD_CLASS);
if (klass == NULL) return false;
log_trace("Hello %s %ld", "fnClassFromName", klass);
method = fnMethodFromName(klass, PAYLOAD_MAIN, 0);
if (method == NULL) return false;
log_trace("Hello %s %ld", "fnMethodFromName", method);
fnRuntimeInvoke(method, NULL, NULL, NULL);
log_trace("\nHello %s", "run mono dll!\n\n");
return true;
}
- 简单实现一个编辑器工具用来查找当前已经打开的unity编辑器进程,然后进行注入
- 由注入的
c# dll
调用的测试输出, 当前编辑器中是没有任何代码的
测试环境
- 操作系统系统: windows 11 64位`, (不兼容32位)
- unity版本: 2021.3.15f1
.NET6.0
(第三方编辑器的实现)