找回密码
 立即注册
查看: 585|回复: 0

[笔记] Unity mono代码结构分析及阅读(三)——mono调试环境搭建

[复制链接]
发表于 2020-12-17 10:05 | 显示全部楼层 |阅读模式
这是基于unity mono代码阅读的第三篇。
      本文主要介绍怎样将mono内嵌到我们的C++控制台程序中。
     上文已经介绍了怎样在windows环境下编译一个debug的mono.dll和创建一个空的工程用来调试我们刚刚编译出来的mono,工程的配置部分部分请参考上一篇文章。
       接着上一节,我们已经构建好了一个工程,接下来我们按照mono官方指引加入代码
       主要看Initializing the Mono runtime这一节。
建议大家一定要先看完上文的官方指引,然后再继续看本文下面的部分,不然会被各种突兀的函数弄的云里雾里。
       根据官方文档,我们首先需要包含mono相关的头文件并创建对应的工作域。
#include <mono/jit/jit.h>
#include <mono/metadata/assembly.h>

MonoDomain *domain;

domain = mono_jit_init (domain_name);
       然后,根据文档,我们需要调用mono_domain_assembly_open来加载assembly,也就是编译好的CIL字节码。
MonoAssembly *assembly;
assembly = mono_domain_assembly_open (domain, "file.exe");
if (!assembly)
error ();
       加载好对应的assembly后,我们就可以调用mono_jit_exec来运行CIL字节码了。
retval = mono_jit_exec (domain, assembly, argc - 1, argv + 1);       当然,最后运行完了不要忘了清理一下现场
mono_jit_cleanup (domain);       好了,我们一板一眼的把官方文档提供的内嵌mono的方法打到我们的代码里面,你会得到这样的代码
       光是一堆这样的标红的函数名就足以说明,事情好像跟我们想象的不一样。
       一个个来解决,首先是
jit.h找不到的问题
       由于Unity所带的Mon版本比较老,官方文档里面的Jit.h位置跟我们手上版本的位置不太一样。这个解决起来也简单,在整个目录下搜索一下jit.h。很快就可以发现在我们的版本,jit.h还是在mini目录下。
     可见新版本的mono已经将Jit功能单独出来,并不包含在mini这个文件夹下了。
平台差异问题
     官方文档的演示代码里面,用的是linux平台下的退出函数,还有参数输入。error()函数我们可以简单的改为return -1而argc argv是输入参数,我们也简单填充一下就好。
一些小细节
还有一些参数没有类型变量没有赋值这些小问题也简单修一下。
#include "pch.h"
#include <iostream>
#include <mono/mini/jit.h>
#include <mono/metadata/assebly.h>
const char * domain_name = "MonoTest";
int main(int argc, char *argv[])
{
   
    MonoDomain *domain;
    domain = mono_jit_init(domain_name);
    MonoAssembly *assembly;
    assembly = mono_domain_assembly_open(domain, "file.exe");
     if (!assembly)
    {
        printf("Assembly Load Fail。");
        system("pause");
        return -1;
    }
    int retval = mono_jit_exec(domain, assembly, argc - 1, argv + 1);
    mono_jit_cleanup(domain);
    system("pause");
}
      简单处理后的代码是这样的,标红的地方也没有了,好,我们简单的编译一下看看。
      我们似曾相识的老朋友又来了。不过这次比较好解决。只是没有连接libmono.lib而已,我们加一行代码即可。
#pragma comment(lib, "mono.lib")
       然后再编译一次
      哦也,我们竟然编译通过了。果然C++是最好的跨平台语言(手动狗头)。
     不过等下,回头想想,额,
assembly = mono_domain_assembly_open(domain, "file.exe");      file.exe是什么玩意????不管了,官方说能行就能行(狗头),先到debug目录运行一把看看。
      直接运行的结果就是我们会得到一个大红的X。不过没关系我们也是身经百战了。看起来只是少了一个mono.dll。巧了,我们刚好编译成功了一个mono.dll
       如果你觉得手工来回复制麻烦,可以写个bat来拷贝
