4

A) C# EXE と DLL をオンザフライでコンパイルするのは比較的簡単です。
B) EXE を実行すると、新しいアプリケーションが実行されます。DLL をロードするということは、アプリケーションまたはプロジェクト間で共有される可能性がある場合に、メソッドと関数を使用できることを意味します。

現在、EXE (または軽度の変更を加えた DLL) をコンパイルする最も迅速で簡単な方法は、MSDNから、またはあなたの便宜のために見つけることができます。

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 をテストするたびにプロジェクト全体を再コンパイルするようユーザーに要求することはできません。 . そのため、ライブラリを「オンザフライ」でロードする方法について簡単に説明します。ここでの別の用語は、「プログラムによって」です。これを行うには、コンパイルが成功した後、次のように Assembly を読み込みます。

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

AppDomain がある場合は、これを試すことができます。

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


ライブラリが「参照」されたので、それを開いて使用できます。これには 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 がロードされた後にのみ失敗します。


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

2 に答える 2

9

実行中のプロセス (の既定のアプリケーション ドメイン) に DLL をロードすると、そのプロセスが終了するまで、ディスク上のファイルを上書きすることはできません。DLL は、アンマネージ コードでアンロードできるように、マネージ コードでアンロードできません。

ホスト プロセスで新しい appdomain を作成し、新しく作成した DLL アセンブリをその appdomain に読み込む必要があります。新しいバージョンの DLL をコンパイルする準備ができたら、appdomain を破棄できます。これにより、メモリから DLL がアンロードされ、DLL ファイルのロックが解除されるため、新しい DLL を同じファイルにコンパイルできます。次に、新しい DLL をロードする新しい appdomain を構築できます。

アプリドメインを使用する主な問題は、IPC やネットワーク RPC のように、アプリドメインの境界を越えたすべての呼び出しをマーシャリングする必要があることです。アプリドメインの境界を越えて呼び出す必要があるオブジェクトのインターフェイスを最小限に抑えるようにしてください。

また、アセンブリをメモリにコンパイルして、バイト配列またはストリームを出力として受け取り、そのアセンブリを別の appdomain にロードすることもできます。これにより、最終的に削除する必要があるディスク上に破片が作成されるのを回避できます。

ファイル ロックの問題の回避策としてメモリへのコンパイルを使用しないでください。中心的な問題は、アセンブリがプロセスの既定の appdomain に読み込まれると、アセンブリをメモリから削除できないことです。プロセスの有効期間の後半でそのアセンブリをメモリからアンロードする場合は、新しい appdomain を作成し、その appdomain に DLL を読み込む必要があります。

別の 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 で生成されたアセンブリの Load を内部的に実行します。必要なのは、アセンブリを他のアプリドメインにロードすることです。明示的に行う必要があります。

更新: appdomain の読み込み方法を示す最近追加されたコード スニペットでは、次の行が問題です。

 _assembly = Assembly.LoadFrom(path);

これにより、DLL がターゲット appdomain (例では _domain で参照) ではなく、現在のappdomain (呼び出し元の appdomain) に読み込まれます。_domain.Load()アセンブリをそのappdomainにロードするには、使用する必要があります。

于 2013-04-04T15:39:44.107 に答える
2

デバッグする必要がない場合、または「動的」コードをデバッグすることを気にしない場合、情報が不足しています。メモリ内にコードを生成できます..これにより、コードを数回コンパイルできます..しかし、.pdbは生成されません

cp.GenerateInMemory = true;

代わりに、ディスク上のアセンブリを見つける必要がない場合は、コンパイラにすべてのコードを一時ディレクトリにダンプし、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をコンパイルするたびにメモリにロードされ、別のアプリドメインを使用しない限りアンロードされないことを理解する必要があります..しかし、それはこの質問の範囲外です..

于 2013-04-04T15:48:56.393 に答える