2013-04-04 1 views
3

A) Сборка C# EXE и DLL на лету относительно проста.
B) Выполнение EXE означает, что запущено новое приложение. Загрузка DLL означает, что методы и функции могут использоваться в случаях, которые могут быть разделены между приложениями или проектами.

Теперь, самый быстрый и простой способ для компиляции EXE (или с умеренными изменениями, DLL) можно найти из MSDN или для вашего удобства:
Как скомпилировать C# DLL на лету, загрузить и использовать

private bool CompileCSharpCode(string script) 
{ 
lvErrors.Items.Clear(); 
    try 
    { 
     CSharpCodeProvider provider = new CSharpCodeProvider(); 
     // Build the parameters for source compilation. 
     CompilerParameters cp = new CompilerParameters 
     { 
      GenerateInMemory = false, 
      GenerateExecutable = false, // True = EXE, False = DLL 
      IncludeDebugInformation = true, 
      OutputAssembly = "eventHandler.dll", // Compilation name 
     }; 

     // Add in our included libs. 
     cp.ReferencedAssemblies.Add("System.dll"); 
     cp.ReferencedAssemblies.Add("System.Windows.Forms.dll"); 
     cp.ReferencedAssemblies.Add("Microsoft.VisualBasic.dll"); 

     // Invoke compilation. This works from a string, but you can also load from a file using FromFile() 
     CompilerResults cr = provider.CompileAssemblyFromSource(cp, script); 
     if (cr.Errors.Count > 0) 
     { 
      // Display compilation errors. 
      foreach (CompilerError ce in cr.Errors) 
      { 
       //I have a listview to display errors. 
       lvErrors.Items.Add(ce.ToString()); 
      } 
      return false; 
     } 
     else 
     { 
      lvErrors.Items.Add("Compiled Successfully."); 
     } 
     provider.Dispose(); 
    } 
    catch (Exception e) 
    { 
     // never really reached, but better safe than sorry? 
     lvErrors.Items.Add("SEVERE! "+e.Message + e.StackTrace.ToString()); 
     return false; 
    } 
    return true; 
} 

Теперь вы можете скомпилировать на лета , существует несколько различий между загрузкой библиотеки DLL. Как правило, вы добавляете его в качестве ссылки в Visual Studios для компиляции в проект. Это довольно легко, и вы, вероятно, сделали это много раз, но мы хотим использовать его в нашем текущем проекте, и мы не можем очень требовать от пользователя перекомпилировать весь проект каждый раз, когда они хотят протестировать свою новую DLL , Поэтому я просто буду обсуждать, как загрузить библиотеку «на лету». Другой термин здесь был бы «программным». Чтобы сделать это, после успешной компиляции, мы загружаем сборочного следующим образом:

Assembly assembly = Assembly.LoadFrom("yourfilenamehere.dll"); 

Если у вас есть AppDomain, вы можете попробовать это:

Assembly assembly = domain.Load(AssemblyName.GetAssemblyName("yourfilenamehere.dll")); 


Теперь, Lib «ссылается», мы можем его открыть и использовать. Есть два способа сделать это. Один требует, чтобы вы знали, есть ли у метода параметры, другой проверит вас. Я сделаю это позже, вы можете проверить MSDN на другой.

// replace with your namespace.class 
Type type = assembly.GetType("company.project"); 
if (type != null) 
{ 
    // replace with your function's name 
    MethodInfo method = type.GetMethod("method"); 

    if (method != null) 
    { 
     object result = null; 
     ParameterInfo[] parameters = method.GetParameters(); 
     object classInstance = Activator.CreateInstance(type, null); 
     if (parameters.Length == 0) // takes no parameters 
     { 
       // method A: 
      result = method.Invoke(classInstance, null); 
       // method B: 
      //result = type.InvokeMember("method", BindingFlags.InvokeMethod, null, classInstance, null); 
     } 
     else // takes 1+ parameters 
     { 
      object[] parametersArray = new object[] { }; // add parameters here 

       // method A: 
      result = method.Invoke(classInstance, parametersArray); 
       // method B: 
      //result = type.InvokeMember("method", BindingFlags.InvokeMethod, null, classInstance, parametersArray); 
     } 
    } 
} 

ПРОБЛЕМА: Первая компиляция отлично работает. Первое исполнение работает отлично. Тем не менее, попытка перекомпиляции будет ошибкой, заявив, что ваша * .PDP (база отладчика) используется. Я слышал некоторые намеки на маршалинг и AppDomains, но я не совсем понял проблему. Повторная компиляция завершится с ошибкой только после загрузки DLL.


Текущая попытка Маршалинг & & AppDomain:

