AppDomain 支持在 UnityEngine 中已失效,有啥方法可以卸载 dll?

Posted

技术标签:

【中文标题】AppDomain 支持在 UnityEngine 中已失效,有啥方法可以卸载 dll?【英文标题】:AppDomain support is dead in UnityEngine, any way to unload dll?AppDomain 支持在 UnityEngine 中已失效,有什么方法可以卸载 dll? 【发布时间】:2019-06-14 07:41:34 【问题描述】:

我有点坚持无法从进程中处理 .NET 3.5 dll。 统一关闭 AppDomain 支持,无法使用 .NET api 从进程中卸载 dll,因为没有实现 C# 功能。

任何人都可以给我一些提示,告诉我我应该如何/从哪里开始以某种方式从内存/进程中删除 dll,以便我可以随时重新加载 dll?

【问题讨论】:

我很确定 .NET 3.5 或更低版本中的唯一方法是卸载 appdomain。也许 Unity 本身有一些东西可以填补这个空白。 我可能在我的自定义单声道版本中找到了解决方案。 @DreTaX 你介意分享你的解决方案吗? 我们正在研究它,因此它有点复杂,您最终需要为它拥有自己的 mono.dll。这是我们为 CIL 修改的游戏做的事情,它有我们的自定义 C# 客户端是私有的,但我肯定会告诉单声道部分的解决方案,这是你需要的唯一必要的东西。 @vexe 已添加解决方案。 【参考方案1】:

好吧,这次之后我认为这是某种繁重的研究,并且没有与此相关的出版物。所以这就是你必须做的。首先前往https://github.com/mono/mono/branches/all 并确定您需要哪个单声道版本。就我而言,我是为一个旧游戏做的,我需要 2014 版的单声道。

我的项目已停止,所以我没有必要保密。以下示例将向您展示一种方法,但可能不足以让您在较新的单声道版本上获得所需的内容。

使用这种方式,您可以卸载新 AppDomain 中的每个 dll。您可以扩展它以创建多个应用程序域,或仅卸载特定的 dll。如果它正在开发更新的单声道版本,请告诉我!学分归我所有,还有 4g3v。

完成后,您将需要完全相同的环境以及该版本的编译器。就我而言,这是 Visual Studio 2010 编译器,我不需要重构大部分内容。

您需要的不仅仅是按照我的指示,您还必须玩单声道,并了解项目的工作原理。

因此如前所述:C# API 不支持 AppDomains,但默认情况下支持 Mono。您只需要对其进行一些改进和扩展。以下是您需要做的:

例如在 mono.def 中定义两个新函数 mono_rb_create_domainmono_rb_unload_domain

以上两个函数将负责创建和处置域。

前往:mono/metadata/object.c 找到函数mono_runtime_unhandled_exception_policy_set并添加(后面我们会创建函数):

mono_add_internal_call("YourDLLNameSpace.Icalls::mono_rb_load_plugin", ves_icall_mono_rb_load_plugin); //Last mono function unity calls before adding their own icalls (mono_runtime_unhandled_exception_policy_set). Adding them at runtime doesn't work, so this should be a pretty good place.

上面的代码将定义一个 C# 函数,该函数将能够处理加载到我们自己的 AppDomain 中的自定义 DLL。确保您的 C# 类和函数是公开的。提醒:这应该已经在您的统一项目中。 (例如 Assembly-CSharp 或其他)。 这很关键,因为这将处理您的新 dll 的加载,并且它们将转到新的应用程序域。

[MethodImpl(MethodImplOptions.InternalCall)]
public extern Assembly mono_rb_load_plugin(IntPtr data, uint dataLen);

好的,我们必须添加一些额外的检查以避免在 unity/unity_liveness.c 中发生崩溃 查找函数 mono_add_process_object 并使其如下所示。这在较新的单声道版本中可能有所不同。 =) 我们这里所做的基本上是确保接收到的对象有一个VTable(应该是类),并且它不为空。

static void mono_add_process_object (MonoObject* object, LivenessState* state)

    gboolean has_references = 0;
    MonoClass* klass; // Define the class

    if (object && !IS_MARKED(object))
    

        klass = GET_VTABLE(object)->klass; // Get the VTable

        // Ensure that the class isn't f***ed up. Read: https://en.wikipedia.org/wiki/Hexspeak
        if(klass == NULL || klass == 0xBAADF00D || klass == 0xFEEEFEEE)
        
            return;
        

        has_references = klass->has_references;
        if(has_references || should_process_value(object,state) != LIVENESS_DONT_PROCESS)
        
            if (array_is_full(state->all_objects))
                array_safe_grow(state, state->all_objects);
            array_push_back(state->all_objects, object);
            MARK_OBJ(object);
        
        // Check if klass has further references - if not skip adding
        if (has_references)
        
            if(array_is_full(state->process_array))
                array_safe_grow(state, state->process_array);
            array_push_back(state->process_array, object);
        
    

