I have a solution in VS2013, the solution has 2 projects, projA and projB.
In projA, the startup project, I wrote some code to compile C# text files at runtime, and my code actually works well when linking it to namespaces inside of the projA running it.
But when I try to link it to namespaces inside of projB, for example doing something like using namespaceInProjB, it says that they dont exist. I thought they would also be found in the current domains assemblies?
Does anyone know how to include them as well? Here is my code:
CodeDomProvider provider = new CSharpCodeProvider();
CompilerParameters compilerParams = new CompilerParameters();
compilerParams.CompilerOptions = "/target:library";
compilerParams.GenerateExecutable = false;
compilerParams.GenerateInMemory = true;
compilerParams.IncludeDebugInformation = false;
compilerParams.ReferencedAssemblies.Add("mscorlib.dll");
var assemblies = AppDomain.CurrentDomain
.GetAssemblies()
.Where(a => !a.IsDynamic)
.Select(a => a.Location);
foreach (string str in assemblies.ToArray<string>())
compilerParams.ReferencedAssemblies.Add(str);
CompilerResults result = provider.CompileAssemblyFromSource(compilerParams, scriptCode);
// Write out all the errors
if (result.Errors.Count > 0)
{
errors = result.Errors;
return null;
}
return result.CompiledAssembly;
Make sure classes you use in projB are public, because by default classes are internal and you can't use them in another project in projA in your case.
Related
I was looking to load a usercontrol from a folder. I want people to be able to package an extension for my app. To do this, they'll have to create a c# user control and put the designer, code, and resx file into a folder. Then when they want to use their "extension", they'll select a folder from a folder selector (I have that) and my app will load their extension. I want to specifically pull out the user control and cast it into a usercontrol object. Am I able to do this and if so, how?
I looked around the internet and there doesn't really seem to be any close to this question. I wasn't able to create any script that could accomplish this. I don't even know where to start in this one. I know that I have to compile their usercontrol.
If this is not possible, the next best solution I can think of is maybe a precompiled usercontrol. If this is possible, how would I load that?
Any help would be appreciated. Thanks!
If you wish to compile the sources, this can be done using the System.CodeDom. Other than that you should load the types from the assembly and test to see if there is a UserControl in there, load it up and add it to your form.
Here is some example of what i described:
public void LoadPlugin(params string[] sourceCodeFilesForUserControl)
{
// Compile the source files
CSharpCodeProvider codeProvider = new CSharpCodeProvider();
CompilerParameters parameters = new CompilerParameters();
parameters.IncludeDebugInformation = true;
parameters.GenerateInMemory = true;
// Add references that they can use
parameters.ReferencedAssemblies.Add("System.dll");
parameters.ReferencedAssemblies.Add("System.Core.dll");
parameters.ReferencedAssemblies.Add("System.Windows.Forms.dll"); // important for UserControl
parameters.TreatWarningsAsErrors = false;
CompilerResults results = codeProvider.CompileAssemblyFromSource(parameters, sourceCodeFilesForUserControl);
if (results.Errors.Count > 0)
{
// Handle compile errors
StringBuilder sb = new StringBuilder();
foreach (CompilerError CompErr in results.Errors)
{
sb.AppendLine("Line number " + CompErr.Line +
", Error Number: " + CompErr.ErrorNumber +
", '" + CompErr.ErrorText + ";");
}
Console.Write(sb.ToString());
}
else
{
// The assembly we can search for a usercontrol
var assembly = results.CompiledAssembly;
// If the assembly was already compiled you might want to load it directly:
// assembly = Assembly.LoadFile(#"C:\Program Files\MyTool\plugins\someplugin.dll");
// Get the first type in the assembly that is a UserControl
var userControl = assembly.GetTypes().FirstOrDefault(x => x.BaseType == typeof(UserControl));
// Create a instance of the UserControl
var createdUserControl = Activator.CreateInstance(userControl, new object[] { }) as UserControl;
// Add the created UserControl to the current form
this.Controls.Add(createdUserControl);
}
}
I have an application that can be customized by writing custom extensions. All of them are in proj\Extensions folder. At runtime my Core project loads every extension from the folder and executes code. The problem is when one of the extensions uses additional libraries, because the Core project cannot find reference to these additional libraries.
So for example in my Core project I have:
public void Preview(IFileDescription fileDescription)
{
var extension = Path.GetExtension(fileDescription.FilePath);
var reader = _readerFactory.Get(extension);
Data = reader.GetPreview(fileDescription);
}
In one of my extensions I have
public DataTable GetPreview(IFileDescription options)
{
var data = new DataTable();
using (var stream = new StreamReader(options.FilePath))
{
var reader = new CsvReader(stream); // <- This is from external library and because of this Core throws IO exception
}
/*
...
*/
return data;
}
Core only knows about the interface, so when one of readers uses for example CsvHelper.dll I get exception of FileNotFound, because Core cannot find CsvHelper.dll. Is there any way to tell the compiler to look for additional libraries in specific folder? I used Reference Paths, but it didn't solve the problem. It still threw the same exception.
Yes, it's possible. You can attach to the AppDomain.AssemblyResolve event and manually load the required DLLs from your add-in directory. Execute the following code before executing any add-in code:
var addinFolder = ...;
AppDomain.CurrentDomain.AssemblyResolve += (sender, e) =>
{
var missing = new AssemblyName(e.Name);
var missingPath = Path.Combine(addinFolder, missing.Name + ".dll");
// If we find the DLL in the add-in folder, load and return it.
if (File.Exists(missingPath))
return Assembly.LoadFrom(missingPath);
// nothing found, let .NET search the common folders
return null;
};
so what I am creating dll type files, running them and then I want to delete them.
However when I try to delete them I get an exception as it claims they are still in use by another process.
I assuming that the code used to create the files does not dispose of resources correctly to allow the deleting of the file after, here is my code to create the file.
if (!Directory.Exists(PathToRobots + Generation))
{
Directory.CreateDirectory(PathToRobots + Generation);
}
File.WriteAllText(Path.Combine(PathToRobots + Generation, NameSpace + GetRobotName() + robotNumber + ".cs"), code);
CSharpCodeProvider provider = new CSharpCodeProvider();
CompilerParameters parameters = new CompilerParameters()
{
GenerateInMemory = false,
GenerateExecutable = false, // True = EXE, False = DLL
IncludeDebugInformation = true,
OutputAssembly = Path.Combine(FileName + ".dll") // Compilation name
};
parameters.ReferencedAssemblies.Add(#"robocode.dll");
CompilerResults results = provider.CompileAssemblyFromSource(parameters, code);
if (results.Errors.HasErrors)
{
StringBuilder sb = new StringBuilder();
foreach (CompilerError error in results.Errors)
{
sb.AppendLine(String.Format("Error ({0}): {1}", error.ErrorNumber, error.ErrorText));
}
throw new InvalidOperationException(sb.ToString());
}
Assembly assembly = results.CompiledAssembly;
provider.Dispose();
The code to delete the file is quite simple and is as follows,
var files = Directory.GetFiles(DirectoryPath);
foreach (var file in files)
{
File.Delete(file);
}
Any idea as to why I can't delete the files?
See the notes at CompilerResults.CompiledAssembly Property
The get accessor for the CompiledAssembly property calls the Load method to load the compiled assembly into the current application domain. After calling the get accessor, the compiled assembly cannot be deleted until the current AppDomain is unloaded.
So when you do this:
Assembly assembly = results.CompiledAssembly;
You have loaded the compiled assembly into the current app domain and thus have locked the file. To be able to delete the generated file, you would need to load it into a separate app domain (this answer may help with specifics for doing that).
string code = System.IO.File.ReadAllText(path);
CSharpCodeProvider codeProvider = new CSharpCodeProvider();
CompilerParameters options = new CompilerParameters();
options.GenerateExecutable = false;
options.GenerateInMemory = true;
options.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().Location);
CompilerResults result;
result = codeProvider.CompileAssemblyFromSource(options, code);
LastErrors = result.Errors;
if (result.Errors.HasErrors)
{
throw new Exception("Compiling the code has returned exceptions!\r\nCheck LastErrors for details.");
}
return result.CompiledAssembly;
is my code for compiling C# code into an assembly.
But instead, I would like to somehow compile all the files in a directory into that assembly.
Can you not just use CompileAssemblyFromFile instead? That allows you to specify multiple filenames.
I have an application which loads up c# source files dynamically and runs them as plugins. When I am running the main application in debug mode, is it possible to debug into the dynamic assembly? Obviously setting breakpoints is problematic, since the source is not part of the original project, but should I be able to step into, or break on exceptions for the code?
Is there a way to get codedom to generate PDBs for this or something?
Here is the code I am using for dynamic compliation.
CSharpCodeProvider codeProvider = new CSharpCodeProvider(new Dictionary<string, string>() { { "CompilerVersion", "v3.5" } });
//codeProvider.
ICodeCompiler icc = codeProvider.CreateCompiler();
CompilerParameters parameters = new CompilerParameters();
parameters.GenerateExecutable = false;
parameters.GenerateInMemory = true;
parameters.CompilerOptions = string.Format("/lib:\"{0}\"", Application.StartupPath);
parameters.ReferencedAssemblies.Add("System.dll");
parameters.ReferencedAssemblies.Add("System.Core.dll");
CompilerResults results = icc.CompileAssemblyFromSource(parameters, Source);
DLL.CreateInstance(t.FullName, false, BindingFlags.Default, null, new object[] { engine }, null, null);
Try the following options:
parameters.GenerateInMemory = false; //default
parameters.TempFiles = new TempFileCollection(Environment.GetEnvironmentVariable("TEMP"), true);
parameters.IncludeDebugInformation = true;
I am not sure if this works OK in your case, but if it does, you can surround this parameters with conditional compilation directive, so that it dumps the generated assembly only in debug mode.
The answer by #bbmud is correct, though it misses one bug fix. The CSharpCodeGenerator (the class in .NET the compiles C# code to IL) is set to remove pdb files immediately after they are created, UNLESS you add /debug:pdbonly to the CompilerOptions string. However, if you do that, the IncludeDebugInformation flag is ignored and the compiler generates optimised code which is hard to debug. To avoid this you must explicitly tell the Code Generator to keep all files.
Here is the complete recipe:
parameters.GenerateInMemory = false; //default
parameters.TempFiles = new TempFileCollection(Environment.GetEnvironmentVariable("TEMP"), true);
parameters.IncludeDebugInformation = true;
parameters.TempFiles.KeepFiles = true
Here is the culprit part of the code of CSharpCodeGenerator:
string fileExtension = "pdb";
if ((options.CompilerOptions != null) && (CultureInfo.InvariantCulture.CompareInfo.IndexOf(options.CompilerOptions, "/debug:pdbonly", CompareOptions.IgnoreCase) != -1))
{
results.TempFiles.AddExtension(fileExtension, true);
}
else
{
results.TempFiles.AddExtension(fileExtension);
}
The TempFiles.AddExtension(fileExtension, true) tells the compile to keep the pdb files. The else option of results.TempFiles.AddExtension(fileExtension); tells it to treat pdb as all temporary files which by default means delete them.