/// <summary> /// Loads the specified plugin assembly file into the returned plugin application domain. /// </summary> /// <param name="pluginAssemblyFile">the.NET maven plugin</param> /// <returns>application domain for .NET maven plugin</returns> /// internal AppDomain GetApplicationDomainFor(FileInfo pluginAssemblyFile) { Console.WriteLine("Loading Generator: " + pluginAssemblyFile.DirectoryName); AppDomainSetup setup = new AppDomainSetup(); setup.ApplicationBase = pluginAssemblyFile.DirectoryName; // This is a fix for NPANDAY-398. The AppDomain should never load NPanday.Plugin in the version // that was referenced by the .NET-Plugin itself, but rather the version of NPanday.Plugin that // the Generator is compiled against. setup.SetConfigurationBytes(Encoding.UTF8.GetBytes(@" <configuration> <runtime> <assemblyBinding xmlns=""urn:schemas-microsoft-com:asm.v1""> <dependentAssembly> <assemblyIdentity name=""NPanday.Plugin"" publicKeyToken=""4b435f4d76e2f0e6"" culture=""neutral"" /> <bindingRedirect oldVersion=""0.0.0.0-65535.65535.65535.65535"" newVersion=""" + typeof(FieldAttribute).Assembly.GetName().Version + @"""/> </dependentAssembly> </assemblyBinding> </runtime> </configuration> ")); AppDomain applicationDomain = AppDomain.CreateDomain("Loader", null, setup); string assemblyName = pluginAssemblyFile.Name.Replace(pluginAssemblyFile.Extension,""); applicationDomain.Load(assemblyName); return applicationDomain; }
/// <summary> /// Creates an ITask instance and returns it. /// </summary> internal static ITask CreateTask(LoadedType loadedType, string taskName, string taskLocation, int taskLine, int taskColumn, LogError logError, AppDomainSetup appDomainSetup, bool isOutOfProc, out AppDomain taskAppDomain) { bool separateAppDomain = loadedType.HasLoadInSeparateAppDomainAttribute(); s_resolverLoadedType = null; taskAppDomain = null; ITask taskInstanceInOtherAppDomain = null; try { if (separateAppDomain) { if (!loadedType.Type.IsMarshalByRef) { logError ( taskLocation, taskLine, taskColumn, "TaskNotMarshalByRef", taskName ); return null; } else { // Our task depend on this name to be precisely that, so if you change it make sure // you also change the checks in the tasks run in separate AppDomains. Better yet, just don't change it. // Make sure we copy the appdomain configuration and send it to the appdomain we create so that if the creator of the current appdomain // has done the binding redirection in code, that we will get those settings as well. AppDomainSetup appDomainInfo = new AppDomainSetup(); // Get the current app domain setup settings byte[] currentAppdomainBytes = appDomainSetup.GetConfigurationBytes(); // Apply the appdomain settings to the new appdomain before creating it appDomainInfo.SetConfigurationBytes(currentAppdomainBytes); if (BuildEnvironmentHelper.Instance.RunningTests) { // Prevent the new app domain from looking in the VS test runner location. If this // is not done, we will not be able to find Microsoft.Build.* assemblies. appDomainInfo.ApplicationBase = BuildEnvironmentHelper.Instance.CurrentMSBuildToolsDirectory; appDomainInfo.ConfigurationFile = BuildEnvironmentHelper.Instance.CurrentMSBuildConfigurationFile; } AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolver; s_resolverLoadedType = loadedType; taskAppDomain = AppDomain.CreateDomain(isOutOfProc ? "taskAppDomain (out-of-proc)" : "taskAppDomain (in-proc)", null, appDomainInfo); if (loadedType.LoadedAssembly != null) { taskAppDomain.Load(loadedType.LoadedAssembly.GetName()); } // Hook up last minute dumping of any exceptions taskAppDomain.UnhandledException += new UnhandledExceptionEventHandler(ExceptionHandling.UnhandledExceptionHandler); } } else { // perf improvement for the same appdomain case - we already have the type object // and don't want to go through reflection to recreate it from the name. return (ITask)Activator.CreateInstance(loadedType.Type); } if (loadedType.Assembly.AssemblyFile != null) { taskInstanceInOtherAppDomain = (ITask)taskAppDomain.CreateInstanceFromAndUnwrap(loadedType.Assembly.AssemblyFile, loadedType.Type.FullName); // this will force evaluation of the task class type and try to load the task assembly Type taskType = taskInstanceInOtherAppDomain.GetType(); // If the types don't match, we have a problem. It means that our AppDomain was able to load // a task assembly using Load, and loaded a different one. I don't see any other choice than // to fail here. if (taskType != loadedType.Type) { logError ( taskLocation, taskLine, taskColumn, "ConflictingTaskAssembly", loadedType.Assembly.AssemblyFile, loadedType.Type.Assembly.Location ); taskInstanceInOtherAppDomain = null; } } else { taskInstanceInOtherAppDomain = (ITask)taskAppDomain.CreateInstanceAndUnwrap(loadedType.Type.Assembly.FullName, loadedType.Type.FullName); } return taskInstanceInOtherAppDomain; } finally { // Don't leave appdomains open if (taskAppDomain != null && taskInstanceInOtherAppDomain == null) { AppDomain.Unload(taskAppDomain); RemoveAssemblyResolver(); } } }
/// <summary> /// Create an instance of the wrapped ITask for a batch run of the task. /// </summary> public ITask CreateTaskInstance(ElementLocation taskLocation, TaskLoggingContext taskLoggingContext, AppDomainSetup appDomainSetup, bool isOutOfProc) { separateAppDomain = false; separateAppDomain = loadedType.HasLoadInSeparateAppDomainAttribute(); taskAppDomain = null; if (separateAppDomain) { if (!loadedType.Type.IsMarshalByRef) { taskLoggingContext.LogError ( new BuildEventFileInfo(taskLocation), "TaskNotMarshalByRef", taskName ); return null; } else { // Our task depend on this name to be precisely that, so if you change it make sure // you also change the checks in the tasks run in separate AppDomains. Better yet, just don't change it. // Make sure we copy the appdomain configuration and send it to the appdomain we create so that if the creator of the current appdomain // has done the binding redirection in code, that we will get those settings as well. AppDomainSetup appDomainInfo = new AppDomainSetup(); // Get the current app domain setup settings byte[] currentAppdomainBytes = appDomainSetup.GetConfigurationBytes(); // Apply the appdomain settings to the new appdomain before creating it appDomainInfo.SetConfigurationBytes(currentAppdomainBytes); taskAppDomain = AppDomain.CreateDomain(isOutOfProc ? "taskAppDomain (out-of-proc)" : "taskAppDomain (in-proc)", null, appDomainInfo); // Hook up last minute dumping of any exceptions taskAppDomain.UnhandledException += new UnhandledExceptionEventHandler(ExceptionHandling.UnhandledExceptionHandler); } } // instantiate the task in given domain if (taskAppDomain == null || taskAppDomain == AppDomain.CurrentDomain) { // perf improvement for the same appdomain case - we already have the type object // and don't want to go through reflection to recreate it from the name. taskInstance = (ITask)Activator.CreateInstance(loadedType.Type); return taskInstance; } if (loadedType.Assembly.AssemblyFile != null) { taskInstance = (ITask)taskAppDomain.CreateInstanceFromAndUnwrap(loadedType.Assembly.AssemblyFile, loadedType.Type.FullName); // this will force evaluation of the task class type and try to load the task assembly Type taskType = taskInstance.GetType(); // If the types don't match, we have a problem. It means that our AppDomain was able to load // a task assembly using Load, and loaded a different one. I don't see any other choice than // to fail here. if (taskType != loadedType.Type) { taskLoggingContext.LogError ( new BuildEventFileInfo(taskLocation), "ConflictingTaskAssembly", loadedType.Assembly.AssemblyFile, loadedType.Type.Assembly.Location ); taskInstance = null; } } else { taskInstance = (ITask)taskAppDomain.CreateInstanceAndUnwrap(loadedType.Type.Assembly.FullName, loadedType.Type.FullName); } return taskInstance; }
/// <summary> /// Sets up an app domain for the task batch, if necessary /// </summary> private AppDomain PrepareAppDomain() { // If the task assembly is loaded into a separate AppDomain using LoadFrom, then we have a problem // to solve - when the task class Type is marshalled back into our AppDomain, it's not just transferred // here. Instead, NDP will try to Load (not LoadFrom!) the task assembly into our AppDomain, and since // we originally used LoadFrom, it will fail miserably not knowing where to find it. // We need to temporarily subscribe to the AppDomain.AssemblyResolve event to fix it. if (null == resolver) { resolver = new TaskEngineAssemblyResolver(); resolver.Initialize(TaskClass.Assembly.AssemblyFile); resolver.InstallHandler(); } bool hasLoadInSeparateAppDomainAttribute = false; try { hasLoadInSeparateAppDomainAttribute = TaskClass.HasLoadInSeparateAppDomainAttribute(); } catch (Exception e) // Catching Exception, but rethrowing unless it's a well-known exception. { if (ExceptionHandling.NotExpectedReflectionException(e)) throw; // Reflection related exception ProjectErrorUtilities.VerifyThrowInvalidProject(false, taskNode, "TaskInstantiationFailureError", TaskName, TaskClass.Assembly.ToString(), e.Message); } AppDomain taskAppDomain = null; if (hasLoadInSeparateAppDomainAttribute) { if (!TaskClass.Type.IsMarshalByRef) { ProjectErrorUtilities.VerifyThrowInvalidProject(false, taskNode, "TaskNotMarshalByRef", TaskName); } else { // Our task depend on this name to be precisely that, so if you change it make sure // you also change the checks in the tasks run in separate AppDomains. Better yet, just don't change it. // Make sure we copy the appdomain configuration and send it to the appdomain we create so that if the creator of the current appdomain // has done the binding redirection in code, that we will get those settings as well. AppDomainSetup appDomainInfo = new AppDomainSetup(); // Get the current app domain setup settings byte[] currentAppdomainBytes = AppDomain.CurrentDomain.SetupInformation.GetConfigurationBytes(); //Apply the appdomain settings to the new appdomain before creating it appDomainInfo.SetConfigurationBytes(currentAppdomainBytes); taskAppDomain = AppDomain.CreateDomain("taskAppDomain", null, appDomainInfo); } } return taskAppDomain; }