class ProxyDomain : MarshalByRefObject 
    { 
     private object _instance; 
     public object Instance 
     { 
      get { return _instance; } 
     } 
     private AppDomain _domain; 
     public AppDomain Domain 
     { 
      get 
      { 
       return _domain; 
      } 
     } 
     public void CreateDomain(string friendlyName, System.Security.Policy.Evidence securityinfo) 
     { 
      _domain = AppDomain.CreateDomain(friendlyName, securityinfo); 
     } 
     public void UnloadDomain() 
     { 
      try 
      { 
       AppDomain.Unload(_domain); 
      } 
      catch (ArgumentNullException dne) 
      { 
       // ignore null exceptions 
       return; 
      } 
     } 
     private Assembly _assembly; 
     public Assembly Assembly 
     { 
      get 
      { 
       return _assembly; 
      } 
     } 
     private byte[] loadFile(string filename) 
     { 
      FileStream fs = new FileStream(filename, FileMode.Open); 
      byte[] buffer = new byte[(int)fs.Length]; 
      fs.Read(buffer, 0, buffer.Length); 
      fs.Close(); 

      return buffer; 
     } 
     public void LoadAssembly(string path, string typeName) 
     { 
      try 
      { 
       if (_domain == null) 
        throw new ArgumentNullException("_domain does not exist."); 
       byte[] Assembly_data = loadFile(path); 
       byte[] Symbol_data = loadFile(path.Replace(".dll", ".pdb")); 

       _assembly = _domain.Load(Assembly_data, Symbol_data); 
       //_assembly = _domain.Load(AssemblyName.GetAssemblyName(path)); 
       _type = _assembly.GetType(typeName); 
      } 
      catch (Exception ex) 
      { 
       throw new InvalidOperationException(ex.ToString()); 
      } 
     } 
     private Type _type; 
     public Type Type 
     { 
      get 
      { 
       return _type; 
      } 
     } 
     public void CreateInstanceAndUnwrap(string typeName) 
     { 
      _instance = _domain.CreateInstanceAndUnwrap(_assembly.FullName, typeName); 
     } 
    } 

Ошибки _instance = _domain.CreateInstanceAndUnwrap (_assembly.FullName, TYPENAME); говоря, что моя Ассамблея не сериализуема. Попробовал добавить [Serializable] тег в мой класс без везения. Все еще исследует исправления.

Кажется, что вещи могут немного запутываться, когда вы не можете видеть, как они используются, так что это облегчает?

private void pictureBox1_Click(object sender, EventArgs e) 
    { 
     pd.UnloadDomain(); 

     if (CompileCSharpCode(header + tScript.Text + footer)) 
     { 
      try 
      { 
       pd.CreateDomain("DLLDomain", null); 
       pd.LoadAssembly("eventHandler.dll", "Events.eventHandler"); 
       pd.CreateInstanceAndUnwrap("Events.eventHandler"); // Assembly not Serializable error! 

       /*if (pd.type != null) 
       { 
        MethodInfo onConnect = pd.type.GetMethod("onConnect"); 

        if (onConnect != null) 
        { 
         object result = null; 
         ParameterInfo[] parameters = onConnect.GetParameters(); 
         object classInstance = Activator.CreateInstance(pd.type, null); 
         if (parameters.Length == 0) 
         { 
          result = pd.type.InvokeMember("onConnect", BindingFlags.InvokeMethod, null, classInstance, null); 
          //result = onConnect.Invoke(classInstance, null); 
         } 
         else 
         { 
          object[] parametersArray = new object[] { }; 

          //result = onConnect.Invoke(classInstance, parametersArray); 
          //result = type.InvokeMember("onConnect", BindingFlags.InvokeMethod, null, classInstance, parametersArray); 
         } 
        } 
       }*/ 
       //assembly = Assembly.LoadFrom(null); 

      } 
      catch (Exception er) 
      { 
       MessageBox.Show("There was an error executing the script.\n>" + er.Message + "\n - " + er.StackTrace.ToString()); 
      } 
      finally 
      { 
      } 
     } 
    } 
+0

Ваш процесс уже загрузил DLL, и вы не можете перезаписать это. – leppie

+2

Вы хотите создать новый домен приложения и загрузить в него DLL. Затем, если вы хотите перекомпилировать, вы разорвите этот домен приложения. Таким образом, ваша основная программа работает в одном домене приложения, и она создает и управляет другим доменом приложения, который используется для загрузки сборки, над которой вы работаете. –

+0

Вам нужны файлы PDB? Отлаживаются ли люди с помощью VS? Set IncludeDebugInformation = false, если они этого не делают. – MrMoDoJoJr

ответ

8

После того, как вы загрузили DLL в (AppDomain по умолчанию) в запущенном процессе, файл на диске не могут быть перезаписаны, пока процесс не будет завершен. DLL не могут быть выгружены в управляемом коде, как в неуправляемом коде.

Необходимо создать новое приложение в хост-процессе и загрузить новую созданную DLL-сборку в этот appdomain. Когда вы готовы скомпилировать новую версию DLL, вы можете избавиться от приложения. Это приведет к выгрузке DLL из памяти и освобождению блокировки DLL-файла, чтобы вы могли скомпилировать новую DLL в тот же файл. Затем вы можете создать новый appdomain для загрузки новой библиотеки DLL.

