Montajı Yüklemenin Doğru Yolu, Sınıfı Bul ve Çalıştır () Yöntemini Çağır


81

Örnek konsol programı.

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();

    }
}

Dinamik olarak bir derleme (.dll) oluşturmak ve ardından derlemeyi yüklemek, bir sınıfı somutlaştırmak ve o sınıfın Run () yöntemini çağırmak istiyorum. TestRunner sınıfını bir şeye aktarmayı denemeli miyim? Bir derlemedeki (dinamik kod) türlerin (statik derleme / kabuk uygulaması) içindeki türlerimi nasıl bileceğinden emin değilim. Sadece bir nesnede Run () 'ı çağırmak için birkaç satır yansıma kodu kullanmak daha mı iyidir? Bu kod neye benzemeli?

GÜNCELLEME: William Edmondson - yoruma bakın


Gelecekten bahsetmişken ... MEF ile çalıştınız mı? Siz exportve importsınıflar, bilinen bir arayüzden türetilen ayrı derlemelerde
olalım

Yanıtlar:


78

Bir AppDomain kullanın

İlk önce montajı kendi içine yüklemek daha güvenli ve daha esnektir AppDomain.

Yani daha önce verilen cevap yerine :

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();

Aşağıdakileri öneririm ( bu yanıttan ilgili soruya uyarlanmıştır ):

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();

Artık montajı kaldırabilir ve farklı güvenlik ayarlarına sahip olabilirsiniz.

Derlemelerin dinamik olarak yüklenmesi ve kaldırılması için daha fazla esneklik ve güç istiyorsanız, Yönetilen Eklentiler Çerçevesine (yani System.AddInad alanına) bakmalısınız . Daha fazla bilgi için , MSDN'de Eklentiler ve Genişletilebilirlik hakkındaki bu makaleye bakın .


1
Ya TypeIWantToLoad bir dizeyse? Önceki yanıtın asm.GetType ("type string") için bir alternatifiniz var mı?
paz

2
Bir yol yerine AssemblyNameCreateInstanceFromAndUnwrap gerektirdiğini düşünüyorum ; bunu mu demek istediniz ? Ayrıca gereklilikCreateFrom(path, fullname).Unwrap()MarshalByRefObject
nedeniyle yandım

1
Belki CreateInstanceAndUnwrap(typeof(TypeIWantToLoad).Assembly.FullName, typeof(TypeIWantToLoad).FullName)?
fadden

1
Merhaba millet, CreateInstanceAndUnwrap ile CreateInstanceFromAndUnwrap arasında kafa karıştırdığınıza inanıyorum.
cdiggins

48

TestRunnerÇağıran derlemede tür bilgisine erişiminiz yoksa (öyle görünüyor olabilir), yöntemi şu şekilde çağırabilirsiniz:

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);

IRunnableArabirim türüne erişiminiz varsa , örneğinizi buna çevirebilirsiniz ( TestRunnerdinamik olarak oluşturulan veya yüklenen montajda uygulanan tür yerine , değil mi?):

  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 satırını kullanarak çalıştı. Bu yöntemi kullanmalı mıyım yoksa arayüzle bir şeyler yapmaya mı devam etmeliyim? Bunu dinamik olarak oluşturulmuş kodun içine yerleştirme konusunda endişelenmemeyi bile tercih ederim.
BuddyJoe

Hmm, ikinci kod bloğu sizin için çalışmıyor mu? Çağıran derlemenizin IRunnable tipine erişimi var mı?
Jeff Sternal

İkinci blok çalışıyor. Calling assembly gerçekten IRunnable hakkında bilgi sahibi değil. Sanırım ikinci yönteme bağlı kalacağım. Hafif takip. Kodu yeniden başlatıp dyn.dll dosyasını yeniden yaptığımda, kullanımda olduğu için onu değiştiremiyorum. Assembly.UnloadType veya .dll'yi değiştirmeme izin verecek bir şey gibi bir şey? Yoksa bunu "hafızamda" mı yapmalıyım? düşünceler? teşekkürler
BuddyJoe

En iyi çözüm buysa, "bellekte" olan şeyi yapmanın doğru yolunu bilmiyorum sanırım.
BuddyJoe

Ayrıntıları hatırlamıyorum (ve bir süreliğine bilgisayarımdan uzaklaşıyorum), ancak bir Montajın AppDomain başına yalnızca bir kez yüklenebileceğine inanıyorum - bu nedenle her bir Assembly örneği için yeni AppDomains oluşturmanız gerekir ( ve Montajları bunlara yükleyin) yoksa Montajın yeni bir sürümünü derlemeden önce uygulamanızı yeniden başlatmanız gerekir.
Jeff Sternal

12

Dinamik olarak derlemek, yüklemek ve C # çalıştırmak için CS-Script kullanan kural motorumda tam olarak aradığınız şeyi yapıyorum . Aradığınız şeye kolayca çevrilebilir ve bir örnek vereceğim. İlk olarak, kod (soyulmuş):

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;
        }
    }
}

Bu, T tipi bir arabirimi alır, bir .cs dosyasını bir derlemede derler, belirli bir türdeki bir sınıfı başlatır ve bu başlatılan sınıfı T arabirimiyle hizalar. Temel olarak, yalnızca başlatılan sınıfın bu arabirimi uyguladığından emin olmanız gerekir. Her şeyi kurmak ve erişmek için özellikleri kullanıyorum, örneğin:

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; }
}

