Интересная ошибка с AppDomain.Load () - PullRequest
3 голосов
/ 21 января 2011

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

//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 ]

6 голосов
/ 21 января 2011

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

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

  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

Правка: здесь подтверждение от MSDN :

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

2 голосов
/ 21 января 2011

Итак, вы пытаетесь загрузить сборку из байта [] и вызвать метод.Я не рекомендую способ, которым вы это сделали (работая с событием 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();
      }
}  
0 голосов
/ 21 января 2011

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

...