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);
}
}
Related
I want to make a reloadable assembly function for scripting.(So i can debug scripts quicker)
The dll generation works and loading too. The main problem is, that I`m using the functions of my temporary AppDomain in my main AppDomain. The dll seems to be linked to my main AppDomain too, because I cant delete it while the program is running.
If I remove all MethodInfo references from my main AppDomain "context" then I have no problems deleting it.
Here you can see how the program works:
Generate DLL from external process
Load DLL by (temp AppDomain).DoCallBack(...)
Get Type & MethodInfo and call it.
AppDomain.Unload(temp AppDomain)
So if I skip step 3, I have no problems deleten the dll. But I cant check if the return of the Function really shows the updated value (which i edit inside the script).
I have posted the source code for each step here:
1.
//This actually isn`t as important for you
Process assemblyGeneration = new Process();
assemblyGeneration.StartInfo.FileName = AppDomain.CurrentDomain.BaseDirectory + #"lib\AssemblyCompiler.exe";
assemblyGeneration.StartInfo.Arguments = "\"" + AppDomain.CurrentDomain.BaseDirectory + "script.dll\" \"" + source + "\" \"System.dll\"";
assemblyGeneration.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
assemblyGeneration.StartInfo.UseShellExecute = true;
assemblyGeneration.Start();
assemblyGeneration.WaitForExit();
assemblyGeneration.Dispose();
2.
_appDomain.DoCallBack(() =>
{
byte[] fileContent = File.ReadAllBytes(AppDomain.CurrentDomain.BaseDirectory + "script.dll");
AppDomain.CurrentDomain.Load(fileContent);
});
3.
MethodInfo info = _compiledScript.GetTypeFrom("LSCT.ScriptClass").GetMethod("DefineX");
var func = (Func<double>) Delegate.CreateDelegate(typeof(Func<double>), info);
double x = func();
Console.WriteLine("Output was : " + x);
4.
AppDomain.Unload(_appDomain);
So is there a way to work around the issue, that I cant reload the DLL?
I think I have solved the problem. Maybe there is a performance issue now but I have not checked it yet.
Anyways, I created a new class which inherit MarshalByRefObject and let it load from the temp AppDomain.
Here is the code for the new class:
public class ProcessRunner : MarshalByRefObject
{
public void Run()
{
Type typ = null;
foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
{
Type t = asm.GetType("LSCT.ScriptClass");
if (t != null)
{
typ = t;
break;
}
}
MethodInfo info = typ.GetMethod("DefineX");
var func = (Func<double>)Delegate.CreateDelegate(typeof(Func<double>), info);
double x = func();
Console.WriteLine("Output was : " + x);
}
}
And here is how to invoke it from the temp AppDomain:
ProcessRunner pr = (ProcessRunner)_appDomain.CreateInstanceFromAndUnwrap(Assembly.GetExecutingAssembly().Location, "Animation_Engine.UserInterface.Code.ProcessRunner");
pr.Run();
Note: As soon as you pass any references from your main AppDomain (by
parameters for example) to the executing class, the dll will be linked to your main AppDomain
and is not able to unload until the main AppDomain ends.
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).
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.
I have developed an application that uses c# script files for certain configurations and settings. The script file contains various user generated objects and certain functions on those objects. Presently, the user has to generate a .cs file using a third party editor and supply the path to my program to make use of it. The disadvantage with this method is that the user does not have the flexibility of Auto-complete and intellisense-esque support while editing the script files.
I want to embed the script editing part into my application. I can do that using a rich-text editor. But coding the auto-complete part is a huge pain. Is there any way in which I can provide the user with an in-program editor that also does auto-complete....
Code for compiling a script dynamically in a program.
public String Compile(String inputfilepath)
{
CompilerResults res = null;
CSharpCodeProvider provider = new CSharpCodeProvider();
String errors = "";
if (provider != null)
{
try
{
Assembly asb = Assembly.Load("BHEL.PUMPSDAS.Datatypes, Version=1.0.0.0, Culture=neutral, PublicKeyToken=81d3de1e03a5907d");
CompilerParameters options = new CompilerParameters();
options.GenerateExecutable = false;
options.OutputAssembly = String.Format(outFileDir + oName);
options.GenerateInMemory = false;
options.TreatWarningsAsErrors = false;
options.ReferencedAssemblies.Add("System.dll");
options.ReferencedAssemblies.Add("System.Core.dll");
options.ReferencedAssemblies.Add("System.Xml.dll");
options.ReferencedAssemblies.Add("System.Xml.dll");
options.ReferencedAssemblies.Add(asb.Location);
res = provider.CompileAssemblyFromFile(options, inputfilepath);
errors = "";
if (res.Errors.HasErrors)
{
for (int i = 0; i < res.Errors.Count; i++)
{
errors += "\n " + i + ". " + res.Errors[i].ErrorText;
}
}
}
catch (Exception e)
{
throw (new Exception("Compilation Failed with Exception!\n" + e.Message +
"\n Compilation errors : \n" + errors + "\n"));
}
}
return errors;
}
Specifically for auto-complete, you will need to make use of two systems: a parser, and reflection.
A parser is a pretty straightforward concept, in theory, but I'm sure that it won't be easy to write for a language with as much syntactic sugar and as many context-sensitive keywords as C#.
Since .NET is inherently reflective, and provides a reflection framework, that part shouldn't be incredibly painful, either. Reflection allows you to manipulate the object-oriented elements comprising compiled assemblies--and the assemblies themselves--as objects. A method would be a Method object, for example. You can take a peek at this system by examining the members of the Type class, which provide one basic starting point for reflection. Another useful starting point is Assembly. MSDN, as usual, has a wealth of "official" information in a structured format.
I'm writing a small application which works at compiling a file from a source (.cs) code file using a function:
public static bool CompileExecutable(String sourceName)
{
//Source file that you are compliling
FileInfo sourceFile = new FileInfo(sourceName);
//Create a C# code provider
CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp");
//Create a bool variable for to to use after the complie proccess to see if there are any erros
bool compileOk = false;
//Make a name for the exe
String exeName = String.Format(#"{0}\{1}.exe",
System.Environment.CurrentDirectory, sourceFile.Name.Replace(".", "_"));
//Creates a variable, cp, to set the complier parameters
CompilerParameters cp = new CompilerParameters();
//You can generate a dll or a exe file, in this case we'll make an exe so we set this to true
cp.GenerateExecutable = true;
//Set the name
cp.OutputAssembly = exeName;
//Save the exe as a physical file
cp.GenerateInMemory = false;
//Set the compliere to not treat warranings as erros
cp.TreatWarningsAsErrors = false;
//Make it compile
CompilerResults cr = provider.CompileAssemblyFromFile(cp, sourceName);
//if there are more then 0 erros...
if (cr.Errors.Count > 0)
{
//A message box shows the erros that occured
MessageBox.Show("Errors building {0} into {1}" +
sourceName + cr.PathToAssembly);
//for each error that occured in the code make a separete message box
foreach (CompilerError ce in cr.Errors)
{
MessageBox.Show(" {0}" + ce.ToString());
}
}
//if there are no erros...
else
{
//a message box shows compliere results and a success message
MessageBox.Show("Source {0} built into {1} successfully." +
sourceName + cr.PathToAssembly);
}
//if there are erros...
if (cr.Errors.Count > 0)
{
//the bool variable that we made in the beggining is set to flase so the functions returns a false
compileOk = false;
}
//if there are no erros...
else
{
//we are returning a true (success)
compileOk = true;
}
//return the result
return compileOk;
}
What i want to achieve is to add user-selected file resources ( images, mp3, avi, ttf,..etc ) which will be added as embedded resources to the being compiled using the function above.
how can we add embedded resources to a file which is compiled from a source file at run-time ?
You'll need to create a .resources file containing the files you want to embed as resources, and then reference the generated resource file in your CompilerParameter's instance using the EmbeddedResources property.
Follow the directions from Resources in .resources Files section of the first link above (the section that refers to System.Resources.ResourceWriter), which will produce a temporary file. Then based on the code in your question (and the example from the EmbeddedResources documentation), you'll reference it with something like:
if (provider.Supports(GeneratorSupport.Resources))
{
cp.EmbeddedResources.Add("pathToYourGeneratedResourceFile");
}