Unity mono代码结构分析及阅读(二)——如何阅读mono
这是基于unity mono代码阅读的第二篇。上文已经介绍了Unity自带mono的目录结构及管中窥豹从几个文件猜了一下mono Jit编译器的原理。
本文将阐述怎样系统的阅读Mono相关的代码,并且在这个基础上的搭建一个可以调试的mono环境。
关于大型工程代码的阅读
很多同学都对大型代码的阅读存在一个困惑,动辄几百万行的代码,如何有条理的阅读实在是一个很让人秃头的事情。。
我们就以Unity自带的mono代码为例简单的聊一下这个问题。知乎上有不少的提问来问这个问题,笔者自己的感受是,比如mono这样的庞大工程会有很多很多的逻辑代码。如果抱着把工程内所有的代码都阅读一遍,是有点不太现实的。
或许一个可能的阅读方法是,先从导出函数看起。
想法很丰满现实却很骨感,mono 导出函数定义在mono\msvc\Mono.def这个文件里面,打开目录,文件大小22k。。。
里面大概有900多个导出函数。。如果没有人指导。。一般人很难从这900多个导出函数内找到关键的几个并进一步阅读下去。
比如这一坨函数,到底哪个函数更重要,或者说哪个函数是关键如果没有人指点是很难从函数名上区分出来的。
那要怎么办呢?
笔者推荐的方法是先手动编译一个debug版本的可执行文件,比如在windows下,我们可以编译一个debug版本的mono.dll。然后,我们搭建一个简单的环境,来调试这个mono.dll。
可能有人会问,为啥我们要搭建一个环境而不是直接在unity里面调试。笔者曾经试过将编译好的mono.dll拷贝到 Unity引擎目录/Editor/Data/Mono/EmbedRuntime/mono.dll进行调试。实际的结果是,unity并不是一个简单的环境。即使没有点击运行,就只简单的在编辑器界面,就会有很多mono函数被调用。
这里面的原因是,unity引擎的编辑器界面使用的是C#,导致即使引擎没有运行,Unity也会调用一堆绘制UI的代码,要在这一堆调用中调试mono的细节。额,就像,在一个高速飞行的火箭上分析宇航员受力一样。
无数工程学先辈告诉我们,要把复杂问题简单化。
高速飞行的火箭上的受力十分复杂,如果我们把宇航员座舱单独拎出来,用液压器械做一个模拟环境。在这个模拟环境的宇航员,虽然真实火箭上的还是存在很大的差异,但是最起码我们可以把一些问题先理清楚,然后再慢慢的接近真实环境。
搭建一个简单的mono分析环境,这个就是本文需要做的事情。
本文的操作都是基于windows 操作系统,如果需要查看macos和linux系统的编译,可以查看官方文档。
编译Windows版及替换
打开mono源码根目录/msvc/mono.sln就可以阅读mono工程源码。(以Unity5.6对应mono源码为例,使用Visual Studio 2017打开),选择Debug_egli选项,编译,可获得对应dll。
可能遇到的问题
1.由于我们暂时只需要使用编译出来的mono.dll进行调试,所以我们需要再工程里面设置Libmono为工程启动项,这步很重要,不然你会遇到各种奇葩的mcs等组件编译不过的问题,我们当前只是想要编译一个mono.dll,其他的暂时可以略过。
2.编译的时候可能会遇到error C2220: 警告被视为错误,只需要找到报错对应的工程右击选择属性->配置属性->c/c++->常规,将“警告视为错误”的选项改为“否”就可以继续编译。
3.stdout实现不一致的问题
在函数unity_mono_redirectoutput中,会遇到stdout不存在_file变量的问题,这个实际上是一个不同平台对stdout标准库实现的差异。你可以注释掉他,也可以用下面unity自己注释掉的fprintf来修改,笔者为了方便直接注释了这句。
4.编译mono-compiler.h里面预制的trunc函数与vs2017自带的trunc函数冲突
mono版本应该是基于2010编译的,从注释上也可以看到trunc这个函数为了vs的编译器单独增加的,但是从vs2010到vs2017可能自带的标准库发生了变化,vs2017自带了这个函数,结果就冲突了。
这个报错vs2017比较迷,他会说你多了一个"("
实际上你只需要将 mono-compiler.h里面多余的trunc的定义给注释了即可。
在经历过上面一系列小打小闹后,很快编译没有了问题,进入链接阶段。你会遇到下面两个问题。
有人看到链接问题就头大,其实这个问题根源很简单,
这两个找不到的定义在errhandingapi.h里面会有_WIN32_WINNT 版本的校验,我们现在编译的时候使用的是默认的0x0500导致这两个函数的定义被屏蔽了。解决方法也很简单,
在exceptions-x86.c 用undef重新定义_WIN32_WINNT 为0x0502即可。
在解决上面一系列问题后,会在mono源码根目录生成对应的windows版本的/builds/embedruntimes/win32/mono.dll。
你看,我很早就说过,C++也是个跨平台的语言(狗头)。。。。
Mono调试环境搭建
现在,我们带debug信息的mono已经生成了,剩下的就是怎样搭建一个包含mono的测试环境。
我们把刚刚构建的mono拿出来,用几句简单的代码把mono嵌入到我们的一个测试环境中。
主要的参考资料是Mono官网的Embedding Mono这一条目
由于笔者使用Windows较多,所以下面的会以Windows平台做示范。如果需要再MAC平台下做相应的实现,可以参考陈嘉栋的文章
首先,我们先创建一个C++ Windows控制台工程,当然,其实空项目也可以,不过要做的工作就会更多一些。
工程创建好了之后,剩下的就是添加mono的include和lib库依赖。点开工程属性,添加include目录。
include目录包含两个一个是Mono源码的根目录,另外一个就是Eglib\src目录。添加好后点击确定保存。然后开始添加lib目录。
库目录路径就在我们刚刚编译完成的Win32_Debug_eglib mono输出目录下。到此依赖关系就处理好了,具体怎样将mono内嵌到自己程序中,我们下一章再见。
页:
[1]