.NETで実行時にアセンブリ検索パスにフォルダを追加するにはどうすればよいですか?


130

私のDLLは、カスタマイズできないサードパーティのアプリケーションによって読み込まれます。私のアセンブリは、独自のフォルダに配置する必要があります。それらをGACに配置できません(私のアプリケーションにはXCOPYを使用してデプロイする必要があります)。ルートDLLが(同じフォルダー内の)別のDLLからリソースまたはタイプをロードしようとすると、ロードは失敗します(FileNotFound)。DLLが配置されているフォルダーをプログラムで(ルートDLLから)アセンブリ検索パスに追加することはできますか?アプリケーションの構成ファイルを変更することはできません。

回答:


154

AppDomain.AssemblyResolveイベントを使用して、DLLディレクトリから手動で依存関係を読み込むことができます。

編集(コメントから):

AppDomain currentDomain = AppDomain.CurrentDomain;
currentDomain.AssemblyResolve += new ResolveEventHandler(LoadFromSameFolder);

static Assembly LoadFromSameFolder(object sender, ResolveEventArgs args)
{
    string folderPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
    string assemblyPath = Path.Combine(folderPath, new AssemblyName(args.Name).Name + ".dll");
    if (!File.Exists(assemblyPath)) return null;
    Assembly assembly = Assembly.LoadFrom(assemblyPath);
    return assembly;
}

4
ありがとう、マティアス!これは機能します:AppDomain currentDomain = AppDomain.CurrentDomain; currentDomain.AssemblyResolve + = new ResolveEventHandler(LoadFromSameFolderResolveEventHandler); static Assembly LoadFromSameFolderResolveEventHandler(object sender、ResolveEventArgs args){string folderPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly()。Location); string assemblyPath = Path.Combine(folderPath、args.Name + ".dll"); アセンブリアセンブリ= Assembly.LoadFrom(assemblyPath); リターンアセンブリ; }
isobretatel

1
基本的なリゾルバーに「フォールバック」したい場合はどうしますか。例if (!File.Exists(asmPath)) return searchInGAC(...);
Tomer W 2017

57

アプリケーションの.configファイルにプローブパスを追加できますが、プローブパスがアプリケーションのベースディレクトリに含まれている場合にのみ機能します。


3
これを追加していただきありがとうございます。私はAssemblyResolve解決策を何度も見てきましたが、別の(そしてより簡単な)オプションがあると便利です。
Samuel Neff

1
..あなたはどこか別の場所にアプリをコピーする場合は、あなたのアプリでApp.configファイルを移動することを忘れないでください
Maxter

12

フレームワーク4の更新

フレームワーク4は、リソースに対してもAssemblyResolveイベントを発生させるため、実際にはこのハンドラーはより適切に機能します。ローカリゼーションはアプリのサブディレクトリにあるという概念に基づいています(1つは、カルチャの名前を持つローカリゼーション用、つまりイタリア語の場合はC:\ MyApp \ itです)内部にはリソースファイルがあります。ローカリゼーションが国-地域、つまりit-ITまたはpt-BRの場合も、ハンドラーは機能します。この場合、ハンドラーは「複数回呼び出される可能性があります:フォールバックチェーン内のカルチャごとに1回」[MSDNから]。つまり、「it-IT」リソースファイルに対してnullを返すと、フレームワークは「it」を要求するイベントを発生させます。

イベントフック

        AppDomain currentDomain = AppDomain.CurrentDomain;
        currentDomain.AssemblyResolve += new ResolveEventHandler(currentDomain_AssemblyResolve);

イベントハンドラー

    Assembly currentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
    {
        //This handler is called only when the common language runtime tries to bind to the assembly and fails.

        Assembly executingAssembly = Assembly.GetExecutingAssembly();

        string applicationDirectory = Path.GetDirectoryName(executingAssembly.Location);

        string[] fields = args.Name.Split(',');
        string assemblyName = fields[0];
        string assemblyCulture;
        if (fields.Length < 2)
            assemblyCulture = null;
        else
            assemblyCulture = fields[2].Substring(fields[2].IndexOf('=') + 1);


        string assemblyFileName = assemblyName + ".dll";
        string assemblyPath;

        if (assemblyName.EndsWith(".resources"))
        {
            // Specific resources are located in app subdirectories
            string resourceDirectory = Path.Combine(applicationDirectory, assemblyCulture);

            assemblyPath = Path.Combine(resourceDirectory, assemblyFileName);
        }
        else
        {
            assemblyPath = Path.Combine(applicationDirectory, assemblyFileName);
        }



        if (File.Exists(assemblyPath))
        {
            //Load the assembly from the specified path.                    
            Assembly loadingAssembly = Assembly.LoadFrom(assemblyPath);

            //Return the loaded assembly.
            return loadingAssembly;
        }
        else
        {
            return null;
        }

    }

AssemblyNameアセンブリ文字列の解析に依存する代わりに、コンストラクタを使用してアセンブリ名をデコードできます。
Sebazzz

10

MS自体からの最良の説明:

AppDomain currentDomain = AppDomain.CurrentDomain;
currentDomain.AssemblyResolve += new ResolveEventHandler(MyResolveEventHandler);

