Интересная ошибка с 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 ответа:
Таким образом, вы пытаетесь загрузить сборку из байта[] и вызвать метод. Я не рекомендую делать то, что вы сделали (работать с событием 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? Это может тебе помочь.