簡體   English   中英

如何即時編譯,加載和使用C#DLL

[英]How to compile C# DLL on the fly, Load, and Use

A)即時編譯C#EXE和DLL相對容易。
B)執行EXE意味着新的應用程序正在運行。 加載DLL意味着可以在應用程序或項目之間共享的情況下使用方法和功能。

現在,可以從MSDN或為了方便起見找到編譯EXE的最快和最簡單的方法(或進行輕度修改的DLL):

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時都重新編譯整個項目。 。 因此,我將簡單討論如何“動態”加載庫。 這里的另一個術語是“以編程方式”。 為此,在成功編譯后,我們按以下方式加載程序集:

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

如果您有AppDomain,則可以嘗試以下操作:

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加載后,重新編譯才會失敗。


目前嘗試封送&& 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]標簽,但沒有運氣。 仍在研究修補程序。

當您看不到它們的使用方式時,似乎會使事情變得有些混亂,所以這使它變得容易嗎?

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不能像托管代碼中那樣被卸載在托管代碼中。

您需要在主機進程中創建一個新的appdomain並將新創建的DLL程序集加載到該appdomain中。 當您准備編譯DLL的新版本時,可以處置appdomain。 這將從內存中卸載DLL並釋放DLL文件上的鎖,以便您可以將新的DLL編譯到同一文件。 然后,您可以構造一個新的appdomain來將新的DLL加載到其中。

使用appdomains的主要危險在於,必須對所有跨越appdomain邊界的調用進行編組,就像IPC或網絡RPC一樣。 嘗試將需要跨越應用程序域邊界調用的對象的接口保持在最低限度。

您還可以將程序集編譯到內存,接收字節數組或流作為輸出,然后將該程序集加載到單獨的appdomain中。 這樣可以避免在磁盤上產生最終將需要刪除的碎片。

不要使用編譯到內存作為解決文件鎖定問題的方法。 核心問題是,將程序集加載到進程的默認應用程序域時無法將其從內存中刪除。 如果要在進程的生命周期中稍后從內存中卸載該程序集,則必須創建一個新的appdomain並將DLL加載到該appdomain中。

這是有關如何在另一個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遠程處理)

當使用多個應用程序域時,必須小心如何訪問類型和程序集,因為許多.NET函數默認在調用者的當前應用程序域中運行,而在擁有多個應用程序域時通常不是您想要的。 compilerResult.CompiledAssembly ,例如,在內部執行在呼叫者的應用程序域生成組件的一個負載。 您需要將程序集加載到其他appdomain中。 您必須明確地做到這一點。

更新:在最近添加的代碼片段中,顯示了如何加載應用程序域,此行是您的問題:

 _assembly = Assembly.LoadFrom(path);

這會將DLL加載到當前的 appdomain(調用者的appdomain)中,而不是加載到目標appdomain(在示例中由_domain引用)。 您需要使用_domain.Load()將程序集加載到 appdomain中。

如果您不需要調試,或者不介意調試帶有某些丟失信息的“動態”代碼。 您可以在內存中生成代碼。這將允許您多次編譯代碼。但是不會生成.pdb

cp.GenerateInMemory = true;

或者,如果不需要在磁盤上找到程序集,則可以要求編譯器將所有代碼轉儲到temp目錄中,並為dll生成一個臨時名稱(該名稱始終是唯一的)

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

在兩種情況下都可以訪問dll及其類型,您可以從編譯器結果中獲取它

Assembly assembly = cr.CompiledAssembly; 

不需要顯式加載

但是,如果不適用這種情況,並且您必須在已知文件夾中使用.pdp的物理.dll。.唯一的建議是我可以給您在dll上放一個版本號..如果您不這樣做的話有一個簡單的方法來控制dll的編譯時間,您可以隨時使用時間戳。

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

當然,您必須意識到,每次編譯時,新的.dll將被加載到內存中,除非您使用單獨的應用程序域,否則不會被卸載..但這超出了此問題的范圍。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM