How to compile C # DLLs on the fly, download and use

A) compiling C # EXE and DLL on the fly is relatively simple.
B) Executing an EXE means that a new application is running. DLL loading means that methods and functions can be used in cases that can be shared between applications or projects.

Now the fastest and easiest way to compile the EXE (or with minor modifications, the DLL) can be found in MSDN or for your convenience:

private bool CompileCSharpCode(string script) { lvErrors.Items.Clear(); try { CSharpCodeProvider provider = new CSharpCodeProvider(); // Build the parameters for source compilation. CompilerParameters cp = new CompilerParameters { GenerateInMemory = false, GenerateExecutable = false, // True = EXE, False = DLL IncludeDebugInformation = true, OutputAssembly = "eventHandler.dll", // Compilation name }; // Add in our included libs. cp.ReferencedAssemblies.Add("System.dll"); cp.ReferencedAssemblies.Add("System.Windows.Forms.dll"); cp.ReferencedAssemblies.Add("Microsoft.VisualBasic.dll"); // Invoke compilation. This works from a string, but you can also load from a file using FromFile() CompilerResults cr = provider.CompileAssemblyFromSource(cp, script); if (cr.Errors.Count > 0) { // Display compilation errors. foreach (CompilerError ce in cr.Errors) { //I have a listview to display errors. lvErrors.Items.Add(ce.ToString()); } return false; } else { lvErrors.Items.Add("Compiled Successfully."); } provider.Dispose(); } catch (Exception e) { // never really reached, but better safe than sorry? lvErrors.Items.Add("SEVERE! "+e.Message + e.StackTrace.ToString()); return false; } return true; } 

Now that you can compile on the fly, there are several differences between loading the DLL. Typically, you add it as a link in Visual Studios to compile into a project. This is pretty easy, and you probably did it many times, but we want to use it in our current project, and we cannot really require the user to recompile the whole project every time they want to test their new DLL, so I’ll just Discuss how to download the library on the fly. Another term here would be "software." To do this, after successful compilation, we load the assembly as follows:

 Assembly assembly = Assembly.LoadFrom("yourfilenamehere.dll"); 

If you have an AppDomain, you can try the following:

 Assembly assembly = domain.Load(AssemblyName.GetAssemblyName("yourfilenamehere.dll")); 


Now that lib is “referenced”, we can open and use it. There are two ways to do this. One requires you to know if the method has parameters, the other will check you. I will do it later, you can check the MSDN for another.

 // replace with your namespace.class Type type = assembly.GetType("company.project"); if (type != null) { // replace with your function name MethodInfo method = type.GetMethod("method"); if (method != null) { object result = null; ParameterInfo[] parameters = method.GetParameters(); object classInstance = Activator.CreateInstance(type, null); if (parameters.Length == 0) // takes no parameters { // method A: result = method.Invoke(classInstance, null); // method B: //result = type.InvokeMember("method", BindingFlags.InvokeMethod, null, classInstance, null); } else // takes 1+ parameters { object[] parametersArray = new object[] { }; // add parameters here // method A: result = method.Invoke(classInstance, parametersArray); // method B: //result = type.InvokeMember("method", BindingFlags.InvokeMethod, null, classInstance, parametersArray); } } } 

PROBLEM: The first compiler works fine. The first performance works fine. However, an attempt to recompile will be a mistake stating that your * .PDP (debugger base) is being used. I heard some hints of marshaling and AppDomains, but I did not quite understand the problem. Re-compilation will fail only after loading the DLL.


Current attempt by Marshaling && AppDomain:

 class ProxyDomain : MarshalByRefObject { private object _instance; public object Instance { get { return _instance; } } private AppDomain _domain; public AppDomain Domain { get { return _domain; } } public void CreateDomain(string friendlyName, System.Security.Policy.Evidence securityinfo) { _domain = AppDomain.CreateDomain(friendlyName, securityinfo); } public void UnloadDomain() { try { AppDomain.Unload(_domain); } catch (ArgumentNullException dne) { // ignore null exceptions return; } } private Assembly _assembly; public Assembly Assembly { get { return _assembly; } } private byte[] loadFile(string filename) { FileStream fs = new FileStream(filename, FileMode.Open); byte[] buffer = new byte[(int)fs.Length]; fs.Read(buffer, 0, buffer.Length); fs.Close(); return buffer; } public void LoadAssembly(string path, string typeName) { try { if (_domain == null) throw new ArgumentNullException("_domain does not exist."); byte[] Assembly_data = loadFile(path); byte[] Symbol_data = loadFile(path.Replace(".dll", ".pdb")); _assembly = _domain.Load(Assembly_data, Symbol_data); //_assembly = _domain.Load(AssemblyName.GetAssemblyName(path)); _type = _assembly.GetType(typeName); } catch (Exception ex) { throw new InvalidOperationException(ex.ToString()); } } private Type _type; public Type Type { get { return _type; } } public void CreateInstanceAndUnwrap(string typeName) { _instance = _domain.CreateInstanceAndUnwrap(_assembly.FullName, typeName); } } 

Errors on _instance = _domain.CreateInstanceAndUnwrap (_assembly.FullName, typeName); saying that my Assembly is not serializable. I tried adding the [Serializable] tag to my class with no luck. Still exploring corrections.

It seems that things can get a little confused when you cannot see how they are used, so making it easier?

 private void pictureBox1_Click(object sender, EventArgs e) { pd.UnloadDomain(); if (CompileCSharpCode(header + tScript.Text + footer)) { try { pd.CreateDomain("DLLDomain", null); pd.LoadAssembly("eventHandler.dll", "Events.eventHandler"); pd.CreateInstanceAndUnwrap("Events.eventHandler"); // Assembly not Serializable error! /*if (pd.type != null) { MethodInfo onConnect = pd.type.GetMethod("onConnect"); if (onConnect != null) { object result = null; ParameterInfo[] parameters = onConnect.GetParameters(); object classInstance = Activator.CreateInstance(pd.type, null); if (parameters.Length == 0) { result = pd.type.InvokeMember("onConnect", BindingFlags.InvokeMethod, null, classInstance, null); //result = onConnect.Invoke(classInstance, null); } else { object[] parametersArray = new object[] { }; //result = onConnect.Invoke(classInstance, parametersArray); //result = type.InvokeMember("onConnect", BindingFlags.InvokeMethod, null, classInstance, parametersArray); } } }*/ //assembly = Assembly.LoadFrom(null); } catch (Exception er) { MessageBox.Show("There was an error executing the script.\n>" + er.Message + "\n - " + er.StackTrace.ToString()); } finally { } } } 
+3
source share
2 answers

Once you have loaded the DLL into the (default appdomain) running process, the file on disk cannot be overwritten until the process is complete. DLLs cannot be unloaded in managed code, as they can be in unmanaged code.

You need to create a new application in the host process and load the newly created DLL assembly into this application. When you are ready to compile the new version of the DLL, you can get rid of the application. This will unload the DLL from memory and release the lock of the DLL file so that you can compile the new DLL into the same file. You can then create a new appdomain to load the new DLL.

The main danger of using appdomains is that all calls at the application boundary must be distributed, like an IPC or RPC network. Try to keep the interface of the objects you need in order to bring the application border to a minimum.

You can also compile the assembly into memory, get an array of bytes or a stream as output, and then load that assembly into a separate appdomain. This avoids the formation of garbage on the disk, which ultimately will need to be removed.

Do not use compilation in memory as a workaround for file locking issues. The main problem is that assemblies cannot be deleted from memory when they are loaded into a standard domain application. You MUST create a new appdomain and load the DLL into this appdomain if you want to unload this assembly from memory later in the process.

Here is a rough outline of building an object in the context of another appdomain:

  var appdomain = AppDomain.CreateDomain("scratch"); byte[] assemblyBytes = // bytes of the compiled assembly var assembly = appdomain.Load(assemblyBytes); object obj = appdomain.CreateInstanceAndUnwrap(assembly.FullName, "mynamespace.myclass"); 

After this sequence, obj will contain a link to the proxy server, which refers to an instance of the actual object inside the application. You can reference obj methods using reflection or the obj type on a common interface type and call methods directly. Be prepared to make adjustments to support RPC sorting of method call parameters. (see the section "Remote Access to .NET")

When working with multiple application domains, you should be careful about how you access types and assemblies, because many .NET functions work by default in the current domain of the calling application, which is usually not what you want when you have several areas of the application, compilerResult.CompiledAssembly , for example, internally loads the generated assembly in the appdomain declaration . You want to load the assembly into another area of ​​the application. You must do this explicitly.

Update: In a recently added code snippet showing how you download your appdomain, this line is your problem:

  _assembly = Assembly.LoadFrom(path); 

This loads the DLL into the current appdomain (calling appdomain), and not into the target area of ​​the application (link to _domain in your example). You need to use _domain.Load() to load the assembly into this application.

+8
source

unless you need to debug or disregard the debugging of "dynamic" code with some missing information. you can generate code in memory .. this will allow you to compile the code several times .. but will not generate .pdb

 cp.GenerateInMemory = true; 

in the alternative case, if you do not need to find the assembly on disk, you can ask the compiler to dump all the code in the temp directory and create a temporary name for the dll (which will always be unique)

 cp.TempFiles = new TempFileCollection(Path.GetTempPath(), false); //cp.OutputAssembly = "eventHandler.dll"; 

in both cases, to access the dll and its types, you can get it from compilation results

 Assembly assembly = cr.CompiledAssembly; 

no explicit download required

but if these situations do not apply, and you must have a physical .dll with .pdp in a known folder. The only advice I can give you is to put the version number in the dll. and in case you don’t have an easy way to control the amount of time the dll was compiled, you can always resort to a timestamp.

 cp.OutputAssembly = "eventHandler"+DateTime.Now.ToString("yyyyMMddHHmmssfff")+".dll"; 

Of course, you should understand that every time you compile a new .dll, it is loaded into memory and will not be unloaded if you do not use separate application domains ... but this is beyond the scope of this question ..

+1
source

Source: https://habr.com/ru/post/972601/


All Articles