private Assembly MyResolveEventHandler(object sender, ResolveEventArgs args)
{
    //This handler is called only when the common language runtime tries to bind to the assembly and fails.

    //Retrieve the list of referenced assemblies in an array of AssemblyName.
    Assembly MyAssembly, objExecutingAssembly;
    string strTempAssmbPath = "";

    objExecutingAssembly = Assembly.GetExecutingAssembly();
    AssemblyName[] arrReferencedAssmbNames = objExecutingAssembly.GetReferencedAssemblies();

    //Loop through the array of referenced assembly names.
    foreach(AssemblyName strAssmbName in arrReferencedAssmbNames)
    {
        //Check for the assembly names that have raised the "AssemblyResolve" event.
        if(strAssmbName.FullName.Substring(0, strAssmbName.FullName.IndexOf(",")) == args.Name.Substring(0, args.Name.IndexOf(",")))
        {
            //Build the path of the assembly from where it has to be loaded.                
            strTempAssmbPath = "C:\\Myassemblies\\" + args.Name.Substring(0,args.Name.IndexOf(","))+".dll";
            break;
        }

    }

    //Load the assembly from the specified path.                    
    MyAssembly = Assembly.LoadFrom(strTempAssmbPath);                   

    //Return the loaded assembly.
    return MyAssembly;          
}

AssemblyResolveCurrentDomain用であり、別のドメインには無効AppDomain.CreateDomain
Kiquenet

8

C ++ / CLIユーザーの場合、ここに@Mattias Sの回答があります(これは私のために機能します)。

using namespace System;
using namespace System::IO;
using namespace System::Reflection;

static Assembly ^LoadFromSameFolder(Object ^sender, ResolveEventArgs ^args)
{
    String ^folderPath = Path::GetDirectoryName(Assembly::GetExecutingAssembly()->Location);
    String ^assemblyPath = Path::Combine(folderPath, (gcnew AssemblyName(args->Name))->Name + ".dll");
    if (File::Exists(assemblyPath) == false) return nullptr;
    Assembly ^assembly = Assembly::LoadFrom(assemblyPath);
    return assembly;
}

// put this somewhere you know it will run (early, when the DLL gets loaded)
System::AppDomain ^currentDomain = AppDomain::CurrentDomain;
currentDomain->AssemblyResolve += gcnew ResolveEventHandler(LoadFromSameFolder);

6

@Mattias S 'ソリューションを使用しました。同じフォルダーの依存関係を実際に解決したい場合は、以下に示すように、アセンブリの場所の要求を使用してみてください。args.RequestingAssemblyがnullかどうかを確認する必要があります。

System.AppDomain.CurrentDomain.AssemblyResolve += (s, args) =>
{
    var loadedAssembly = System.AppDomain.CurrentDomain.GetAssemblies().Where(a => a.FullName == args.Name).FirstOrDefault();
    if(loadedAssembly != null)
    {
        return loadedAssembly;
    }

    if (args.RequestingAssembly == null) return null;

    string folderPath = Path.GetDirectoryName(args.RequestingAssembly.Location);
    string rawAssemblyPath = Path.Combine(folderPath, new System.Reflection.AssemblyName(args.Name).Name);

    string assemblyPath = rawAssemblyPath + ".dll";

    if (!File.Exists(assemblyPath))
    {
        assemblyPath = rawAssemblyPath + ".exe";
        if (!File.Exists(assemblyPath)) return null;
    } 

    var assembly = System.Reflection.Assembly.LoadFrom(assemblyPath);
    return assembly;
 };

4

AppDomain.AppendPrivatePath(非推奨)またはAppDomainSetup.PrivateBinPathを調べます


11
MSDNから:AppDomainSetupインスタンスのプロパティを変更しても、既存のAppDomainには影響しません。AppDomainSetupインスタンスをパラメーターとして指定してCreateDomainメソッドを呼び出すと、新しいAppDomainの作成にのみ影響を与える可能性があります。
ネイサン

2
AppDomain.AppendPrivatePathのドキュメントは、AppDomain検索パスの動的な拡張をサポートする必要があることを示唆しているようですが、機能は廃止されています。動作する場合、オーバーロードよりもはるかにクリーンなソリューションですAssemblyResolve
binki 2015

参考のために、それは次のようになりAppDomain.AppendPrivatePath 、.NETのコアでは何もしませんし、アップデート.PrivateBinPathの完全な枠組みの中を
ケビノイド

3

App.Configファイルへのプローブタグの追加に関する別の(重複とマークされた)質問からここに来ました。

これに付記を追加したいのですが、Visual Studioは既にApp.configファイルを生成していますが、事前生成されたランタイムタグにプローブタグを追加しても機能しませんでした。プローブタグが含まれた別のランタイムタグが必要です。つまり、App.Configは次のようになります。

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
    </startup>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="System.Text.Encoding.CodePages" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-4.1.1.0" newVersion="4.1.1.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>

  <!-- Discover assemblies in /lib -->
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <probing privatePath="lib" />
    </assemblyBinding>
  </runtime>
</configuration>

これは理解するのに少し時間がかかったので、ここに投稿します。PrettyBin NuGetパッケージクレジットます。DLLを自動で移動するパッケージです。もっと手作業のアプローチが好きだったので、使用しませんでした。

また-すべての.dll / .xml / .pdbを/ Libにコピーするビルド後スクリプトがあります。これにより、/ debug(または/ release)フォルダーが整理され、人々が達成しようとしていると思います。

:: Moves files to a subdirectory, to unclutter the application folder
:: Note that the new subdirectory should be probed so the dlls can be found.
SET path=$(TargetDir)\lib
if not exist "%path%" mkdir "%path%"
del /S /Q "%path%"
move /Y $(TargetDir)*.dll "%path%"
move /Y $(TargetDir)*.xml "%path%"
move /Y $(TargetDir)*.pdb "%path%"
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.