xcopy E:\mono\mono-unity-5.6\msvc\..\builds\embedruntimes\win32 D:\MonoTest\Debug  /s /e /y /d         运行之后,目录变成这样,我们再运行一次monotest.exe,这次没报错了,但是好像控制台一闪而过。再点击一次看看呢?还是一闪而过??到底出了什么问题呢?
          我们打开命令行,并CD到这个目录,再次运行程序
         这个错误就是提示我们缺少mscorlib.dll,由于我们并没有完整的编译整个mono工具包,所以导致我们没有生成运行CLR需要的标准库即没有生成mscorlib.dll这样的dll。
         这一个要怎么办呢?去重新按照教程想办法重新生成一遍标准库是可以的,甚至你还可以用github上开源的标准库替代mono自己的标准库,虽然会有些小修改,但是也不会有啥大问题。不过既然我们分析的是Unity自带的mono代码,那相信各位读者一定都已经装了unity,unity本身是已经生成好了一套适合编辑器使用的标准库了。那么这套标准库再哪个目录下呢?我们在unity引擎目录下简单搜索一下mscorlib.dll就会有所发现。
         笔者所用的引擎为unity5.6,其中mono所带的标准库在Data\Mono\lib\mono下面,新的2017和2019可能会存在差异,请读者注意筛选。
         不过就算已经找到了unity生成好的标准库,怎样在当前的测试工程里面告诉mono去哪里寻找这个标准库呢?这个就需要google一番。很快就找到一个函数 mono_assembly_setrootdir,作为附带我们还发现了一个mono api文档库。
我们再把unity已经编译好的CLR的标准库设置进去再试一下。
    const char *CLRRoot = "E:\\UnityEngine\\Data\\Mono\\lib";
    mono_assembly_setrootdir(CLRRoot);
我们发现还是不行,似乎这次是unity带的标准库出了点问题,版本对不上。我们打开unitymono目录下,2.0文件夹,查看一下mscore.dll的文件信息
好了,我们现在有了当前unity所带的CLR标准库的版本号,然后我们将原来的mono_jit_init替换成可以指定版本的初始化版本mono_jit_init_version再来试一下。
domain =  mono_jit_init_version("MonoTest", "v2.0.50727");;
点击,这次啥都没有出现,多年的经验告诉我事情没有这么简单,因为我们现在已经搭好了调试环境,这次可以直接F5走起。不断F5后,很快我们就停了下来。
我们的程序异常了。。
如果真的手工复制编译过本文上述代码的网友应该很清楚的记得,我们有设置过assembly的root dir的?那为啥会出问题了呢?这个时候我们自己手工编译mono的好处就体现出来了,VS里面可以关联我们编译的mono源代码目录,直接就可以浏览一下这个assembly root Dir是在干什么。我们正好可以直接从当前的这个异常来查看问题,并且了解底层实现。
很明显,我们一开始设置的assemblyrootdir在后面被别的代码给置空了,我们现在要做的,只是在Setrootdir这个函数打一个断点看是谁干的这个坏事即可。第一次的设置是我们自己,很快第二次清空assemblyrootdir的凶手就被我们抓到了
在mono初始化的时候,如果检查到Config_dir为null,就会把我们开头辛辛苦苦设置的assemblyrootdir给置空。。他还真是好意来着。。
好了,知道问题,我们现在就需要设置一下Configdir不是么。那么config_dir长啥样呢。
你看,看有注释的源代码就是爽。我们现在可以看到,这个目录应该跟我们刚刚找到的,unity目录下lib目录同级的etc目录,我们去unity自带的mono目录看一下。
你看,还真有,那就安排上。
const char *CLRRoot = "E:\\trunk\\Data\\Mono\\lib";
const char *ConfigRoot = "E:\\trunk\\Data\\Mono\\etc";
mono_set_dirs(CLRRoot, ConfigRoot);
再编译运行一次看看
很显然,这次是真的没有找到file.exe这个CIL文件。
好了到此,我们构建一个可以调试的
mono环境的任务已经完成了。
期间遇到了很多问题,最终也被我们解决。而且在这个解决的过程中,我们也认识了好多个函数,如果有兴趣可以研究一下这几个函数的内部代码实现。
至于怎样生成file.exe这个可以运行的文件,我们下章再见。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-5-28 13:43 , Processed in 0.101454 second(s), 27 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表