上面的代码将确保处理的类没有错误,或者指向其他不应该出现的地方。

让我们创建我们的域处理程序。 确保在 mono 项目下创建它。

我的头文件被命名为 mono_rustbuster.h,并且包含:

static MonoDomain* rustBusterDomain;
GHashTable* pluginHashTable;
void mono_method_info_object();
MonoReflectionAssembly* ves_icall_mono_rb_load_plugin(void* objectPtr, char* data, guint32 dataLen) MONO_INTERNAL;

然后我们创建了 mono_rustbuster.c,并编写了以下内容:

#include "metadata\metadata-internals.h"
#include "metadata\image.h"
#include "metadata\assembly.h"
#include "metadata\debug-helpers.h"
#include "metadata\class-internals.h"
#include "metadata\object-internals.h"

static MonoDomain* rustBusterDomain;
GHashTable* pluginHashTable;

void mono_method_info_object()



int mono_rb_create_domain()

    pluginHashTable = g_hash_table_new(g_str_hash, g_str_equal);
    rustBusterDomain = mono_domain_create_appdomain("PluginDomain", NULL);
    return 0x01;


int mono_rb_unload_domain()

    mono_domain_unload(rustBusterDomain);
    return 0x01;


MonoReflectionAssembly* ves_icall_mono_rb_load_plugin(void* objectPtr, char* data, guint32 dataLen)

    MonoAssembly* ass;
    MonoImage* img;
    MonoImageOpenStatus status;
    MonoDomain* current;
    char *assNameBuf;

    current = mono_domain_get();

    mono_domain_set(rustBusterDomain, FALSE);

    img = mono_image_open_from_data_full(data, dataLen, TRUE, NULL, FALSE);
    ass = mono_assembly_load_from_full(img, "", &status, FALSE);

    assNameBuf = (char*)malloc(256);
    sprintf(assNameBuf, "%s", ass->aname.name);
    g_hash_table_insert(pluginHashTable, (gpointer)assNameBuf, (gpointer)ass);

    mono_domain_set(current, FALSE);

    return mono_assembly_get_object(rustBusterDomain, ass);

在此设置之后,您加载的 DLL 可能会抱怨缺少引用。这主要发生在您使用这些函数自己加载一个新的 DLL 时。为此,我们在 mono/metadata/assembly.c 中添加了一些层。 查找 mono_assembly_load_reference 此方法适用于程序集的引用,找到它调用引用变量的位置并添加:

if(reference == NULL && strstr(image->name, "data-"))

    reference = (MonoAssembly*)g_hash_table_lookup(pluginHashTable, aname.name);

提示:Mono 将数据添加到所有 dll 中,这样我们就可以使用该内存指针来查找引用。基本上修复了未解决的引用。

前往 mono/metadata/class.c 并找到:mono_class_is_assignable_from

Before 函数将使用最后一个函数检查类名是否相等返回数据。 最后的返回看起来像这样:

return mono_class_has_parent (oklass, klass);

添加:

if(!mono_class_has_parent (oklass, klass))

    if(strstr(klass->image->name, "data-"))
    
        if(!strcmp((oklass)->supertypes [(klass)->idepth - 1]->name, klass->name))
        
            //OutputDebugStringA("mono_class_is_assignable_from(): Class names are equal so true is returned");
            return TRUE;
        
    

上面的代码将添加对 ScriptableObject 转换异常的修复。

你已经完成了。您可能会遇到其他问题。以下是它在 C# 中的工作方式:

[DllImport("mono.dll")]
internal static extern int mono_rb_create_domain();
[DllImport("mono.dll")]
internal static extern int mono_rb_unload_domain();

处理新 dll 的加载:

var icalls = new Icalls();
int domaincreation = RustBuster.mono_rb_create_domain();

byte[] bytes = getyourdllsbytearraysomehow;
IntPtr pluginMem = Marshal.AllocHGlobal(bytes.Length);
for (int i = 0; i < bytes.Length; i++)

    Marshal.WriteByte(pluginMem, i, bytes[i]);


assembly = icalls.mono_rb_load_plugin(pluginMem, (uint) bytes.Length);

卸载:

int domaincheck2 = RustBuster.mono_rb_unload_domain();

【讨论】:

以上是关于AppDomain 支持在 UnityEngine 中已失效,有啥方法可以卸载 dll?的主要内容,如果未能解决你的问题,请参考以下文章

使用 JointCode.Shuttle 访问任意 AppDomain 的服务

消息泵和 AppDomain

获取 AppDomain 中所有可用命名空间的列表

每个实例的 AppDomain 或进程?

使用 UnityEngine.Social 注销

使用 JointCode.Shuttle 进行跨 AppDomain 通信的一个简单示例