Örneğiniz için, Run () 'ı çağırmak istiyorsunuz, bu nedenle Run () yöntemini tanımlayan bir arabirim oluşturabilirim, örneğin:

public interface ITestRunner
{
    void Run();
}

Ardından, aşağıdaki gibi uygulayan bir sınıf oluşturun:

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

RulesEngine'in adını TestHarness gibi bir şeye değiştirin ve özelliklerinizi ayarlayın:

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; }
}

Ardından, onu aramak istediğiniz her yerde koşabilirsiniz:

ITestRunner testRunner = TestHarnessInterface;

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

Muhtemelen bir eklenti sistemi için harika çalışır, ancak benim kodum olduğu gibi bir dosyanın yüklenmesi ve çalıştırılmasıyla sınırlıdır, çünkü tüm kurallarımız tek bir C # kaynak dosyasındadır. Yine de çalıştırmak istediğiniz her biri için tür / kaynak dosyasını geçirmek için onu değiştirmenin oldukça kolay olacağını düşünürdüm. Kodu alıcıdan bu iki parametreyi alan bir yönteme taşımanız yeterlidir.

Ayrıca, ITestRunner yerine IRunnable'ınızı kullanın.


@Interface nedir? burada çok güzel fikirler. bunu tamamen sindirmeniz gerekiyor. +1
BuddyJoe

çok ilginç C # ayrıştırıcısının bir değişken adının veya bir @ "" dizesinin parçası olup olmadığını görmek için @ 'yi geçen bir karaktere bakması gerektiğini bilmiyordum.
BuddyJoe

Teşekkürler. Değişken adı bir anahtar sözcük olduğunda, değişken adından önceki @ kullanılır. Bir değişkeni "sınıf", "arabirim", "yeni", vb. Adlandıramazsınız ... Ancak başına @ eklerseniz yapabilirsiniz. Büyük "I" olan durumumda muhtemelen önemli değil, ama ben onu bir otomatik mülke dönüştürmeden önce başlangıçta alıcı ve ayarlayıcıya sahip dahili bir değişkendi.
Chris Doggett

Doğru. @ Şeyini unuttum. Jeff Sternal'a "hafızadaki şey" hakkında sormak zorunda olduğum soruyu nasıl ele alırdın? Sanırım şu andaki en büyük sorunum dinamik .dll'yi oluşturup yükleyebiliyorum, ancak bunu yalnızca bir kez yapabiliyorum. Montajın nasıl "boşaltılacağını" bilmiyorum. Başka bir AppDomain oluşturmak mümkün mü, montajı o alana yükleyin, kullanın ve ardından bu ikinci AppDomain'i kaldırın. Durulama. Tekrar et.?
BuddyJoe

1
İkinci bir AppDomain kullanmadığınız sürece derlemeyi kaldırmanın bir yolu yoktur. CS-Script'in bunu dahili olarak nasıl yaptığından emin değilim, ancak kural motorumun çıkarmış olduğum kısmı, dosya her değiştiğinde LoadRules () 'i otomatik olarak tekrar çalıştıran bir FileSystemWatcher. Kuralları düzenleriz, müşterileri bu dosyanın üzerine yazan kullanıcılara göndeririz, FileSystemWatcher değişiklikleri fark eder ve geçici dizine başka bir dosya yazarak DLL'yi yeniden derler ve yeniden yükler. İstemci başladığında, ilk dinamik derlemeden önce bu dizini temizler, bu yüzden bir ton artakalanımız kalmaz.
Chris Doggett

6

"TestRunner" türünü almak için yansıma kullanmanız gerekecek. Assembly.GetType yöntemini kullanın.

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();
    }
}

Bu MethodInfo, türden ve çağrıdan uygun olanı aldığınız bir adımı kaçırmıyor Invokemu? (Orijinal soruyu arayanın söz konusu Tür hakkında hiçbir şey bilmediğini belirtmek olarak anladım.)
Jeff Sternal

Bir şeyi kaçırıyorsun. TestRunner yazmak için obj çevirmelisiniz. var obj = (TestRunner) Activator.CreateInstance (tür);
BFree

Tyndall aslında bu dll'yi daha önceki bir adımda inşa ediyor gibi görünüyor. Bu uygulama, Run () yönteminin zaten var olduğunu ve parametresi olmadığını bildiğini varsayar. Bunlar gerçekten bilinmiyorsa, biraz daha derinlemesine düşünmesi gerekir
William Edmondson

hmmm. TestRunner, dinamik yazılı kodumun içindeki bir sınıftır. Dolayısıyla, örneğinizdeki bu statik kod TestRunner'ı çözemez. Ne olduğu hakkında hiçbir fikri yok.
BuddyJoe

@WilliamEdmondson "(TestRunner)" burada bahsedilmediği için kodda nasıl kullanılır?
Antoops

2

Montajınızı oluşturduğunuzda, telefonla arayabilir AssemblyBuilder.SetEntryPointve sonra geri alabilirsiniz.Assembly.EntryPoint mülkten .

Bu imzayı kullanmak isteyeceğinizi ve isimlendirilmesi gerekmediğini unutmayın Main:

static void Run(string[] args)

AssemblyBuilder nedir? CodeDomProvider'ı ve ardından "provider.CompileAssemblyFromSource" u deniyordum
u deniyordum
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.