Mono:重新加载程序集/图像和域时出现问题

3
我有一个问题已经处理了一周,你不会相信我已经检查了所有可能的东西,但它仍然不能正常工作。现在我正在为我的游戏引擎编写脚本系统,我需要动态重新编译/重新加载包含我的库和脚本的dll文件。为此,我执行以下操作:
  1. 初始化Mono(我得到了一个已为我创建的应用程序域(MonoDomain*))
  2. 打开程序集文件(dll)
  3. 从该程序集获取映像
  4. 使用mono(我加载内部调用,创建C#对象,调用函数等)。
但在某些时候,我需要重新编译/重新加载这个程序集,因为我改变了脚本代码,嗯,这是可以理解的。根据我从大量阅读的文档和各种来源中所了解的,我不必在域、程序集和映像处于活动状态时进行描述,并且为此,您首先需要:
  1. 创建新的域
  2. 上传映像
  3. 将映像连接到对象程序集(从而使映像状态为OK)
  4. 然后才描述最后一个域的资源
但是,只要我尝试,无论是在mono代码中出现错误,还是现在我已经达到了没有错误的阶段,但是这里来自新加载的程序集(具有相同名称)的新映像指针是相同的,因此它表明mono继续使用相同的映像,而不具有更新的脚本值。
这是我的Mono初始化代码:
    mono_set_dirs(".", ".");

    // 0. Load first domain, init mono + appdomain + load assembly.
    const char* domainName   = "ForceCS";
    const char* assemblyName = "ForceCS.dll";
    pMonoDomain = mono_jit_init(domainName);
    if (pMonoDomain) {
        pMonoAssembly = mono_domain_assembly_open(pMonoDomain, assemblyName);
        if (pMonoAssembly) {
            pMonoImage = mono_assembly_get_image(pMonoAssembly);
            if (pMonoImage) {
                AddInternalFunctionsAndScriptClasses(); // mono_add_internal_call ...
            }
        }
    }

然后我会使用我的动态链接库做一些事情,比如创建对象或调用方法,就像我之前说的那样。然后尝试重新加载ForceCS.dll。

    // 1. Load new domain (Works fine).
    char* newDomainName = "ForceCS";
    MonoDomain* newDomain = mono_domain_create_appdomain(newDomainName, NULL);
    if (!mono_domain_set(newDomain, false)) {
         // Cannot set domain!
    }

    // 2. Load image. (Works fine)
    const char* dllFile = "C:\\Force\\Editor\\ForceCS.dll";
    MonoImageOpenStatus status;
    MonoImage* newImage = mono_image_open(dllFile, &status); ** Problem here: Shows the same address as before and that means that Mono use same .dll.** 

    // 3. Load new assembly. (Works fine).
    MonoAssembly* assembly = mono_assembly_load_from(newImage, mono_image_get_name(newImage), &status);
    if (status != MonoImageOpenStatus::MONO_IMAGE_OK) {
        ///Cannot load assembly!
    }

    assembly = mono_assembly_open(dllFile, &status);

    newImage = mono_assembly_get_image(assembly);
    if (newImage) {
        AddInternalFunctionsAndScriptClasses();
    }

    // 4. Inload old domain.
    MonoDomain* domainToUnload = pMonoDomain == nullptr ? mono_domain_get() : pMonoDomain;
    if (domainToUnload && domainToUnload != mono_get_root_domain()) {
        mono_domain_unload(domainToUnload);

        mono_gc_collect(mono_gc_max_generation());
        mono_domain_finalize(domainToUnload, 2000);
        mono_gc_collect(mono_gc_max_generation());

        pMonoDomain = newDomain;
        pMonoImage = newImage;
        pMonoAssembly = assembly;
    }

最终问题是图像始终保持不变,只有在我使用不同的名称(例如ForceCS1.dll)加载此程序集时才起作用,但这不是我想要的。请解释一下:

  1. 何时需要关闭/释放域、程序集、图像。
  2. 程序集-图像之间的关联是什么。
  3. 如何重新加载我的.dll程序集。

我将非常感谢每一个答案。

1个回答

2

终于,在受折磨的另一个星期后,我终于设法修复了它。总的来说,我理解的问题是,我卸载了我的唯一主域,但我没有为mono创建/设置另一个域,以便他可以在重新加载程序集时在另一个线程中访问它。

一般来说,我完全重写了代码,得到了如下内容:


int main() {

    // Init Mono Runtime. (Create Root Domain)
    mono_init();
    if (mono_create_domain_and_open_assembly("Scripting Domain", "MyScript.dll"))
        mono_invoke("Script", "OnCreate", 0, NULL, NULL);

    while (true) {
        bool is_reload_request;
        std::cin >> is_reload_request;

        if (is_reload_request) {
            mono_destroy_domain_with_assisisated_assembly(domain);

            // Update new compiled assembly.
            mono_update_assembly("C:\\MonoProject\\MyScript.dll", "C:\\MonoProjectC#\\Binary\\Debug-windows-x86_64\\MyScript.dll");

            if (mono_create_domain_and_open_assembly("Scripting Domain", "MyScript.dll"))
                mono_invoke("Script", "OnCreate", 0, NULL, NULL);
        }
    }

    mono_jit_cleanup(root_domain);
    return 0;
}

现在我想详细解释一下我所做的事情,为那些可能也遇到类似问题并不理解如何修复的人提供帮助。

初始化Mono并创建根域。

我所做的第一件事是像以前一样初始化mono运行时,从而创建一个根域,以便在重新加载时我们可以切换到它。

void mono_init() {
    mono_set_dirs(".", ".");
    root_domain = mono_jit_init_version("Root Domain", "v4.0.30319");
}

创建脚本域,加载程序集。

然后我在我们的情况下创建了一个新的域,这是 脚本域,创建后,我将其设置为mono (mono_domain_set(domain, 0)),以便所有后续操作都从该域执行。然后,就像之前一样,通过该域打开了我的程序集 MyScript.dll,并获取了程序集和图像的指针。

bool mono_create_domain_and_open_assembly(char* domain_name, const char* assembly_file) {
    domain = mono_domain_create_appdomain(domain_name, NULL);
    if (domain) {
        mono_domain_set(domain, 0);

        // Open assembly.
        assembly = mono_domain_assembly_open(mono_domain_get(), assembly_file);
        if (assembly) {
            image = mono_assembly_get_image(assembly);
            if (image)
                return true;
        }
    }
    return false;
}

在这个阶段,我检查了我的组装是否正常工作,只创建了一个脚本对象并从中调用了OnCreate方法。我不会写这段代码,所以我希望那些已经使用过Mono的人知道如何做。
重新加载
现在是重新加载本身。事实上,在这里一切都比看起来简单得多,只需要在调用mono_domain_upload之前,调用mono_domain_set将我们的脚本域切换到Root Domain,当我们创建新域(新dll)时。
bool mono_destory_domain_with_assisisated_assembly(MonoDomain* domain_to_destroy) {
    if (domain_to_destroy != root_domain) {
        mono_domain_set(root_domain, 0);
        mono_domain_unload(domain_to_destroy);
        return true;
    }
    return false;
}

然后编译我们的C#项目并将其移动到我们的项目中(或者您指定的mono路径)。或者通过C++ 进行操作,就像我所做的那样:

void mono_update_assembly(char* assembly_path, char* assembly_path_from) {
    if (std::filesystem::exists(assembly_path_from)) {
        
        if (std::filesystem::exists(assembly_path))
            std::filesystem::remove(assembly_path);

        std::filesystem::copy(assembly_path_from, assembly_path);
    }
}


在完成所有这些步骤后,我们只需创建新的域并加载新程序集。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接