.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); स्टैटिक असेंबली LoadFromSameFolderResolveEventHandler (ऑब्जेक्ट प्रेषक, ResolveEventArgs आर्ग्स) {string folderPath = Path.GetDirectoryName (Assembly.GetExecutingAssembly () स्थान); string AssemblyPath = Path.Combine (folderPath, args.Name + ".dll"); विधानसभा विधानसभा = विधानसभा। वापसी विधानसभा; }
isobretatel

1
यदि आप मूल रिज़ॉल्वर को "कमबैक" करना चाहते हैं तो आप क्या करेंगे। जैसेif (!File.Exists(asmPath)) return searchInGAC(...);
तोमर डब्ल्यू

57

आप अपने एप्लिकेशन की .config फ़ाइल में एक प्रोबिंग पथ जोड़ सकते हैं , लेकिन यह केवल तभी काम करेगा जब प्रोबिंग पथ आपके एप्लिकेशन की आधार निर्देशिका में निहित हो।


3
इसे जोड़ने के लिए धन्यवाद। मैंने AssemblyResolveकई बार समाधान देखा है , एक और (और आसान) विकल्प के लिए अच्छा है।
शमूएल नेफ

1
अगर आप अपने ऐप को कहीं और कॉपी करते हैं तो App.config फ़ाइल को अपने ऐप के साथ स्थानांतरित करना न भूलें ..
Maxter

12

फ्रेमवर्क 4 के लिए अद्यतन

चूंकि फ्रेमवर्क 4 असेंबली को बढ़ाता है। संसाधन के लिए घटना भी वास्तव में यह हैंडलर बेहतर काम करता है। यह इस अवधारणा पर आधारित है कि स्थानीयकरण ऐप उपनिर्देशिकाओं में हैं (संस्कृति के नाम के साथ स्थानीयकरण के लिए एक अर्थात C: \ MyApp \ इटालियन के लिए) इसके अंदर संसाधन फ़ाइल हैं। यदि स्थानीयकरण देश-क्षेत्र अर्थात आईटी या pt-BR है तो हैंडलर भी काम करता है। इस मामले में हैंडलर "कई बार कहा जा सकता है: एक बार फॉलबैक चेन में प्रत्येक संस्कृति के लिए" [MSDN से]। इसका मतलब यह है कि अगर हम "इट-आईटी" संसाधन फ़ाइल के लिए शून्य वापस आते हैं, तो फ्रेमवर्क घटना को "यह" के लिए पूछ रहा है।

घटना हुक

        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असेंबली स्ट्रिंग को पार्स करने पर निर्भर करने के बजाय असेंबली का नाम डीकोड करने के लिए कंस्ट्रक्टर का उपयोग कर सकते हैं ।
सेबज़्ज़

10

एमएस से ही सबसे अच्छी व्याख्या :

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 अशक्तता के लिए जाँच की जानी चाहिए।

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.AppendPStreetPath (पदावनत) या AppDomainSetup.PrivateBinath में देखें


11
से MSDN : एक AppDomainSetup उदाहरण के गुणों को बदलने से किसी भी मौजूदा AppDomain प्रभावित नहीं करता। यह केवल एक नए AppDomain के निर्माण को प्रभावित कर सकता है, जब CreateDomain विधि को पैरामीटर के रूप में AppDomainSetup उदाहरण के साथ कहा जाता है।
नाथन

2
AppDomain.AppendPrivatePathके प्रलेखन से लगता है कि उसे गतिशील रूप से AppDomainखोज पथ के विस्तार का समर्थन करना चाहिए , बस यह है कि सुविधा को हटा दिया जाए। यदि यह काम करता है, तो यह ओवरलोडिंग की तुलना में बहुत क्लीनर समाधान है AssemblyResolve
बिंकी


3

मैं एक और (डुप्लिकेट के रूप में चिह्नित) से आया था , जो कि प्रोबिंग टैग को App.Config फ़ाइल में जोड़ने के बारे में था।

मैं इसमें एक जोड़ने का काम करना चाहता हूं - विजुअल स्टूडियो ने पहले ही एक 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>

यह पता लगाने में कुछ समय लगा इसलिए मैं इसे यहां पोस्ट कर रहा हूं। इसके अलावा प्रिटिन नूगेट पैकेज का श्रेय । यह एक पैकेज है जो dll को स्वचालित रूप से स्थानांतरित करता है। मुझे अधिक मैन्युअल दृष्टिकोण पसंद आया इसलिए मैंने इसका उपयोग नहीं किया।

इसके अलावा - यहाँ एक पोस्ट बिल्ड स्क्रिप्ट है जो सभी .dll / .xml / .pdb से / Lib कॉपी करता है। यह / / डिबग (या रिलीज़) फ़ोल्डर को अशुद्ध करता है, मुझे लगता है कि लोग क्या हासिल करने की कोशिश करते हैं।

:: 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%"
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.