如何编译C#DLL即时,加载和使用 [英] How to compile C# DLL on the fly, Load, and Use

查看:363
本文介绍了如何编译C#DLL即时,加载和使用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

A)编译C#EXE和DLL的运行相对容易。

B)执行EXE意味着运行一个新的应用程序。加载DLL意味着方法和函数可以在可以在应用程序或项目之间共享的情况下使用。



现在,最快捷,最简单的编译EXE的方法轻微修改,DLL)可以从 MSDN中找到或您的方便:

A) compiling C# EXE's and DLL's on the fly are relatively easy.
B) Executing an EXE means that a new application is run. Loading a DLL means that methods and functions can be used in cases that may be shared between applications or projects.

Now, the quickest and easiest way to compile your EXE (or with mild modifications, DLL) can be found from the 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;
}

现在可以即时编译了,加载DLL。通常来说,您可以将其作为Visual Studio中的参考添加到项目中。这是相当容易,你可能做了很多次,但我们想在我们当前的项目中使用它,我们不能很好地要求用户重新编译整个项目每次他们想测试出他们的新DLL 。因此,我将简单讨论如何加载图书馆飞行。这里的另一个术语是以编程方式。为此,在成功编译之后,我们按如下方式加载程序集:

Now that you can compile on the fly, there are a few variances between how to load the DLL. Typically speaking, you would add it as a reference in Visual Studios to be compiled into the project. This is rather easy and you have probably done it many times over, but we want to use it in our current project, and we can't very well require the user to recompile the entire project every time they want to test out their new DLL. Therefor, I will simply discuss how one loads a library 'on the fly'. Another term here would by "programmatically". To do this, after a successful compile, we load up an Assembly as follows:

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

如果您有AppDomain,可以尝试以下操作:

If you have an AppDomain, you can try this:

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



现在lib是引用的,我们可以打开它并使用它。有2种方法可以做到这一点。一个需要你知道方法是否有参数,另一个将检查你。我稍后会做,您可以检查 MSDN 的其他。

// replace with your namespace.class
Type type = assembly.GetType("company.project");
if (type != null)
{
    // replace with your function's 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);
        }
    }
}

问题:
首先编译工程好。第一次执行工作正常。但是,重新编译尝试将会报错,说您的* .PDP(调试器数据库)正在使用中。我听说过关于封送和一些关于AppDomains的提示,但我还没有完全清除这个问题。重新编译只会在加载DLL后失败。

PROBLEM: First compile works fine. First execution works fine. However, the recompile attempt will error, saying that your *.PDP (debugger database) is in use. I've heard some hints about marshaling, and AppDomains, but I haven't quite cleared up the problem. Recompile will only fail after the DLL has been loaded.

当前尝试在Marshaling& AppDomain:

Current attempt at 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);
        }
    }

错误_instance = _domain.CreateInstanceAndUnwrap(_assembly.FullName ,typeName);说我的大会是不可序列化的。试图添加[Serializable]标签到我的类没有运气。仍在研究修正。

Errors on _instance = _domain.CreateInstanceAndUnwrap(_assembly.FullName, typeName); saying that my Assembly isn't serializable. Tried adding [Serializable] tag to my class with no luck. Still researching fixes.

看起来有些混乱,当你看不到他们是如何使用,所以这里是容易吗?

Seems things can get a bit confusing when you can't see how they're being used, so here's making it easy?

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
            {
            }
        }
    }


推荐答案

将DLL加载到正在运行的进程中(默认的appdomain)后,磁盘上的文件不能被覆盖,直到进程终止。 DLL不能在托管代码中卸载,因为它们可以是非托管代码。

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

您需要在主机进程中创建一个新的appdomain,并将新创建的DLL程序集加载到appdomain。当您准备好编译新版本的DLL时,您可以处置appdomain。这将从内存中卸载DLL并释放DLL文件上的锁,以便您可以编译一个新的DLL到同一个文件。然后你可以构造一个新的appdomain来加载新的DLL到

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

使用appdomains的主要危险是,跨appdomain边界的所有调用必须编组,很像一个IPC或网络RPC。尝试保持您需要跨越appdomain边界调用的对象的接口最小。

The main hazard of using appdomains is that all calls across the appdomain boundary must be marshalled, much like an IPC or network RPC. Try to keep the interface of the objects you need to call across the appdomain boundary to a minimum.

您还可以将程序集编译到内存,接收字节数组或流作为输出,然后将该程序集加载到单独的appdomain中。这避免在磁盘上创建最终需要删除的碎片。

You can also compile the assembly to memory, receiving a byte array or stream as the output, then load that assembly into the separate appdomain. This avoids creating debris on disk that will need to be deleted eventually.

不要使用编译到内存作为文件锁问题的解决方法。核心问题是,当程序加载到进程的默认应用程序域时,程序集不能从内存中删除。您必须创建一个新的appdomain,并将DLL加载到该appdomain中,如果您要在进程的生命周期内从内存中卸载该程序集。

Do not use compile to memory as a workaround for the file lock issue. The core issue is that assemblies cannot be removed from memory when they are loaded into the default appdomain of the process. You MUST create a new appdomain and load the DLL into that appdomain if you want to unload that assembly from memory later in the lifetime of the process.

这里是一个粗略的大纲如何在另一个appdomain的上下文中构造一个对象:

Here's a rough outline of how to construct 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");

在此序列之后, obj 引用到链接到appdomain中的实际对象实例的代理。你可以使用反射或类型转换obj对一个通用的接口类型调用obj上的方法,并直接调用方法。准备进行调整以支持RPC编组的方法调用参数。 (参见.NET remoting)

After this sequence, obj will contain a reference to a proxy that links to the actual object instance inside the appdomain. You can invoke methods on obj using reflection or typecast obj to a common interface type and call methods directly. Be prepared to make adjustments to support RPC marshalling of the method call parameters. (see .NET remoting)

在使用多个应用程序域时,必须小心如何访问类型和程序集,因为许多.NET函数默认在当前appdomain的调用者,这通常不是你想要的,当你有多个appdomains。 compilerResult.CompiledAssembly ,例如,在调用者的appdomain 中内部执行生成的程序集的加载。你想要的是将程序集加载到你的其他appdomain。

When working with multiple appdomains, you have to be careful how you access types and assemblies because a lot of .NET functions default to operating in the current appdomain of the caller, which is usually not what you want when you have multiple appdomains. compilerResult.CompiledAssembly, for example, internally performs a Load of the generated assembly in the caller's appdomain. What you want is to load the assembly into your other appdomain. You have to do that explicitly.

更新:
在您最近添加的代码段中显示如何加载应用程序域,此行是您遇到的问题:

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

 _assembly = Assembly.LoadFrom(path);

将DLL加载到当前 appdomain(调用者的appdomain)不会进入目标appdomain(在您的示例中由_domain引用)。您需要使用 _domain.Load()将程序集加载到 appdomain。

That loads the DLL into the current appdomain (the caller's appdomain), not into the target appdomain (referenced by _domain in your example). You need to do use _domain.Load() to load the assembly into that appdomain.

这篇关于如何编译C#DLL即时,加载和使用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