使用场景
- 当前项目编辑器中不方便存放或者提交扩展代码
- 相同的扩展功能需要在多个项目(编辑器)中使用
- 项目开发中,偶尔临时需要使用一个功能,想随时使用随时卸载
设计思路
- 使用进程注入,将一个
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
//远程进程插入dll bypid
bool DllInject::nsertDllToProcessByPid(DWORD Pid, const char* pDllName)
{
// 提升权限
//ImproveProcessPri();
// 获取要插入的进程ID
DWORD dwIDExplorer = Pid;
if (dwIDExplorer == 0)
{
MEMBOX("Get Pro ID Error!\n");
return false;
}
// 打开进程
HANDLE hProcess = OpenProcess(/*PROCESS_ALL_ACCESS*/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;
}
//看有Game.exe 是否打开 . 打开了 GetProcAddress 是不准的
/*
1. 检测静态 变量是否存储
2.
*/
//卸载保护
HMODULE h = GetModuleHandle("MessageHis.dat");
if (h != NULL)
FreeLibrary(h);
PROC AdrMyDllDir = GetProcAddress(GetModuleHandle("Kernel32"), "LoadLibraryA");//(PROC)::GetProcAddress(::GetModuleHandle(TEXT("kernel32.dll")),"LoadLibraryA");
// 创建远程线程
HANDLE hThread = CreateRemoteThread(hProcess, 0, 0, (LPTHREAD_START_ROUTINE)AdrMyDllDir, pDllPath, 0, 0);
if (!hThread)
{
MEMBOX("Remote thread faile.");
//AfxMessageBox("远程线程创建失败");
return false;
}
WaitForSingleObject(hThread, INFINITE); //等待运行完成
CloseHandle(hThread);//这个CloseHandle是不会关掉远程线程的,TerminateThread能关掉
VirtualFreeEx(hProcess, pDllPath, strlen(pDllName) + 1, MEM_RELEASE);
CloseHandle(hProcess);
MEMBOX("Remote Inject Dll Success");
//AfxMessageBox("注入成功");
return true;
}
- 调用
mono
接口加载c# dll
bool MonoInjecter::InjectMonoAssembly()
{
//GetProcAddress
/* grab the root domain */
// domain = fnGetRootDomain();
// fnThreadAttach(domain);
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());
//----------------------------
/* open payload assembly */
std::string assemblyDir;
/* Grab our root directory*/
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);
/* grab the hack entrypoint */
method = fnMethodFromName(klass, PAYLOAD_MAIN, 0);
if (method == NULL) return false;
log_trace("Hello %s %ld", "fnMethodFromName", method);
/* call our entrypoint */
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
(第三方编辑器的实现)
文章来源地址https://www.toymoban.com/news/detail-556547.html
文章来源:https://www.toymoban.com/news/detail-556547.html
到了这里,关于记录使用注入的方式为Unity编辑器实现扩展能力的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!