mono源码学习 运行程序

起因

在 使用vs2017编译mono ,一直没空闲时间,在写使用vs2017编译mono的时候,还在青岛出差,赶巧在五一在回家,青岛的工作算告一个段落了,五一之后,就在郑州出差,离家近了不少.这一段时间一直忙着新项目的事情.


今天接着上篇博文,学习Mono源码,更好的理解Mono运行原理,Mono源码很多,且复杂.还是尽量在调试源码学习.

想起当初为了看Mono源码,就自学c语言,因为赶上C#跨平台风,就又开始学了Linux操作(虽然学的不怎么样),后来在用lzma的时候,发现是用c++写的,后面就开始看了c++.就慢慢忘了初心是用来学习Mono了,转了一圈,好几年过去了.

c#测试程序代码,下面测试被调用的Program.exe

using System;
using System.Collections.Generic;
using System.Linq;

namespace qiufeng.tools
{
    static class Program
    {
        /// <summary>
        /// 应用程序的主入口点。
        /// </summary>
        static void Main()
        {
            Console.WriteLine("hello mono");
            Console.ReadLine();
        }
    }
}

进入Debug目录

先找一个c#控制台程序,放到Debug目录中.
注意: mono运行c#程序,是需要两个目录的,一个是lib(存放程序集),另一个是etc(存放配置),mono在运行的默认读取这两个目录.这里直接添加的目录与Debug同级.
进入命令行,用mono-sgen.exe,去运行我们的c#程序.
有错误提示,内容为Runtime critical type System.RuntimeType not found
又错误提示,没能成功运行,查找是那里的问题.

因上次已经成功编译了.打开mono.sln,准备调试

选择mono项目.
在vs解决方案中,选择mono的项目
看一下main.c文件中,从main函数开始
int
main (void)
{
	TCHAR szFileName[MAX_PATH];
	int argc;
	gunichar2** argvw;
	gchar** argv;
	int i;
	DWORD count;
	
	argvw = CommandLineToArgvW (GetCommandLine (), &argc);   //使用Windows API 获取参数
	argv = g_new0 (gchar*, argc + 1);
	for (i = 0; i < argc; i++)
		argv [i] = g_utf16_to_utf8 (argvw [i], -1, NULL, NULL, NULL);
	argv [argc] = NULL;

	LocalFree (argvw);

	if ((count = GetModuleFileName (NULL, szFileName, MAX_PATH)) != 0){
		char *entry = g_utf16_to_utf8 (szFileName, count, NULL, NULL, NULL);
		probe_embedded (entry, &argc, &argv);
	}

	return mono_main_with_options  (argc, argv);
}

设置项目 调试参数

在vs项目中,设置调试参数

因代码较多,简单看一下调用过程

mono 简要调用过程
重点说一下,mono_init_internal函数部分代码和出错代码
先看看怎么将C#类型添加到CLR运行时上
mono_defaults.corlib = mono_assembly_get_image (ass);

mono_defaults.object_class = mono_class_load_from_name (
            mono_defaults.corlib, "System", "Object");

mono_defaults.void_class = mono_class_load_from_name (
            mono_defaults.corlib, "System", "Void");

mono_defaults.boolean_class = mono_class_load_from_name (
            mono_defaults.corlib, "System", "Boolean");

mono_defaults.byte_class = mono_class_load_from_name (
            mono_defaults.corlib, "System", "Byte");

mono_defaults.sbyte_class = mono_class_load_from_name (
            mono_defaults.corlib, "System", "SByte");

mono_defaults.int16_class = mono_class_load_from_name (
            mono_defaults.corlib, "System", "Int16");

mono_defaults.uint16_class = mono_class_load_from_name (
            mono_defaults.corlib, "System", "UInt16");

在这行代码,看到加载RunTimeType类型.

mono_defaults.runtimetype_class = mono_class_load_from_name (
            mono_defaults.corlib, "System", "RuntimeType");

进入mono_class_load_from_name

