Интересная ошибка с AppDomain.Загрузка()


Я пытаюсь найти способ компилировать сборки во время выполнения и загружать их. Основное намерение состоит в том, чтобы хранить их в базе данных, а не на диске. Поэтому я написал код, но увидел интересную ситуацию. Вот мой код:

//SumLib
namespace SumLib
{
    public class SumClass
    {
        public static int Sum(int a, int b)
        {
            return a + b;
        }
    }
}


// Console app
class Program
{

    public static void AssemblyLoadEvent(object sender, AssemblyLoadEventArgs args)
    {

        object[] tt = { 3, 6 };
        Type typ = args.LoadedAssembly.GetType("SumLib.SumClass");
        MethodInfo minfo = typ.GetMethod("Sum");
        int x = (int)minfo.Invoke(null, tt);
        Console.WriteLine(x);
    }

    static void Main(string[] args)
    {

        AppDomain apd = AppDomain.CreateDomain("newdomain", AppDomain.CurrentDomain.Evidence, AppDomain.CurrentDomain.SetupInformation);
        apd.AssemblyLoad += new AssemblyLoadEventHandler(AssemblyLoadEvent);

        FileStream fs = new FileStream("Sumlib.dll", FileMode.Open);
        byte[] asbyte = new byte[fs.Length];
        fs.Read(asbyte, 0, asbyte.Length);
        fs.Close();
        fs.Dispose();

//      File.Delete("Sumlib.dll");

        apd.Load(asbyte);

        Console.ReadLine();
    }
}

Код отлично работает с delete строка закомментирована, если я раскомментирую ее, домен приложения загружает сборку, AssemblyLoadEvent() метод запускается, я вижу число 9 на консоли, но когда метод заканчивается apd.Load() выдает ошибку : "не удалось загрузить файл или собрание.- что вполне разумно.

Вопрос в следующем: как метод AssemblyLoadEvent() может работать без файла сборки на диске?

Если метод каким-то образом выполняется с помощью необработанных двоичных данных, то есть ли способ, которым appdomain успешно завершает метод Load()?

3 3

3 ответа:

Таким образом, вы пытаетесь загрузить сборку из байта[] и вызвать метод. Я не рекомендую делать то, что вы сделали (работать с событием AssemblyLoad), так как оно будет вызываться для всех зависимостей.

@Jester прав насчет загрузки сборки с помощью Load () из родительского домена. Чтобы исправить это, я предлагаю использовать класс-оболочку следующим образом:

// Console app 
class Program 
{  
    public class AssemblyLoader : MarshalByRefObject
    {
        public void LoadAndCall(byte[] binary)
        {
            Assembly loadedAssembly = AppDomain.CurrentDomain.Load(binary);
            object[] tt = { 3, 6 };
            Type typ = loadedAssembly.GetType("SumLib.SumClass");
            MethodInfo minfo = typ.GetMethod("Sum", BindingFlags.Static | BindingFlags.Public);
            int x = (int)minfo.Invoke(null, tt);
            Console.WriteLine(x);
        }
    }

    static void Main()
    {
        AppDomain apd = AppDomain.CreateDomain("newdomain", AppDomain.CurrentDomain.Evidence, AppDomain.CurrentDomain.SetupInformation);
        FileStream fs = new FileStream("Sumlib.dll", FileMode.Open);
        byte[] asbyte = new byte[fs.Length];
        fs.Read(asbyte, 0, asbyte.Length);
        fs.Close();
        fs.Dispose();
        File.Delete("Sumlib.dll");    

        AssemblyLoader loader = (AssemblyLoader)apd.CreateInstanceAndUnwrap(typeof(AssemblyLoader).Assembly.FullName, typeof(AssemblyLoader).FullName);
        loader.LoadAndCall(asbyte);
        Console.ReadLine();
      }
}  

Он загружает сборку fine в "newdomain" и вызывает обработчик событий , все еще находящийся в newdomain (Вы можете проверить это, если напечатаете текущий домен в обработчике событий). Наконец, он создает возвращаемое значение, которое будет передано обратно. Вы игнорируете это возвращаемое значение в своем примере кода, но оно все равно создается. Исключение происходит во время маршалинга между доменами, поскольку десериализация также хочет загрузить сборку в домен по умолчанию.

Вот стек вызовов исключений из mono:

  at System.AppDomain.Load (System.String assemblyString, System.Security.Policy.Evidence assemblySecurity, Boolean refonly) [0x00000] in <filename unknown>:0
  at System.AppDomain.Load (System.String assemblyString) [0x00000] in <filename unknown>:0
  at (wrapper remoting-invoke-with-check) System.AppDomain:Load (string)
  at System.Reflection.Assembly.Load (System.String assemblyString) [0x00000] in <filename unknown>:0
  at System.UnitySerializationHolder.GetRealObject (StreamingContext context) [0x00000] in <filename unknown>:0
  at System.Runtime.Serialization.ObjectRecord.LoadData (System.Runtime.Serialization.ObjectManager manager, ISurrogateSelector selector, StreamingContext context) [0x00000] in <filename unknown>:0
  at System.Runtime.Serialization.ObjectManager.DoFixups () [0x00000] in <filename unknown>:0
  at System.Runtime.Serialization.Formatters.Binary.ObjectReader.ReadNextObject (System.IO.BinaryReader reader) [0x00000] in <filename unknown>:0
  at System.Runtime.Serialization.Formatters.Binary.ObjectReader.ReadObjectGraph (BinaryElement elem, System.IO.BinaryReader reader, Boolean readHeaders, System.Object& result, System.Runtime.Remoting.Messaging.Header[]& headers) [0x00000] in <filename unknown>:0
  at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.NoCheckDeserialize (System.IO.Stream serializationStream, System.Runtime.Remoting.Messaging.HeaderHandler handler) [0x00000] in <filename unknown>:0
  at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize (System.IO.Stream serializationStream) [0x00000] in <filename unknown>:0
  at System.Runtime.Remoting.RemotingServices.DeserializeCallData (System.Byte[] array) [0x00000] in <filename unknown>:0
  at (wrapper xdomain-invoke) System.AppDomain:Load (byte[])
  at (wrapper remoting-invoke-with-check) System.AppDomain:Load (byte[])
  at Program.Main (System.String[] args) [0x00000] in <filename unknown>:0

Edit: вот подтверждение от MSDN:

Попытка вызвать Load в домене целевого приложения, который не является текущим доменом приложения, приведет к успешной загрузке сборки в домене целевого приложения. Поскольку сборка не является MarshalByRefObject, когда этот метод пытается вернуть сборку для загруженной сборки в текущий домен приложения, среда выполнения common language попытается загрузить сборка в текущий домен приложения и загрузка могут завершиться ошибкой. Сборка, загруженная в текущий домен приложения, может отличаться от сборки, загруженной первой, если параметры пути для двух доменов приложения различны.

Почему бы вам не использовать параметр Shadow Copy? Это может тебе помочь.