none
C#(.NET Framework)で作成したexeが参照するdllの場所について RRS feed

  • 質問

  • いつもお世話になっております。

    プログラムというよりは、環境について質問があります。

    現在Visual Studio 2017のC#を用いてexeとdllを作成しております。

    開発時は、サイドバイサイドでdllとexeを配置していれば動作しておりますが、

    システムの都合上、exeをdllを別の場所に置き、exeからdllを参照して動作できるようにしたいのですが、思った通りのことが出来ません。

    C++等のexeの場合、環境変数[PATH]にdllのパスを設定しておけば、参照してくれましたが、C#(.NET Framework)で作成したexeは.環境変数[PATH]をみません。

    これは仕様だということは理解していますが、C/C++で作成したexeと同様、C#で作成したexeも環境変数[PATH]以下のdllを見て起動できるようにしたいです。

    .configファイルを編集しなくてもできる方法、もしくは.configファイルを編集して、環境変数[PATH]を見てdllをロードする方法、もしくは、特定の固定フォルダ以下のdllをロードして起動する方法があればご教授お願い致します。

    ちなみに、以下の.configをいじる方法は試しましたが、バージョン指定は行いたくありません。(バージョン指定をしないと動作しませんでした)

    <dependentAssembly>
            <assemblyIdentity name="dll名" culture="neutral" publicKeyToken="xxxxxxxx"/>
            <codeBase version="x.x.x.x" href="dllpath"/>
     </dependentAssembly>

    2020年6月2日 5:13

回答

  • 自分で探して読み込んでみる

    namespace TestProgram
    {
        using System;
        using System.Linq;
        using System.IO;
        using System.Reflection;
    
        static class Program
        {
            static Program()
            {
    #if DEBUG
                File.Delete(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "ClassLibrary1.dll"));
    #endif
                AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
            }
    
            private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
            {
                var reqAsmName = new System.Reflection.AssemblyName(args.Name);
                var reqToken = reqAsmName.GetPublicKeyToken();
    
                if (reqAsmName.Name == "ClassLibrary1")
                {
                    var envPath = System.Environment.GetEnvironmentVariable("Path");
                    foreach (string testFolder in envPath.Split(';'))
                    {
                        var dllpath = Path.Combine(testFolder.Trim(), "ClassLibrary1.dll");
                        if (File.Exists(dllpath))
                        {
                            var dllAsmName = System.Reflection.AssemblyName.GetAssemblyName(dllpath);
    
                            if (dllAsmName.Name != reqAsmName.Name)
                            {
                                //アセンブリの名前が違う
                                continue;
                            }
                            if (reqToken != null && !reqToken.SequenceEqual(dllAsmName.GetPublicKeyToken()))
                            {
                                //不正な署名を検出
                                continue;
                            }
    
                            var asm = Assembly.LoadFile(dllpath);
                            return asm;
                        }
                    }
                }
                throw new System.DllNotFoundException(args.Name);
            }
    
            [STAThread]
            static void Main(string[] args)
            {
                Console.WriteLine(typeof(ClassLibrary1.Class1).Assembly.Location);
                new ClassLibrary1.Class1().Test();
                Console.ReadLine();
            }
        }
    }
    
    //ClassLibrary1.dll
    //namespace ClassLibrary1
    //{
    //    public class Class1
    //    {
    //        public void Test()
    //        {
    //            System.Console.WriteLine("OK");
    //        }
    //    }
    //}


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 編集済み gekkaMVP 2020年6月3日 0:35 読み込んでしまう前にチェックするように
    • 回答としてマーク coco2014 2020年6月3日 5:38
    2020年6月2日 8:50

すべての返信

  • 自分で探して読み込んでみる

    namespace TestProgram
    {
        using System;
        using System.Linq;
        using System.IO;
        using System.Reflection;
    
        static class Program
        {
            static Program()
            {
    #if DEBUG
                File.Delete(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "ClassLibrary1.dll"));
    #endif
                AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
            }
    
            private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
            {
                var reqAsmName = new System.Reflection.AssemblyName(args.Name);
                var reqToken = reqAsmName.GetPublicKeyToken();
    
                if (reqAsmName.Name == "ClassLibrary1")
                {
                    var envPath = System.Environment.GetEnvironmentVariable("Path");
                    foreach (string testFolder in envPath.Split(';'))
                    {
                        var dllpath = Path.Combine(testFolder.Trim(), "ClassLibrary1.dll");
                        if (File.Exists(dllpath))
                        {
                            var dllAsmName = System.Reflection.AssemblyName.GetAssemblyName(dllpath);
    
                            if (dllAsmName.Name != reqAsmName.Name)
                            {
                                //アセンブリの名前が違う
                                continue;
                            }
                            if (reqToken != null && !reqToken.SequenceEqual(dllAsmName.GetPublicKeyToken()))
                            {
                                //不正な署名を検出
                                continue;
                            }
    
                            var asm = Assembly.LoadFile(dllpath);
                            return asm;
                        }
                    }
                }
                throw new System.DllNotFoundException(args.Name);
            }
    
            [STAThread]
            static void Main(string[] args)
            {
                Console.WriteLine(typeof(ClassLibrary1.Class1).Assembly.Location);
                new ClassLibrary1.Class1().Test();
                Console.ReadLine();
            }
        }
    }
    
    //ClassLibrary1.dll
    //namespace ClassLibrary1
    //{
    //    public class Class1
    //    {
    //        public void Test()
    //        {
    //            System.Console.WriteLine("OK");
    //        }
    //    }
    //}


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 編集済み gekkaMVP 2020年6月3日 0:35 読み込んでしまう前にチェックするように
    • 回答としてマーク coco2014 2020年6月3日 5:38
    2020年6月2日 8:50
  • こんにちは。

    私の場合、dllファイルの位置を特定の場所に配置せよという仕様を満たすため、APIのSetDllDirectoryを使いました。

    2020年6月2日 22:41
  • .NETでは、MEF; Managed Extensibility Frameworkが用意されています。別のディレクトリからDLLを読み込む場合、この拡張機能が適切なような気がします。

    別のアプローチとしては、app.configに探索パスとして<probing>を記述することです。ただしこちらはEXEからの相対パスを記述する想定ですので、期待にそぐわないかもしれません。

    あとは泥臭い方法で、AppDomain.AssemblyResolveイベントで、期待するパスから読み込んで返すことです(gekkaさんのサンプルコード)。

    2020年6月2日 23:27
  • gekkaさん

    回答ありがとうございます。

    自分でPATHを探して。という処理ですね。どうしても無理な場合はそのような対応で進めたいと思います。

    いつもありがとうございます。

    2020年6月3日 5:30
  • huahi11112さん

    回答ありがとうございます。

    SetDllDirectoryは私も内部的にいろんなところで使ったことがあります。

    ただ、今回は、dllの場所は任意の場所に置かれているので(インストーラでPATHの場所にはおかれている)

    固定で場所を指定できないのです。環境変数のPATHを探して~のSetDllDirectoryでもよさそうですね。

    ありがとうございました。

    2020年6月3日 5:36
  • 佐祐理さん

    Managed Extensibility Frameworkですね。

    その存在知りませんでした。

    勉強してみます。

    どうしても無理な場合は、gekkaさんの方法で進めようと思います。

    ありがとうございました。
    2020年6月3日 5:38