Основная опасность использования доменов приложений заключается в том, что все вызовы через границу домена должны быть распределены, подобно IPC или RPC сети. Попытайтесь сохранить интерфейс объектов, которые вам нужны, чтобы вызвать границу приложениядомен до минимума.

Вы также можете скомпилировать сборку в память, получив массив байтов или поток в качестве вывода, а затем загрузите эту сборку в отдельный appdomain. Это позволяет избежать образования мусора на диске, который в конечном итоге необходимо будет удалить.

Не используйте компиляцию в память в качестве обходного пути для проблемы с блокировкой файлов. Основная проблема заключается в том, что сборки не могут быть удалены из памяти, когда они загружаются в стандартное приложение домена. Вы ДОЛЖНЫ создать новый appdomain и загрузить DLL в этот appdomain, если вы хотите выгрузить эту сборку из памяти позже в течение всего процесса.

Вот грубый набросок того, как построить объект в контексте другого AppDomain:

var appdomain = AppDomain.CreateDomain("scratch"); 
    byte[] assemblyBytes = // bytes of the compiled assembly 
    var assembly = appdomain.Load(assemblyBytes); 
    object obj = appdomain.CreateInstanceAndUnwrap(assembly.FullName, "mynamespace.myclass"); 

После этой последовательности, obj будет содержать ссылку на прокси-сервер, который ссылается на текущий экземпляр объекта внутри AppDomain , Вы можете ссылаться на методы obj с использованием отражения или типа obj на общий тип интерфейса и методы вызова напрямую. Будьте готовы внести коррективы для поддержки RPC-сортировки параметров вызова метода. (см. раздел «Удаленный доступ к .NET»)

При работе с несколькими доменами приложения вы должны быть осторожны с тем, как вы обращаетесь к типам и сборкам, потому что многие функции .NET по умолчанию работают в текущем домене приложения вызывающего, что обычно не является вы хотите, когда у вас есть несколько приложений. compilerResult.CompiledAssembly, например, внутренне выполняет нагрузку генерируемой сборки в домене вызывающего абонента. Вы хотите загрузить сборку в другую область приложения. Вы должны сделать это явно.

Update: В вашем недавно добавленным фрагмент кода, показывающий, как вы загрузите AppDomain, эта линия ваша проблема:

_assembly = Assembly.LoadFrom(path); 

Это загружает DLL в текущего AppDomain (AppDomain вызывающего абонента), а не в целевой appdomain (ссылка на _domain в вашем примере). Вам нужно использовать _domain.Load(), чтобы загрузить сборку в , что appdomain.

+1

+1: Хорошее объяснение :) – leppie

+0

исправил фрагмент, но я получаю 'Type 'Events.eventHandler' in assembly ' eventHandler, Version = 0.0.0.0, Culture = neutral, PublicKeyToken = null 'не помечен как сериализуемый. 'при попытке сохранить' _instance = _domain.CreateInstanceAndUnwrap (_assembly.FullName, typeName); ' – Komak57

+0

@ Komak57 Теперь вы получаете в RPC/.net удаленный доступ, чтобы выставить исходный объект по границе приложения. Вы можете либо внести корректировки в целевой тип, чтобы он был сериализуемым, либо добавить фиктивный класс в целевую сборку с минимальными аргументами свойств и методов (тип строковых данных легко сериализуется, сложные типы сложнее) и вызывать класс фиктивных элементов на границе приложения, а фиктивный класс управляет целевым объектом изнутри приложения. Это может сэкономить вам некоторую головную боль от создания сериализуемого целевого типа. – dthorpe

1

, если вам не нужно отлаживать или не против отлаживать «динамический» код с некоторой отсутствующей информацией. вы можете сгенерировать код в памяти .. это позволит вам скомпилировать код несколько раз .. но не будет генерировать a.PDB

cp.GenerateInMemory = true; 

в альтернативной, если у вас нет необходимости в состоянии найти сборку на диске вы можете попросить компилятор сбросить весь код в временный каталог и генерировать имя Темп для библиотеки DLL (которым всегда будет уникальный)

cp.TempFiles = new TempFileCollection(Path.GetTempPath(), false); 
//cp.OutputAssembly = "eventHandler.dll"; 

в обоих этих случаях, чтобы получить доступ к DLL, и это типа вы можете получить его от компилятора результатов

Assembly assembly = cr.CompiledAssembly; 

явно не загружается необходимо

, но если не применимы эти ситуации, и вы должны и физическое .dll с .pdp в известной папке. Единственный совет, который я могу вам дать, чтобы поместить номер версии в dll .. и в случае, если вы не имеете простой способ контролировать количество раз длл был составлен, вы всегда можете прибегнуть к временной метки ..

cp.OutputAssembly = "eventHandler"+DateTime.Now.ToString("yyyyMMddHHmmssfff")+".dll"; 

, конечно, вы должны понимать, что каждый раз, когда вы собираете новое .dll будет загружаться в память и не выгружаться, если вы не используете отдельные домены приложений .. но это выходит за рамки этого вопроса.