असेंबली लोड करने का सही तरीका, क्लास और कॉल रन () विधि का पता लगाएं


81

नमूना सांत्वना कार्यक्रम।

class Program
{
    static void Main(string[] args)
    {
        // ... code to build dll ... not written yet ...
        Assembly assembly = Assembly.LoadFile(@"C:\dyn.dll");
        // don't know what or how to cast here
        // looking for a better way to do next 3 lines
        IRunnable r = assembly.CreateInstance("TestRunner");
        if (r == null) throw new Exception("broke");
        r.Run();

    }
}

मैं गतिशील रूप से एक असेंबली (.dll) का निर्माण करना चाहता हूं, और फिर असेंबली को लोड करता हूं, एक क्लास को इंस्टेंट करता हूं, और उस क्लास की रन () विधि को कॉल करता हूं। क्या मुझे TestRunner क्लास को कुछ करने की कोशिश करनी चाहिए? यह निश्चित नहीं है कि एक विधानसभा (डायनामिक कोड) के प्रकारों को मेरे (स्टेटिक असेंबली / शेल ऐप) में मेरे प्रकारों के बारे में कैसे पता चलेगा। क्या सिर्फ एक वस्तु पर रन () कॉल करने के लिए प्रतिबिंब कोड की कुछ पंक्तियों का उपयोग करना बेहतर है? उस कोड को कैसा दिखना चाहिए?

अद्यतन: विलियम एडमंडसन - टिप्पणी देखें


भविष्य से बोलते हुए ... क्या आपने MEF के साथ काम किया है? आइए आपको exportऔर importएक ज्ञात इंटरफ़ेस से प्राप्त होने वाली अलग-अलग विधानसभाओं में कक्षाएं
आरजेबी

जवाबों:


78

एक AppDomain का उपयोग करें

यह AppDomainपहली बार में विधानसभा को लोड करने के लिए अधिक सुरक्षित और अधिक लचीला है ।

इसलिए पहले दिए गए उत्तर के बजाय :

var asm = Assembly.LoadFile(@"C:\myDll.dll");
var type = asm.GetType("TestRunner");
var runnable = Activator.CreateInstance(type) as IRunnable;
if (runnable == null) throw new Exception("broke");
runnable.Run();

मैं निम्नलिखित सुझाव देगा ( संबंधित प्रश्न के उत्तर से अनुकूलित ):

var domain = AppDomain.CreateDomain("NewDomainName");
var t = typeof(TypeIWantToLoad);
var runnable = domain.CreateInstanceFromAndUnwrap(@"C:\myDll.dll", t.Name) as IRunnable;
if (runnable == null) throw new Exception("broke");
runnable.Run();

अब आप असेंबली को अनलोड कर सकते हैं और विभिन्न सुरक्षा सेटिंग्स रख सकते हैं।

यदि आप डायनामिक लोडिंग और असेंबलियों को अनलोड करने के लिए और भी अधिक लचीलापन और शक्ति चाहते हैं, तो आपको प्रबंधित ऐड-इन्स फ्रेमवर्क (यानी System.AddInनेमस्पेस) को देखना चाहिए । अधिक जानकारी के लिए MSDN पर ऐड-इन्स और एक्स्टेंसिबिलिटी पर यह लेख देखें ।


1
यदि TypeIWantToLoad एक स्ट्रिंग है तो क्या होगा? क्या आपके पास पिछले उत्तर के asm.GetType ("टाइप स्ट्रिंग") का विकल्प है?
पाज़

2
मुझे लगता है कि मार्ग के बजाय असेंबलीनाम कीCreateInstanceFromAndUnwrap आवश्यकता है ; क्या आपका मतलब था ? इसके अलावा, मैं द्वारा जला दिया गया आवश्यकताCreateFrom(path, fullname).Unwrap()MarshalByRefObject
drzaus

1
शायद CreateInstanceAndUnwrap(typeof(TypeIWantToLoad).Assembly.FullName, typeof(TypeIWantToLoad).FullName)?
फादन

1
हाय दोस्तों, मुझे विश्वास है कि आप CreateInstanceAndUnwrap को भ्रमित कर रहे हैं CreateInstanceFromAndUnwrap के साथ।
cdiggins

48

यदि आपके पास TestRunnerकॉल असेंबली में टाइप जानकारी तक पहुँच नहीं है (ऐसा लगता है कि आप ऐसा नहीं कर सकते हैं), तो आप इस तरह से विधि को कॉल कर सकते हैं:

Assembly assembly = Assembly.LoadFile(@"C:\dyn.dll");
Type     type     = assembly.GetType("TestRunner");
var      obj      = Activator.CreateInstance(type);

