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

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

问题描述

A) 动态编译 C# EXE 和 DLL 相对容易.
B) 执行一个 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 Studios 中的引用以编译到项目中.这相当容易,您可能已经做过很多次了,但是我们想在我们当前的项目中使用它,而且我们不能很好地要求用户每次想要测试他们的新 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"));


现在该库已被引用",我们可以打开它并使用它.有两种方法可以做到这一点.一个要求您知道该方法是否有参数,另一个将检查您.我稍后再做,其他的你可以查看 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.

目前对编组和&的尝试应用域:

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.
>" + er.Message + "
   - " + er.StackTrace.ToString());
            }
            finally
            {
            }
        }
    }

推荐答案

一旦您将 DLL 加载到正在运行的进程(的默认应用程序域)中,磁盘上的文件在进程终止之前不能被覆盖.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.

您需要在宿主进程中创建一个新的应用程序域,并将新创建的 DLL 程序集加载到该应用程序域中.当您准备好编译新版本的 DLL 时,您可以处理 appdomain.这将从内存中卸载 DLL 并释放对 DLL 文件的锁定,以便您可以将新的 DLL 编译到同一文件中.然后,您可以构建一个新的应用程序域来将新的 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.尽量减少需要跨应用域边界调用的对象的接口.

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.

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

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.

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

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.

以下是如何在另一个应用程序域的上下文中构造对象的粗略概述:

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 将包含对代理的引用,该代理链接到应用程序域内的实际对象实例.您可以使用反射或将 obj 类型转换为公共接口类型来调用 obj 上的方法并直接调用方法.准备进行调整以支持方法调用参数的 RPC 编组.(请参阅 .NET 远程处理)

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 函数默认在调用方的当前应用程序域中运行,这在您拥有多个应用程序域时通常不是您想要的.compilerResult.CompiledAssembly,例如,在调用者的应用程序域中在内部执行生成的程序集的加载.您想要的是将程序集加载到您的其他应用程序域中.你必须明确地这样做.

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 加载到 current 应用程序域(调用者的应用程序域)中,而不是加载到目标应用程序域中(在您的示例中由 _domain 引用).您需要使用 _domain.Load() 将程序集加载到那个应用程序域中.

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天全站免登陆