MonoClass *
mono_class_load_from_name (MonoImage *image, const char* name_space, const char *name)
{
	ERROR_DECL (error);
	MonoClass *klass;

	klass = mono_class_from_name_checked (image, name_space, name, error);
	if (!klass)
		g_error ("Runtime critical type %s.%s not found", name_space, name);
	mono_error_assertf_ok (error, "Could not load runtime critical type %s.%s", name_space, name);
	return klass;
}

在mono_class_from_name_checked函数,没有找到RuntimeType这个Class.

在用ILSpy反编译mscorlib.dll程序集,的确没有在System命名空间下,找到Runtime这个class.推测是用这个mscorlib.dll版本旧的缘故.mono程序比较新.

所以就查看mono版本.

查看mono版本

在编译Bcl.sln解决方案,没能编译成功,又不能.Net Framework 的程序集,只能重新下载mono安装包,只是下载的特别慢.今天无法确定这个问题.

下载Mono 5.12.0 Stable (5.12.0.226)版本.提取mscorlib.dll

下载Mono还是很慢的.可以使用这个工具进行提取. MSIExtractor , 替换mscorlib.dll

替换为Mono 5.12 自带的corlib.dll

用mono-sgen.exe 调用Program.exe 看看,可以成功运行不.

使用编译好的mono-sgen.exe加载Program.exe

发现版本还是没有对应好.使用自己编译mono-sgen.exe需要版本号为1051500003的corlib.dll,而在Mono 5.12.0.226提取的corlib.dll内部版本号是1051500002.

具体可以看代码,分别在appdomain.c和config.h这两个文件中.

在appdomain.c文件中. 在mono_check_corlib_version函数中校验corlib.dll内部版本号.

const char*
mono_check_corlib_version (void)
{
	int version = mono_get_corlib_version ();  /* 获取corlib.dll 版本号 */
	if (version != MONO_CORLIB_VERSION)
		return g_strdup_printf ("expected corlib version %d, found %d.", MONO_CORLIB_VERSION, version);

	/* Check that the managed and unmanaged layout of MonoInternalThread matches */
	guint32 native_offset = (guint32) MONO_STRUCT_OFFSET (MonoInternalThread, last);
	guint32 managed_offset = mono_field_get_offset (mono_class_get_field_from_name (mono_defaults.internal_thread_class, "last"));
	if (native_offset != managed_offset)
		return g_strdup_printf ("expected InternalThread.last field offset %u, found %u. See InternalThread.last comment", native_offset, managed_offset);

	return NULL;
}

在mono_get_corlib_version获取版本号.

static int
mono_get_corlib_version (void)
{
	ERROR_DECL (error);
	MonoClass *klass;
	MonoClassField *field;
	MonoObject *value;

	//加载System命名空间下,Environment类
	klass = mono_class_load_from_name (mono_defaults.corlib, "System", "Environment");  
	mono_class_init (klass);						 //初始化Environment类
	field = mono_class_get_field_from_name (klass, "mono_corlib_version");   //获取mono_corlib_version字段
	if (!field)
		return -1;
	if (! (field->type->attrs & FIELD_ATTRIBUTE_STATIC))
		return -1;
	value = mono_field_get_value_object_checked (mono_domain_get (), field, NULL, error);
	mono_error_assert_ok (error);
	return *(gint32*)((gchar*)value + sizeof (MonoObject));
}

看过怎么获取版本号,还是回到mono_check_corlib_version函数中来

获取加载corlib.dll版本,和MONO_CORLIB_VERSION进行对比.

MONO_CORLIB_VERSION是宏, 在config.h文件中. 关于宏可以看看:了解c语言中的宏

既然确定是版本号问题.这里采用简单粗暴的方式.直接修改宏MONO_CORLIB_VERSION

直接替换MONO_CORLIB_VERION版本号

重新编译mono项目.

我们编译的mono程序可以加载c#程序并运行了

后面就可以慢慢学习mono内部机制了.

秋风 2018-05-17