// Alternately you could get the MethodInfo for the TestRunner.Run method
type.InvokeMember("Run", 
                  BindingFlags.Default | BindingFlags.InvokeMethod, 
                  null,
                  obj,
                  null);

यदि आपके पास IRunnableइंटरफ़ेस प्रकार तक पहुंच है , तो आप अपने उदाहरण को उस TestRunnerप्रकार के बजाय कास्ट कर सकते हैं , जो कि गतिशील रूप से बनाए गए या लोड किए गए असेंबली, राइट?) में लागू किया गया है।

  Assembly assembly  = Assembly.LoadFile(@"C:\dyn.dll");
  Type     type      = assembly.GetType("TestRunner");
  IRunnable runnable = Activator.CreateInstance(type) as IRunnable;
  if (runnable == null) throw new Exception("broke");
  runnable.Run();

+1 यह type.invokeMember लाइन का उपयोग करके काम करता है। क्या मुझे उस विधि का उपयोग करना चाहिए या इंटरफ़ेस के साथ कुछ करने की कोशिश करते रहना चाहिए? मैं बल्कि गतिशील रूप से निर्मित कोड में इसे डालने के बारे में चिंता करने की ज़रूरत नहीं है।
बड्डीजॉय जूल

हम्म, आपके लिए कोड का दूसरा ब्लॉक काम नहीं करता है? क्या आपकी कॉलिंग असेंबली में IRunnable प्रकार की पहुंच है?
जेफ सनातन जुएल 16'09

दूसरा ब्लॉक काम करता है। असेंबली को कॉल करना वास्तव में IRunnable के बारे में नहीं जानता है। इसलिए मुझे लगता है कि मैं दूसरी विधि के साथ रहूँगा। थोड़ा फॉलो अप करें। जब मैं कोड को पुनः प्राप्त कर लेता हूं और फिर dyn.dll पुनः प्राप्त करता हूं तो मैं इसे प्रतिस्थापित नहीं कर सकता क्योंकि इसके उपयोग में है। एक विधानसभा की तरह कुछ भी .UnloadType या मुझे .dll को बदलने की अनुमति देने के लिए कुछ? या क्या मुझे इसे "मेमोरी में" करना चाहिए? विचार? धन्यवाद
बुदिजेओ

लगता है कि मैं "स्मृति में" बात करने का उचित तरीका नहीं जानता अगर वह सबसे अच्छा समाधान है।
बुडीजॉए जुले

मुझे विवरण याद नहीं है (और मैं थोड़ी देर के लिए अपने कंप्यूटर से दूर जा रहा हूं), लेकिन मेरा मानना ​​है कि एक असेंबली केवल AppDomain के अनुसार एक बार भरी जा सकती है - इसलिए आपको या तो प्रत्येक असेंबली उदाहरण के लिए नए AppDomains बनाने होंगे; और असेंबलियों को उन पर लोड करें) या आपको विधानसभा के नए संस्करण को संकलित करने से पहले अपने आवेदन को फिर से शुरू करना होगा।
जेफ सनातन

12

मैं बिल्कुल वही कर रहा हूं जो आप मेरे नियम इंजन में देख रहे हैं, जो गतिशील रूप से संकलन, लोड करने और C # चलाने के लिए CS-Script का उपयोग करता है। यह आसानी से अनुवाद योग्य होना चाहिए कि आप क्या खोज रहे हैं, और मैं एक उदाहरण दूंगा। सबसे पहले, कोड (छीन लिया गया):

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using CSScriptLibrary;

namespace RulesEngine
{
    /// <summary>
    /// Make sure <typeparamref name="T"/> is an interface, not just any type of class.
    /// 
    /// Should be enforced by the compiler, but just in case it's not, here's your warning.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class RulesEngine<T> where T : class
    {
        public RulesEngine(string rulesScriptFileName, string classToInstantiate)
            : this()
        {
            if (rulesScriptFileName == null) throw new ArgumentNullException("rulesScriptFileName");
            if (classToInstantiate == null) throw new ArgumentNullException("classToInstantiate");

            if (!File.Exists(rulesScriptFileName))
            {
                throw new FileNotFoundException("Unable to find rules script", rulesScriptFileName);
            }

            RulesScriptFileName = rulesScriptFileName;
            ClassToInstantiate = classToInstantiate;

            LoadRules();
        }

        public T @Interface;

        public string RulesScriptFileName { get; private set; }
        public string ClassToInstantiate { get; private set; }
        public DateTime RulesLastModified { get; private set; }

        private RulesEngine()
        {
            @Interface = null;
        }

        private void LoadRules()
        {
            if (!File.Exists(RulesScriptFileName))
            {
                throw new FileNotFoundException("Unable to find rules script", RulesScriptFileName);
            }

            FileInfo file = new FileInfo(RulesScriptFileName);

            DateTime lastModified = file.LastWriteTime;

            if (lastModified == RulesLastModified)
            {
                // No need to load the same rules twice.
                return;
            }

            string rulesScript = File.ReadAllText(RulesScriptFileName);

            Assembly compiledAssembly = CSScript.LoadCode(rulesScript, null, true);

            @Interface = compiledAssembly.CreateInstance(ClassToInstantiate).AlignToInterface<T>();

            RulesLastModified = lastModified;
        }
    }
}

