So, after doing a ton of research on how to sandbox a c# script compiler so that the assemblies being loaded are only loaded into the sandbox AppDomain and not my primary AppDomain, I have run into the problem where all of the dll's I created are unloaded upon unloading the sandbox AppDomain EXCEPT FOR ONE. That one is from a script that looks like:
return new Func<List<int>,int>
(
(list) =>
{
var total = 0;
foreach (int i in list)
{
total += i;
}
return total;
}
);
Now what happens with the returns from the scripts is that they are all eventually returned in a dictionary to the primary AppDomain. The rest of the scripts all return simple serializable objects or primitives and as I said all of their assemblies unload correctly and I am able to delete them with the primary domain still active. Is it possible that this particular return has to "pass" back the creating assembly to the primary AppDomain because its value is a Func? Is there no way around this?
The reason I have the sandbox in the first place is so that after a set of scripts runs I can dispose of the object that executed them and that dispose method unloads the sandbox domain and deletes all of the created assemblies. I want to be able to use this in a constantly running environment like a web server where a build up of assemblies is problematic and currently, every time a script set runs with a return of a Func, I am going to have a lingering assembly. I would rather not have an asterisk next to the documentation of using this library, so any ideas would be welcome.
For reference, here is my code that compiles the script:
var provider = new CSharpCodeProvider(new Dictionary<string, string>() { { CompilerOptionName, CompilerOptionVersion } });
var compilerParams = new CompilerParameters { GenerateExecutable = false, GenerateInMemory = false };
compilerParams.ReferencedAssemblies.AddRange(References);
compilerParams.TempFiles = new TempFileCollection(BinPath);
compilerParams.OutputAssembly = Path.Combine(BinPath,
Utilities.AssemblyPrefix + ProfigurationExe.Profiguration.ID + "_" + Action.ID + "_Script" + Utilities.DllExt);
// If no object is returned by user code, enter in a return null to satisfy returning an object for
// default code template. If they do have a return that will return first.
Code = Code + ReturnNull;
var parameterString = BuildParameterString();
parameterString = !String.IsNullOrEmpty(parameterString) ? ", " + parameterString : String.Empty;
// If someone simply imports namespaces separated by spaces, commas, or semicolons, create a proper using statement
if (!String.IsNullOrEmpty(Imports) && !IsUsingRegex.IsMatch(Imports))
{
Imports = String.Join("; ", Imports.Split(" ,;".ToCharArray()).Select(s => Using + " " + s)) + "; ";
}
FinalCode = String.Format(Imports + CodeTemplate,
new object[] {DefaultNamespace, ClassName, DefaultMethod, parameterString, Code});
// This just is a variable of the code that will be compiled, with all spaces and line breaks removed
var debugFriendlierFinalCode = U.CompressWhiteSpaceRegex.Replace(FinalCode.Replace("\r", String.Empty).Replace("\n", String.Empty), U.OneSpace);
// Note that we are adding the import fields to the beginning in case user wants to import namespaces (and not have to always use fully qualified class names)
var results = provider.CompileAssemblyFromSource(compilerParams, FinalCode);
Assembly = results.CompiledAssembly;
if (!results.Errors.HasErrors)
{
return Assembly;
}
// If there were compiler errors, aggregrate them into an exception.
var errors = new StringBuilder("Dynamic Code Compiler Errors :\r\n");
foreach (CompilerError error in results.Errors)
{
errors.AppendFormat("Line {0},{1}\t: {2}\n",
error.Line, error.Column, error.ErrorText);
}
throw new ProfigurationException(errors.ToString());
The Func is serializable and is being copied like you intend. But it points to code that resides inside of the assembly you want to unload. That's why the assembly is being loaded in the parent AppDomain as well.
Not sure what to recommend. I hope it makes sense that you cannot unload code that is being used.
Related
Module Creation process
var file = Console.ReadLine();
var moduleContext = ModuleDef.CreateModuleContext();
var moduleCreationOptions = new ModuleCreationOptions(moduleContext, CLRRuntimeReaderKind.Mono);
var buffer = File.ReadAllBytes(file);
var moduleDefMD = ModuleDefMD.Load(buffer, moduleCreationOptions);
var moduleWriterOptions = new ModuleWriterOptions(moduleDefMD);
moduleWriterOptions.MetadataLogger = DummyLogger.NoThrowInstance;
moduleWriterOptions.MetadataOptions.Flags |=
MetadataFlags.PreserveStringsOffsets
| MetadataFlags.PreserveUSOffsets
| MetadataFlags.PreserveBlobOffsets
| MetadataFlags.PreserveExtraSignatureData;
moduleWriterOptions.Cor20HeaderOptions.Flags = ComImageFlags.ILOnly;
Add Static Method with single instruction ret and call it in Module cctor
var methodDef = new MethodDefUser(Guid.NewGuid().ToString(), MethodSig.CreateStatic(moduleDefMD.CorLibTypes.Void));
methodDef.IsStatic = true;
methodDef.Body = new CilBody();
methodDef.Body.Instructions.Add(new Instruction(OpCodes.Ret));
moduleDefMD.GlobalType.Methods.Add(methodDef);
var cctorInstructions = moduleDefMD.GlobalType.FindOrCreateStaticConstructor().Body.Instructions;
cctorInstructions.Insert(0, new Instruction(OpCodes.Call, methodDef));
Write ModuleDefMD
var output = Path.Combine(Path.GetDirectoryName(file), "output." + Path.GetExtension(file));
moduleDefMD.Write(output);
Result
assembly attributes are deleted, only single one is left is [assembly: AssemblyVersion("1.0.0.0")], nothing else, no Assembly Name, no Assembly Company etc it causes problems with Assembly.GetAssembly().GetName
I tried to create another project with the same parameters and conditions i.e same .NET Framework versions, same ModuleDefMD writing process, I tried to do everything the same but nothing is help me, this is just like a curse, I'm deeply learing dnlib sources and I didn't found anything interesting that can help me to solve this problem. I open issue on dnlib GitHub
The problem is solved, when I was resolving CustomAttributes via reflection I used to be removing them
With Mono.Cecil it looks quite simple when we can just set the Body of the target MethodDefinition to the Body of the source MethodDefinition. For simple methods, that works OK. But for some methods whereas a custom type is used (such as to init a new object), it won't work (with an exception thrown at the time writing the assembly back).
Here is my code:
//in current app
public class Form1 {
public string Test(){
return "Modified Test";
}
}
//in another assembly
public class Target {
public string Test(){
return "Test";
}
}
//the copying code, this works for the above pair of methods
//the context here is of course in the current app
var targetAsm = AssemblyDefinition.ReadAssembly("target_path");
var mr1 = targetAsm.MainModule.Import(typeof(Form1).GetMethod("Test"));
var targetType = targetAsm.MainModule.Types.FirstOrDefault(e => e.Name == "Target");
var m2 = targetType.Methods.FirstOrDefault(e => e.Name == "Test");
var m1 = mr1.Resolve();
var m1IL = m1.Body.GetILProcessor();
foreach(var i in m1.Body.Instructions.ToList()){
var ci = i;
if(i.Operand is MethodReference){
var mref = i.Operand as MethodReference;
ci = m1IL.Create(i.OpCode, targetType.Module.Import(mref));
}
else if(i.Operand is TypeReference){
var tref = i.Operand as TypeReference;
ci = m1IL.Create(i.OpCode, targetType.Module.Import(tref));
}
if(ci != i){
m1IL.Replace(i, ci);
}
}
//here the source Body should have its Instructions set imported fine
//so we just need to set its Body to the target's Body
m2.Body = m1.Body;
//finally write to another output assembly
targetAsm.Write("modified_target_path");
The code above was not referenced from anywhere, I just tried it myself and found out it works for simple cases (such as for the 2 methods Test I posted above). But if the source method (defined in the current app) contains some Type reference (such as some constructor init ...), like this:
public class Form1 {
public string Test(){
var u = new Uri("SomeUri");
return u.AbsolutePath;
}
}
Then it will fail at the time writing the assembly back. The exception thrown is ArgumentException with the following message:
"Member 'System.Uri' is declared in another module and needs to be imported"
In fact I've encountered a similar message before but it's for method calls like (string.Concat). And that's why I've tried importing the MethodReference (you can see the if inside the foreach loop in the code I posted). And really that worked for that case.
But this case is different, I don't know how to import the used/referenced types (in this case it is System.Uri) correctly. As I know the result of Import should be used, for MethodReference you can see that the result is used to replace the Operand for each Instruction. But for Type reference in this case I totally have no idea on how.
All my code posted in my question is fine BUT not enough. Actually the exception message:
"Member 'System.Uri' is declared in another module and needs to be imported"
complains about the VariableDefinition's VariableType. I just import the instructions but not the Variables (which are just referenced exactly from the source MethodBody). So the solution is we need to import the variables in the same way as well (and maybe import the ExceptionHandlers as well because an ExceptionHandler has CatchType which should be imported).
Here is just the similar code to import VariableDefinition:
var vars = m1.Body.Variables.ToList();
m1.Body.Variables.Clear();
foreach(var v in vars){
var nv = new VariableDefinition(v.Name, targetType.Module.Import(v.VariableType));
m1.Body.Variables.Add(nv);
}
Since CompileAssemblyFromSource add custom functions in a smart way was ignored im going to ask this question differently so people will bother to read it.
cutting at the chase,i am making a language by "translating" the new syntax into c# and compiling it in memory in this fashion.
using (Microsoft.CSharp.CSharpCodeProvider CodeProv =
new Microsoft.CSharp.CSharpCodeProvider())
{
CompilerResults results = CodeProv.CompileAssemblyFromSource(
new System.CodeDom.Compiler.CompilerParameters()
{
GenerateInMemory = true
},
code);
var type = results.CompiledAssembly.GetType("MainClass");
var obj = Activator.CreateInstance(type);
var output = type.GetMethod("Execute").Invoke(obj, new object[] { });
Console.WriteLine(output);
}
basically i am executing a "main" function written inside the code variable.
and i am using some functions in the code variable i would like to include without adding it as a string at the bottom like this:
code += #"public void Write(string path, object thevar)
{
if (thevar.GetType() == typeof(string))
{
System.IO.File.WriteAllText(path,(string)thevar);
}
if (thevar.GetType() == typeof(string[]))
{
System.IO.File.WriteAllLines(path,(string[])thevar);
}
}";
Can i somehow add a class from my Actual main project in VS and let the compiled in memory code access it? without adding it as a string.
You can embed your source code file(s) as resources. With this technique you can edit the file in Visual Studio and access the contents of the files as if it was a string during run-time.
This link shows how to do it:
https://stackoverflow.com/a/433182/540832
Is there a way to unload parent AppDomain?
I am trying to load a different version of an assembly in my new AppDomain, but it keeps loading the version from the parent domain. When I am loading the assembly in the new AppDomain I am showing the correct path.
Or maybe there is another way I can do that?
Thanks in advance.
EDIT
AppDomain MailChimpDomain = AppDomain.CreateDomain("MailChimpDomain");
string path = AppDomain.CurrentDomain.BaseDirectory + "ServiceStack_V3\\ServiceStack.Text.dll";
MailChimpDomain.Load(AssemblyName.GetAssemblyName(path));
EDIT2
Code 2:
var MailDom = AppDomain.CreateDomain("MailChimpDomain");
MailDom.AssemblyLoad += MailDom_AssemblyLoad;
MailDom.AssemblyResolve += new ResolveEventHandler(MailDom_AssemblyResolve);
MailDom.DoCallBack(() =>
{
string name = #"ServiceStack.Text.dll";
var assembly = AppDomain.CurrentDomain.Load(name);
string name2 = #"MailChimp.dll";
var assembly2 = AppDomain.CurrentDomain.Load(name2);
//mailChimp object with API key found in mailChimp profile
MailChimp.MailChimpManager mc = new MailChimp.MailChimpManager("111111111111222f984b9b1288ddf6f0-us1");
//After this line there are both versions of ServiceStack.Text Assembly
MailChimp.Helper.EmailParameter em = new MailChimp.Helper.EmailParameter();
em.Email = strEmailTo;
//Creating email parameters
string CampaignName = "Digest for " + strEmailTo + " " + DateTime.Now.ToShortDateString();
MailChimp.Campaigns.CampaignCreateOptions opt = new MailChimp.Campaigns.CampaignCreateOptions();
opt.ListId = "l338dh";
opt.Subject = strSubject;
opt.FromEmail = strEmailFrom;
opt.FromName = strNameFrom;
opt.Title = CampaignName;
//creating email content
MailChimp.Campaigns.CampaignCreateContent content = new MailChimp.Campaigns.CampaignCreateContent();
content.HTML = strEmailContent;
//Creating new email and sending it
MailChimp.Campaigns.CampaignFilter par = null;
MailChimp.Campaigns.CampaignSegmentOptions SegOpt = null;
MailChimp.Campaigns.CampaignTypeOptions typeOpt = null;
mc.CreateCampaign("regular", opt, content, SegOpt, typeOpt);
MailChimp.Campaigns.CampaignListResult camp2 = mc.GetCampaigns(par, 0, 5, "create_time", "DESC");
foreach (var item in camp2.Data)
{
if (item.Title == CampaignName)
{
mc.SendCampaign(item.Id);
break;
}
}
});
static Assembly MailDom_AssemblyResolve(object sender, ResolveEventArgs args)
{
byte[] rawAssembly = File.ReadAllBytes(Path.Combine(path, args.Name));
return Assembly.Load(rawAssembly);
}
What your code actually does is it loads assembly to your parent domain. If you want to load assembly into child domain you have to do it from inside child domain. This is kind of chicken-egg problem, because the parent assembly (which loads child assembly into child domain) has to be loaded into your child domain as well in order to be executed.
Assuming simple example that you have console application and assembly called MyAssembly.dll it can be done like this:
static void Main(string[] args) {
var domain = AppDomain.CreateDomain("MailChimpDomain");
domain.AssemblyResolve +=new ResolveEventHandler(domain_AssemblyResolve);
domain.DoCallBack(() => {
string path = #"MyAssembly.dll";
var assembly = AppDomain.CurrentDomain.Load(path);
// to do something with the assembly
var type = assembly.GetType("MailChimp.MailChimpManager");
var ctor = type.GetConstructor(new[] { typeof(string) });
var mc = ctor.Invoke(new object[] { "111111111111222f984b9b1288ddf6f0" });
});
}
static Assembly domain_AssemblyResolve(object sender, ResolveEventArgs args) {
byte[] rawAssembly = File.ReadAllBytes(Path.Combine(#"c:\MyAssemblyPath", args.Name));
return Assembly.Load(rawAssembly);
}
In this case child domain has same root directory for resolving assemblies as parent domain (and thus it can execute the code which loads "MyAssembly.dll").
If the code working with reflection is longer than this, you may consider using bootstrapper instead.
I.E. you create new library called MyBootstrapper.dll, you'll reference directly version of ServiceStack.Text.dll and MailChimp.dll you like from MyBootstrapper.dll, and you'll create bootstrap class - lets call it Bootstrapper that will be static and will have single public static method called Run that will do dirty work.
Then inside DoCallBack() method you'll call this bootstrapper instead.
string path = #"MyBootstrapper.dll";
var assembly = AppDomain.CurrentDomain.Load(path);
// to do something with the assembly
var type = assembly.GetType("MyBootstrapper.Bootstrapper");
var method = type.GetMethod("Run", BindingFlags.Static);
method.Invoke(null, null);
// or if the Run method has one parameter of "string" type
var method = type.GetMethod("Run", BindingFlags.Static, Type.DefaultBinder, new[] { typeof(string) }, null);
method.Invoke(null, new object[] { "Parameter to run" });
No, you may not unload the default AppDomain or any assemblies loaded in the default AppDomain.
What you can do, however, is load both versions of the ServiceStack assembly in two child domains. You should be able to unload either of them. However, using types from these domains may prove more difficult than usual. You'll have to do it via remoting.
Given the overhead imposed by this, you should consider using only one version of that assembly (even if this means adapting part of your app, the one that runs in the default domain).
I have a situation and I need to know how to deal with it in the best way.
I have an application (MVC3) and I have couple of integrations for it. I have an interface "IntegrationInterface" and every integration implements it.
I want to load the dlls of the integrations, create a list of them, and run a loop that runs a method for every integration in the list.
For example - let's say I have integrations for facebook, myspace and twitter (for my appliction), and every time the user post a message in my application I want to post a message on his\her facebook, myspace and twitter.
I don't want that the code will know which integrations I have, so if tomorrow I'll create a new integration for google+, I'll just need to add a new DLL without changing the code of my application.
How can I do that?
First, you'll have to find all relevant dlls and classes:
loadedIntegrations.Clear();
if (!Directory.Exists(path))
return;
DirectoryInfo di = new DirectoryInfo(path);
FileInfo[] files = di.GetFiles("*.dll");
foreach (var file in files)
{
Assembly newAssembly = Assembly.LoadFile(file.FullName);
Type[] types = newAssembly.GetExportedTypes();
foreach (var type in types)
{
//If Type is a class and implements the IntegrationInterface interface
if (type.IsClass && (type.GetInterface(typeof(IntegrationInterface).FullName) != null))
loadedIntegrations.Add(type);
}
}
loadedIntegrations is of type List<Type>. Then you can instantiate each integration and call its methods:
foreach(var integrationType in loadedIntegrations)
{
var ctor = integrationType.GetConstructor(new Type[] { });
var integration = ctor.Invoke(new object[] { }) as IntegrationInterface;
//call methods on integration
}
I am doing something similar to what you described in an import utility that wrote. My issue was that I didn't want to load ALL the assemblies. I only wanted to load assemblies that contained types that were requested.
To accomplish this I've used the AppDomain.CurrentDomain.AssemblyResolve event handler.
This event handler is raised just before the AppDomain throw an exception notifying that an assembly is not found. I execute similar code to what Nico suggested in that handler and return the requested assembly.
NOTE: I have a sub-directory called 'Tasks' (think Import Tasks) where I store all of my assemblies I want to load at runtime.
Here is the code:
var tasks = GetTasks();
var basedir = AppDomain.CurrentDomain.BaseDirectory; // Get AppDomain Path
var tasksPath = Path.Combine(basedir, "Tasks"); // append 'Tasks' subdir
// NOTE: Cannot be factored, relies on 'tasksPath' variable (above).
AppDomain.CurrentDomain.AssemblyResolve += (s, e) => // defined 'AssemblyResolve' handler
{
var assemblyname = e.Name + ".dll"; // append DLL to assembly prefix
// *expected* assembly path
var assemblyPath = Path.Combine(tasksPath, assemblyname); // create full path to assembly
if (File.Exists(assemblyPath)) return Assembly.LoadFile(assemblyPath); // Load Assembly as file.
return null; // return Null if assembly was not found. (throws Exception)
};
foreach (var task in tasks.OrderBy(q => q.ExecutionOrder)) // enumerate Tasks by ExecutionOrder
{
Type importTaskType = Type.GetType(task.TaskType); // load task Type (may cause AssemblyResolve event to fire)
if (importTaskType == null)
{
log.Warning("Task Assembly not found");
continue;
}
ConstructorInfo ctorInfo = importTaskType.GetConstructor(Type.EmptyTypes); // get constructor info
IImportTask taskInstance = (IImportTask)ctorInfo.Invoke(new object[0]); // invoke constructor and cast as IImportTask
taskInstances.Add(taskInstance);
}
// rest of import logic omitted...
If u know MEF (Managed extensibility framework) this might help you I personally use it but have to say that using MEF with MVC is not trivial i think for more information please visit
http://msdn.microsoft.com/en-us/library/dd460648.aspx