Обмен информацией из скомпилированных библиотек через домены приложений в Unity - PullRequest
0 голосов
/ 09 июля 2019

Я пишу игру Unity, в которой вы пишете код C # / Unity (MonoBehaviours) для решения проблем.Скомпилирую код пользователя в dll и загружаю его в основной домен приложения.Он работает нормально, но проблема в том, что добавленные библиотеки остаются в игре до тех пор, пока она не будет закрыта.

Я нашел решение загрузить эти сборки в отдельный домен приложения, и мне это удалось.Если я останусь в дочернем домене приложения, я не смогу присоединить эти monoBehaviours к gameObject.Я получаю ошибку: «AddComponent запрашивает« {тип} », который не является типом движка Unity».В противном случае, очевидно, я не смогу отправить данные типа обратно в основной домен приложения, поскольку это приведет к загрузке сборки в основном домене приложения.Это также происходит, когда я пытаюсь создать простой сериализуемый класс и отправить его в основной домен.

Версия Unity является персональной 2019.3.0a6.
Чтобы выполнить удаленную загрузку сборок, я компилирую Loader.dll и помещаю его в папку «Ресурсы» моего проекта, чтобы Unity распознала его.

Это загрузчик и большинство вещей которые я пробовал.Моно, которое я пытаюсь добавить и получить, находится в той же сборке, что и Loader:

public class Loader : MarshalByRefObject
{
    public void LoadBytes(byte[] bytes) /// Works
    {
        var assemb = Assembly.Load(bytes);

        var types = assemb.GetTypes();

        this.LogTypes(types);
    }

    public void GetComponentFromAttachedObject() ///ArgumentException: GetComponent requires that the requested component '{type}' derives from MonoBehaviour or Component or is an interface.
    {
        var main = GameObject.Find("Main");
        var scr = main.GetComponent<LoaderAssemblyMonoTest>();
        scr.Work();
    }

    public void CreatePrimitiveAndAttach() /// AddComponent asking for "{type}" which is not a Unity engine type.
    {
        var prim = GameObject.CreatePrimitive(PrimitiveType.Sphere);
        Debug.Log("created primitive");
        prim.transform.position = new Vector3(10,10,10);
        Debug.Log("modified primitive");
        var scr = prim.AddComponent<LoaderAssemblyMonoTest>();
        Debug.Log("attached scr to primitive");
    }

    public byte[] SerializeInstance(byte[] bytes) /// Loads the assembly in main domain
    {`enter code here`
        var assemb = Assembly.Load(bytes);
        var types = assemb.GetTypes();
        var instance = (IPlugIn)Activator.CreateInstance(types[0]);
        var formatter = new BinaryFormatter();
        var stream = new MemoryStream(); 
        formatter.Serialize(stream, instance);
        return stream.ToArray(); 
    } 

    public IPlugIn[] LoadPlugins(byte[] bytes) // Loads the assembly in main domain
    {
        var result = new List<IPlugIn>(); 
        var assemb = Assembly.Load(bytes);
        var types = assemb.GetTypes();
        foreach (var type in types)
        {
            if (typeof(IPlugIn).IsAssignableFrom(type))
            {
                result.Add((IPlugIn)Activator.CreateInstance(type));
            }
        }

        foreach (var plugin in result)
        {
            Debug.Log("From Loader Domain");
            plugin.DoAThing();
        }
        Debug.Log("After Do A Thing Loader Domain");

        return result.ToArray();
    }

    public void AttachTest() /// AddComponent asking for "{type}" which is not a Unity engine type.
    {
        var main = GameObject.Find("Main");
        main.AddComponent<LoaderAssemblyMonoTest>();
    }

    public void AttachTestMadTest1(Type type) /// AddComponent asking for "{type}" which is not a Unity engine type.
    {
        Debug.Log("Here");
        var main = GameObject.Find("Main");
        main.AddComponent(type);
    }
}

Вот как я настраиваю новый домен:

private static AppDomain SetUpAsMainWithLoaderOptimization(string name, LoaderOptimization optimization)
{
    var info = AppDomain.CurrentDomain.SetupInformation;
    info.LoaderOptimization = optimization;

    var domain = AppDomain.CreateDomain(
        name,
        AppDomain.CurrentDomain.Evidence,
        info);

    return domain;
}

Вот как я добавляюсборки к нему:

public static void AddAllCurrentDomainAssembliesToDomain(AppDomain domain)
{
    foreach (var ass in AppDomain.CurrentDomain.GetAssemblies())
    {
        domain.Load(File.ReadAllBytes(ass.Location));
    }
}

Так я запускаю загрузчик.Я не загружаю сборку сначала, потому что с предыдущим способом она уже там.

private static Loader InitLoader(AppDomain domain)
{ 
    //domain.Load(typeof(Loader).Assembly.FullName);

    Loader loader = (Loader)Activator.CreateInstance(
        domain,
        typeof(Loader).Assembly.FullName,
        typeof(Loader).FullName,
        false,
        BindingFlags.Public | BindingFlags.Instance,
        null,
        null,
        null,
        null).Unwrap();

    return loader;
}

Это один из примеров того, как я его использую:

public static void AttachMonoFromSeparateDomain ()
{
    userDomain = SetUpAsMainWithLoaderOptimization("testy", LoaderOptimization.MultiDomain);
    AddAllCurrentDomainAssembliesToDomain(userDomain);
    userDomain.AssemblyResolve += AssemblyResolveSeparateDomain;
    AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolveMainDomain;

    var main = GameObject.Find("Main");
    main.AddComponent<LoaderAssemblyMonoTest>();

    var loader = InitLoader(userDomain);
    loader.GetComponentFromAttachedObject();
}

Из того, что я прочитал, я ожидал, что отправка сериализуемого класса через домены приложения не должна приводить к загрузке сборки вОсновное приложение, но это так.Будем весьма благодарны за любые идеи о том, как сделать это без утечек памяти или как прикрепить monoBehaviours из дочернего домена appDomain!

...