यह टाइप T का एक इंटरफ़ेस लेगा, एक .cs फ़ाइल को असेंबली में संकलित करेगा, किसी दिए गए प्रकार के एक वर्ग को तुरंत कर सकता है, और उस तत्काल वर्ग को T इंटरफ़ेस में संरेखित कर सकता है। मूल रूप से, आपको बस यह सुनिश्चित करना होगा कि तत्काल वर्ग उस इंटरफ़ेस को लागू करता है। मैं गुणों का उपयोग सेटअप और सब कुछ एक्सेस करने के लिए करता हूं, जैसे:

private RulesEngine<IRulesEngine> rulesEngine;

public RulesEngine<IRulesEngine> RulesEngine
{
    get
    {
        if (null == rulesEngine)
        {
            string rulesPath = Path.Combine(Application.StartupPath, "Rules.cs");

            rulesEngine = new RulesEngine<IRulesEngine>(rulesPath, typeof(Rules).FullName);
        }

        return rulesEngine;
    }
}

public IRulesEngine RulesEngineInterface
{
    get { return RulesEngine.Interface; }
}

अपने उदाहरण के लिए, आप रन () को कॉल करना चाहते हैं, इसलिए मैं एक इंटरफ़ेस बनाऊंगा जो रन () विधि को परिभाषित करता है, जैसे:

public interface ITestRunner
{
    void Run();
}

फिर एक वर्ग बनाएं जो इसे लागू करता है, जैसे:

public class TestRunner : ITestRunner
{
    public void Run()
    {
        // implementation goes here
    }
}

TestHarness की तरह कुछ नियमों के नियम बदलें, और अपने गुण सेट करें:

private TestHarness<ITestRunner> testHarness;

public TestHarness<ITestRunner> TestHarness
{
    get
    {
        if (null == testHarness)
        {
            string sourcePath = Path.Combine(Application.StartupPath, "TestRunner.cs");

            testHarness = new TestHarness<ITestRunner>(sourcePath , typeof(TestRunner).FullName);
        }

        return testHarness;
    }
}

public ITestRunner TestHarnessInterface
{
    get { return TestHarness.Interface; }
}

फिर, कहीं भी आप इसे कॉल करना चाहते हैं, आप बस चला सकते हैं:

ITestRunner testRunner = TestHarnessInterface;

if (null != testRunner)
{
    testRunner.Run();
}

यह शायद एक प्लगइन सिस्टम के लिए बहुत अच्छा काम करेगा, लेकिन जैसा कि मेरा कोड एक फ़ाइल लोड करने और चलाने तक सीमित है, क्योंकि हमारे सभी नियम एक सी # स्रोत फ़ाइल में हैं। मुझे लगता है कि इसे संशोधित करना बहुत आसान होगा, बस इसे टाइप / सोर्स फाइल में पास करना होगा हर एक के लिए जिसे आप चलाना चाहते थे। आपको बस कोड को गेट्टर से एक विधि में स्थानांतरित करना होगा जो उन दो मापदंडों को ले गया।

इसके अलावा, ITestRunner के स्थान पर अपने IRunnable का उपयोग करें।


@Interface क्या है? यहाँ बहुत अच्छा विचार है। इसे पूरी तरह से पचाने की जरूरत है। +1
बुडीजॉए

बहुत दिलचस्प है कि मुझे यह महसूस नहीं हुआ कि सी # पार्सर को यह देखने के लिए एक वर्ण पास करना होगा कि क्या वह चर नाम या @ "" स्ट्रिंग का हिस्सा है।
बुडीजोई

धन्यवाद। चर नाम से पहले @ का उपयोग तब किया जाता है जब चर नाम एक खोजशब्द होता है। आप एक चर "वर्ग", "इंटरफ़ेस", "नया", आदि का नाम नहीं दे सकते हैं ... लेकिन यदि आप एक @ प्रस्तुत करते हैं तो आप कर सकते हैं। संभवतया मेरे मामले में "आई" की पूंजी के साथ कोई फर्क नहीं पड़ता, लेकिन मूल रूप से ऑटो-प्रॉपर्टी में बदलने से पहले यह एक गेट्टर और सेटर के साथ एक आंतरिक चर था।
क्रिस डोगेट

