如何让Visual Studio Tools for Unity插件用于调试你自己的Mono嵌入应用程序

发布时间 2024-01-13 16:47:30作者: bodong

     最近在测试将mono嵌入到C++应用程序中,苦于没有调试器,有时候还是不怎么方便。网上搜了一下,有VS插件MDebug、VSMonoDebugger,实际试用了一下,有点麻烦,而且似乎对Windows+Visual Studio 2022支持不大好。因此想到了,Unity引擎是基于mono的,Visual Studio 2022也内置了针对Unity的调试器,名为:Visual Studio Tools for Unity。我想如果这个插件也能调试我的应用程序就好了。

     打开VS,使用菜单中的“附加到Unity”菜单打开附加对话框。最后发现并不能识别我的mono嵌入应用程序。因此直接调试Visual Studio 2022,查找和研究VS发现Unity进程的方法。经过一系列的调试,发现查找Unity相关进程的代码位于:SyntaxTree.VisualStudio.Unity.Messaging.dll 中,文件路径:

// c:\program files\microsoft visual studio\2022\professional\common7\ide\extensions\microsoft\visual studio tools for unity\SyntaxTree.VisualStudio.Unity.Messaging.dll
// SyntaxTree.VisualStudio.Unity.Messaging, Version=17.8.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a

     具体路径可能你跟我不一样。下面这个函数位于:SyntaxTree.VisualStudio.Unity.Messaging.UnityProbe类中。

public static IEnumerable<UnityProcess> GetUnityProcesses(string informationFormat = null, bool localPlayerProcessDetection = false)
{
    Process[] array = SafeProcess.GetProcesses().ToArray<Process>();
    IEnumerable<UnityProcess> enumerable = from p in array
        where p.ProcessProperty((Process _) => _.ProcessName) == "Unity"
        select UnityProbe.UnityProcessFor(p, UnityProcessType.Editor, UnityProbe.GetDebuggerPort(p.Id), informationFormat);
    if (!localPlayerProcessDetection)
    {
        return enumerable;
    }
    IEnumerable<UnityProcess> enumerable2 = from p in array.Where(new Func<Process, bool>(UnityProbe.IsLocalPlayerProcess))
        select UnityProbe.UnityProcessFor(p, UnityProcessType.Player, 0, informationFormat);
    return enumerable.Concat(enumerable2);
}

      我们可以看到,这里使用了两种方法来探查Unity相关进程。其一是直接查找名字叫Unity的进程,其二是探查可能是Unity Player(即Windows PC档)的进程。前者直接查看进程名称,后者通过下面这个函数来判断:

public static bool IsLocalPlayerProcess(Process process)
{
    bool flag;
    try
    {
        if (process.MainWindowHandle == IntPtr.Zero)
        {
            flag = false;
        }
        else
        {
            ProcessModule mainModule = process.MainModule;
            if (mainModule == null)
            {
                flag = false;
            }
            else
            {
                string fileName = mainModule.FileName;
                string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName);
                string directoryName = Path.GetDirectoryName(fileName);
                if (directoryName == null)
                {
                    flag = false;
                }
                else
                {
                    flag = Directory.Exists(Path.Combine(directoryName, fileNameWithoutExtension + "_Data")) && File.Exists(Path.Combine(directoryName, "UnityPlayer.dll"));
                }
            }
        }
    }
    catch (Exception)
    {
        flag = false;
    }
    return flag;
}

      可以看到,只要目标进程目录下有一个 “进程名(无扩展名)” + "_Data"的目录,且该目录下有一个UnityPlayer.dll,即可被视为Unity相关进程。

  因此要将我们自己的进程被该插件识别到,也有两种方法,假如我们的项目叫MyApp。那么其一是让我们生成的进程也叫Unity.exe;其二是在MyApp.exe所在目录下,新增一个MyApp_Data,然后再随便新建一个空白文本文件,把名称(含扩展名)改成"UnityPlayer.dll"即可。

       这样执行后,你就会发现,你的进程出现在了搜索对话框中了。

       当然,如果此时你直接双击连接是无法连接成功的,因为前面的任务只是让你可以被找到,如果要被连接上,还有另外一些额外的要求。让我们看一下下面这个函数:

public static int GetDebuggerPort(int processId)
{
    return 56000 + processId % 1000;
}

    可以看到,调试器假定了目标端口和进程id之间的关联关系,因此在我们初始化mono的时候,也需要考虑到这一点,因此,我们初始化mono调试器的时候,应该这样:

int DebuggerPort = 56000 + GetProcessId() % 1000;

std::string argument = std::string("--debugger-agent=transport=dt_socket,embedding=1,server=y,suspend=n,address=127.0.0.1:") + ToStlString(DebuggerPort);

const char* options[] = 
{
   argument.c_str()
};

mono_jit_parse_options(sizeof(options)/sizeof(options[0]), (char**)options);
mono_debug_init(MONO_DEBUG_FORMAT_MONO);

    这样你就可以白嫖Visual Studio Tools for Unity,用于调试你自己的mono嵌入程序了。