ये सही है। म @ बात भूल गया। जेफ सनातन के पास "स्मृति चीज़" के बारे में मेरे पास जो सवाल था उसे आप कैसे संभालेंगे? मुझे लगता है कि अब मेरा बड़ा मुद्दा यह है कि मैं गतिशील .dll का निर्माण कर सकता हूं और इसे लोड कर सकता हूं, लेकिन मैं इसे केवल एक बार ही कर सकता हूं। विधानसभा को "अनलोड" करने का तरीका नहीं जानते। क्या किसी अन्य AppDomain को उस स्थान पर असेंबली लोड करना संभव है, इसका उपयोग करें, और फिर इस दूसरे AppDomain को नीचे ले जाएं। कुल्ला करना। बार-बार?
बुडजोई जूल

1
जब तक आप दूसरे AppDomain का उपयोग नहीं करते, तब तक असेंबली को उतारने का कोई तरीका नहीं है। मुझे यकीन नहीं है कि CS-Script इसे आंतरिक रूप से कैसे करता है, लेकिन मेरे नियम इंजन का एक हिस्सा जो मैंने छीन लिया है वह एक फाइलसिस्टमचैकर है जो स्वचालित रूप से लोडर्यूल () को चलाता है जब भी फाइल बदलती है। हम नियमों को संपादित करते हैं, उन्हें उन उपयोगकर्ताओं के लिए धक्का देते हैं, जिनके ग्राहक उस फ़ाइल को ओवरराइट कर देते हैं, FileSystemWatcher परिवर्तनों को नोटिस करता है, और अस्थायी निर्देशिका में एक और फ़ाइल लिखकर DLL को पुनः लोड करता है। जब क्लाइंट शुरू होता है, तो वह पहले डायनामिक कंपाइल से पहले उस डायरेक्टरी को क्लियर कर देता है, इसलिए हमारे पास एक टन भी बचे नहीं हैं।
क्रिस डॉग्ज

6

आपको "TestRunner" प्रकार प्राप्त करने के लिए प्रतिबिंब का उपयोग करने की आवश्यकता होगी। असेंबली का उपयोग करें। गेट टाइप विधि।

class Program
{
    static void Main(string[] args)
    {
        Assembly assembly = Assembly.LoadFile(@"C:\dyn.dll");
        Type type = assembly.GetType("TestRunner");
        var obj = (TestRunner)Activator.CreateInstance(type);
        obj.Run();
    }
}

क्या यह एक स्टेप नहीं है, जिसमें आप MethodInfoटाइप और कॉल से उपयुक्त हैं Invoke? (मैंने मूल प्रश्न को प्रश्न में टाइप के बारे में कुछ भी नहीं जानने वाले को निर्दिष्ट करने के रूप में समझा था।)
जेफ सनातन

आपको एक बात याद आ रही है। आपको TestRunner टाइप करने के लिए obj डालना होगा। var obj = (TestRunner) Activator.CreateInstance (प्रकार);
BFree

ऐसा लगता है कि टाइन्डल वास्तव में पहले चरण में इस dll का निर्माण कर रहा है। यह कार्यान्वयन मानता है कि वह जानता है कि विधि रन () पहले से मौजूद है और जानता है कि इसके पास कोई पैरामीटर नहीं है। यदि ये वास्तव में अज्ञात हैं, तो उसे थोड़ा गहरा प्रतिबिंब बनाने की आवश्यकता होगी
विलियम एडमंडसन

हम्म। TestRunner मेरे गतिशील लिखित कोड के अंदर एक वर्ग है। तो आपके उदाहरण में यह स्थिर कोड TestRunner को हल नहीं कर सकता है। इसका कोई पता नहीं है कि यह क्या है।
बड्डीजॉय

@WilliamEdmondson कोड में "(TestRunner)" का उपयोग कैसे कर सकते हैं क्योंकि यह यहां संदर्भित नहीं है?
Antoops

2

जब आप अपना असेंबली बनाते हैं, तो आप कॉल कर सकते हैं AssemblyBuilder.SetEntryPoint, और फिर इसे वापस लाने के लिए इसे Assembly.EntryPointसंपत्ति से वापस ले सकते हैं।

ध्यान रखें कि आप इस हस्ताक्षर का उपयोग करना चाहते हैं, और ध्यान दें कि इसका नाम नहीं है Main:

static void Run(string[] args)

असेम्बली क्या है? मैं कोडडोमप्रॉइडर की कोशिश कर रहा था और फिर "provider.CompileAssemblyFromSource"
BuddyJoe
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.