Replacing dll at runtime without pain - c#

I have a basic WinForm Solution (MS VS2013, .Net framework 4.5) for test some methods included in a Dll, using Reflection. My goal is test the main application that it can run two methods of Dll (without referencing the dll in project), and later, run four methods (2 methods added) without stop the main application and run it again, using reflection.
Reflections works fine (if I stop the main application, replacing the dll file and run the main application again, all works fine), but I can't replace the dll at runtime.
The main application has a Timer control with an interval of 60 seconds. Every 60 seconds, a method is executed that checks if a DLL file is in a folder. If a DLL file exists in that folder, I want to use the new DLL in the main application (running) because the new DLL contains old methods (first DLL) and additional methods that the main application needs.
However, I am getting an error that the file is in use.
I have read several posts, questions, answers, MEF documentation, AppDomains related, but I have been unable to concatenate the information to be able to implement a solution.
Actually, I thought a lot before post this question, but I confess that I prefer to spend a moment of shame, knowing that you can give me a hand.
It would be for me a great help if you help me with code and specific instructions.
This is the code:
Main application:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Reflection;
using System.IO;
namespace testDLLs
{
public partial class Principal : Form
{
public Principal()
{
InitializeComponent();
}
private void Principal_Load(object sender, EventArgs e)
{
labelVersionDll.Text = metodosApoyo.RunDLLFunction("VersionOperaciones");
string cListaOperaciones = metodosApoyo.RunDLLFunction("ListaOperaciones");
string[] aOperaciones = cListaOperaciones.Split('|');
comboBoxOperaciones.Items.Clear();
foreach (string cOperacion in aOperaciones)
{
comboBoxOperaciones.Items.Add(cOperacion);
}
timerForUpdate.Interval = 60000;
timerForUpdate.Enabled = true;
}
private void buttonRun_Click(object sender, EventArgs e)
{
int iOperador1, iOperador2;
string resultadoDesdeDll = null;
string cOperacionSeleccionada;
Int32.TryParse(textBoxOperador1.Text, out iOperador1);
Int32.TryParse(textBoxOperador2.Text, out iOperador2);
cOperacionSeleccionada = comboBoxOperaciones.GetItemText(comboBoxOperaciones.SelectedItem);
object[] parametersArray = new object[] { iOperador1, iOperador2 };
resultadoDesdeDll = metodosApoyo.RunDLLFunction(cOperacionSeleccionada, parametersArray);
textBoxResultado.Text = resultadoDesdeDll;
}
private void timerForUpdate_Tick(object sender, EventArgs e)
{
labelUpdateStatus.Text = "Checking updates ...";
notifyIconUpdate.Visible = true;
notifyIconUpdate.BalloonTipText = "Instalando nuevo DLL....";
notifyIconUpdate.BalloonTipTitle = "Info:";
notifyIconUpdate.ShowBalloonTip(5000);
if (File.Exists(#"C:\DLLsForCopy\OperacionesDLL.dll"))
{
File.Copy(#"C:\DLLsForCopy\OperacionesDLL.dll", #"D:\DLLs\OperacionesDLL.dll", true);
labelVersionDll.Text = metodosApoyo.RunDLLFunction("VersionOperaciones");
string cListaOperaciones = metodosApoyo.RunDLLFunction("ListaOperaciones");
string[] aOperaciones = cListaOperaciones.Split('|');
comboBoxOperaciones.Items.Clear();
foreach (string cOperacion in aOperaciones)
{
comboBoxOperaciones.Items.Add(cOperacion);
}
}
labelUpdateStatus.Text = "";
notifyIconUpdate.Visible = false;
}
}
}
Class in project, for some functions for Main application:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
namespace testDLLs
{
class metodosApoyo
{
public static string RunDLLFunction(string cMetodo, object[] aParametros = null)
{
string cRetornoGlobal = "";
Object resultado = null;
string cOperacionSeleccionada = cMetodo;
Assembly assembly = Assembly.LoadFile(#"D:\DLLs\OperacionesDLL.dll");
Type type = assembly.GetType("OperacionesDLL.Operaciones");
MethodInfo methodInfo = type.GetMethod(cOperacionSeleccionada);
ParameterInfo[] parameters = methodInfo.GetParameters();
object classInstance = Activator.CreateInstance(type, null);
object[] parametersArray = null;
if (aParametros != null)
{
parametersArray = new object[aParametros.Length];
int i = 0;
foreach (object value in aParametros)
{
parametersArray[i] = aParametros[i];
i++;
}
}
resultado = methodInfo.Invoke(methodInfo, parametersArray);
cRetornoGlobal = (string)resultado;
return cRetornoGlobal;
}
}
}
DLL source (OperacionesDLL.dll):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OperacionesDLL
{
public class Operaciones
{
public static string VersionOperaciones()
{
string retorno;
retorno = "1.0701-16";
return retorno;
}
public static string ListaOperaciones()
{
string retorno;
retorno = "Suma|Resta|Multiplicación|División";
return retorno;
}
public static string Suma(int operador1, int operador2)
{
int resultado;
string retorno;
resultado = operador1 + operador2;
retorno = resultado.ToString();
return retorno;
}
public static string Resta(int operador1, int operador2)
{
int resultado;
string retorno;
resultado = operador1 - operador2;
retorno = resultado.ToString();
return retorno;
}
public static string Multiplicación(int operador1, int operador2)
{
int resultado;
string retorno;
resultado = operador1 * operador2;
retorno = resultado.ToString();
return retorno;
}
public static string División(int operador1, int operador2)
{
int resultado;
string retorno;
resultado = operador1 / operador2;
retorno = resultado.ToString();
return retorno;
}
}
}
Thanks in advance.

You can do what you're describing by using the Managed Addin Framework (MAF) in the System.Addin namespace. I've used it to write apps that scans a folder for DLLs and dynamically loads them. You can also use it to unload and reload DLLs as they appear and disappear from the folder.

Related

Cannot figure out 'The name does not exist in the current context; Error

I want to assign my program a Guid and use it to pull config data from the SQL server. However, I keep getting the error:
The name 'ApplicationID' does not exist in the current context
I've always struggled with variable declaration. I can usually figure it out, but I'm stuck on this one....
Program.cs creates the ApplicationID, then I call LoadConfig to grab and set the rest of the variables based on that ApplicationID. However when I go to use the ApplicationID in LoadConfig it gives me that error.
I confirmed that if I replaced the ApplicationID with the Guid string it pulls everything as expected.
I also tried adding using ABBAS.ProgC to Config, but that didn't help either.
using System;
using System.Collections.Generic;
using ABBAS.sFTP;
using Renci.SshNet;
using Renci.SshNet.Sftp;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.DependencyInjection;
using System.Diagnostics;
using System.Reflection;
namespace ABBAS.ProgC {
class Program {
static private Guid processid;
static private SftpClient sftpc;
static private sFTPFiles sFTPFiles;
static public Guid ApplicationID = new Guid("d71b536f-1428-4797-95e6-3948e736fcca");
static async Task Main() {
ManualResetEvent exitEvent = new(false);
TaskCompletionSource tcs = new();
Console.WriteLine($"Program Starting at {DateTime.Now}");
Console.WriteLine($"Process id: {Environment.ProcessId}");
programversion = FileVersionInfo.GetVersionInfo(System.Reflection.Assembly.GetExecutingAssembly().Location).FileVersion;
Console.WriteLine($"Version: {programversion}");
Config.LoadConfig();
...
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Thinktecture.IdentityModel.Client;
using System.Data.SqlClient;
using Newtonsoft.Json.Linq;
using ABBASLibrary.Configuration;
using System.Text.Json;
using System.Data;
namespace ABBAS {
static public partial class Config {
// Environmental Variables
static public int ConfigurationID;
//Global Variables
static public string sFTPServer;
static public string sFTPServerUID;
static public string sFTPServerPWD;
static public int sFTPServerPort;
static public int sFTPRetryMax;
static public int sFTPConnectDelayMin;
static public int sFTPConnectDelayMax;
static public void LoadConfig() {
using (SqlConnection _sqlconn = new SqlConnection())
{
_sqlconn.ConnectionString = sqlconnstring;
_sqlconn.Open();
SqlDataAdapter da;
DataSet ds = new DataSet();
string varName;
string varValue;
int newNum;
bool isParsable;
try
{
String query = "SELECT * FROM [ConfigurationDetails] WHERE [ApplicationID] = " + ApplicationID;
da = new SqlDataAdapter(query, _sqlconn);
da.Fill(ds);
for (int cnt = 0; cnt < ds.Tables[0].Rows.Count; cnt++)
{
varName = "";
varValue = "";
varName = ds.Tables[0].Rows[cnt][4].ToString(); // VariableName
varValue = ds.Tables[0].Rows[cnt][5].ToString(); // VariableValue
if (varName.ToString() == "sFTPServer")
sFTPServer = varValue;
if (varName.ToString() == "sFTPServerUID")
sFTPServerUID = varValue;
if (varName.ToString() == "sFTPServerPWD")
sFTPServerPWD = varValue;
if (varName.ToString() == "P21CompanyID")
P21CompanyID = varValue;
if (varName.ToString() == "sFTPServerPort")
{
isParsable = Int32.TryParse(varValue, out newNum);
if (isParsable)
sFTPServerPort = newNum;
}
if (varName.ToString() == "sFTPRetryMax")
{
isParsable = Int32.TryParse(varValue, out newNum);
if (isParsable)
sFTPRetryMax = newNum;
}
if (varName.ToString() == "sFTPConnectDelayMin")
{
isParsable = Int32.TryParse(varValue, out newNum);
if (isParsable)
sFTPConnectDelayMin = newNum;
}
if (varName.ToString() == "sFTPConnectDelayMax")
{
isParsable = Int32.TryParse(varValue, out newNum);
if (isParsable)
sFTPConnectDelayMax = newNum;
}
}
}
catch (Exception ex)
{
_sqlconn.Close();
}
finally
{
if (_sqlconn != null)
_sqlconn.Dispose();
if (ds != null)
ds.Dispose();
}
}
}
}
}
The original idea (not mine!) was to create the variable in Config, and they set it in Program.cs, but since we are calling LoadConfig and need the variable set to get the rest of our variables that clearly won't work.
Because it doesn't exist in that context. It's declared as a static member of the Program class:
class Program {
//...
static public Guid ApplicationID = new Guid("d71b536f-1428-4797-95e6-3948e736fcca");
To access it you'd need to reference it statically from that class:
String query = "SELECT * FROM [ConfigurationDetails] WHERE [ApplicationID] = " + Program.ApplicationID;
Note that there are a few anti-patterns in your code. Including, but not limited to:
Making everything static is going to drastically increase complexity and the potential for bugs. (You've already encountered a problem with it here in this question.) This is generally the approach when developers want "global variables" for everything. It sounds nice at first, because that way you always have access to everything and can use what you want when you want. But it quickly becomes problematic because you always have access to everything and can't effectively isolate any of the code into meaningful components.
Silently ignoring exceptions is going to make it very difficult to identify, diagnose, and correct problems. At the very least there should be some logging or output of exception information.

Managed-code action not changing property

I am attempting to use a managed code action in an InstallShield suite project.
I followed their example in hopes it wouldn't be a big deal.
http://helpnet.flexerasoftware.com/installshield21helplib/helplibrary/SteActionManagedCd.htm
Ideally, I want to have my .dll method to change an install shield property when the action is executed. When I test out the installer to see if the property changed, I get the default value I set.
Maybe I am doing it wrong, any suggestions would be greatly appreciated.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Microsoft.Web.Administration;
namespace IISHelp
{
[Guid("BAFAEAED-08C6-4679-B94E-487A4D89DE63")]
[TypeLibType(4288)]
public interface ISuiteExtension
{
[DispId(1)]
string get_Attribute(string bstrName);
[DispId(2)]
void LogInfo(string bstr);
[DispId(3)]
string get_Property(string bstrName);
[DispId(3)]
void set_Property(string bstrName, string bstrValue);
[DispId(4)]
string FormatProperty(string bstrValue);
[DispId(5)]
string ResolveString(string bstrStringId);
}
public class Help
{
const UInt32 ERROR_INSTALL_FAILURE = 1603;
const UInt32 ERROR_SUCCESS = 0;
ISuiteExtension GetExtensionFromDispatch(object pDispatch)
{
if (Marshal.IsComObject(pDispatch) == false)
throw new ContextMarshalException("Invalid dispatch object passed to CLR method");
return (ISuiteExtension)pDispatch;
}
public UInt32 IsUniqueName(object pDispatch)
{
try
{
List<string> currentVirtualDirectories = GetApplicationNames();
ISuiteExtension suiteExtension = GetExtensionFromDispatch(pDispatch);
var name = suiteExtension.get_Property("APPLICATION_NAME");
suiteExtension.set_Property("IS_UNIQUE_APPLICATION_NAME", (!currentVirtualDirectories.Contains(name)).ToString());
}
catch (System.ContextMarshalException)
{
return ERROR_INSTALL_FAILURE;
}
return ERROR_SUCCESS;
}
public List<string> GetApplicationNames()
{
List<string> currentVirtualDirectories = new List<string>();
ServerManager mgr = new ServerManager();
foreach (Site s in mgr.Sites)
{
foreach (Application app in s.Applications)
{
currentVirtualDirectories.Add(app.Path.Replace('/', ' ').Trim());
}
}
return currentVirtualDirectories.Where(s => !string.IsNullOrWhiteSpace(s)).Distinct().ToList();
}
}
}

SymbolFinder.FindReferencesAsync doesn't find anything

I want to find all PropertyChangedEventHandler events in my solution, and then find all listeners added to those events. But I can't seem to get a list of the events.
This is all the code in the solution being analyzed:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RoslynTarget
{
public class Example : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public void DoNothing() { }
}
}
And this is my code for analyzing it. references.Count == 1 and r.Locations.Count == 0, but exactly one location should be found. What's going on here?
Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.MSBuild;
namespace RoslynTest
{
class Program
{
static void Main(string[] args)
{
const string solutionPath = #"C:\Users\<user>\Code\RoslynTarget\RoslynTarget.sln";
const string projectName = "RoslynTarget";
var msWorkspace = MSBuildWorkspace.Create(new Dictionary<string, string> { { "CheckForSystemRuntimeDependency", "true" } });
var solution = msWorkspace.OpenSolutionAsync(solutionPath).Result;
var project =
solution.Projects
.Where(proj => proj.Name == projectName)
.First();
var compilation = project.GetCompilationAsync().Result;
var eventType = compilation.ResolveType("System.ComponentModel.PropertyChangedEventHandler").First();
var references = SymbolFinder.FindReferencesAsync(eventType, solution).Result;
foreach (var r in references)
{
foreach (var loc in r.Locations)
{
// ...
}
}
}
}
}
Extensions.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
namespace RoslynTest
{
public static class Extensions
{
public static IEnumerable<INamedTypeSymbol> ResolveType(this Compilation compilation, string classFullName)
{
return new IAssemblySymbol[] { compilation.Assembly }
.Concat(compilation.References
.Select(compilation.GetAssemblyOrModuleSymbol)
.OfType<IAssemblySymbol>())
.Select(asm => asm.GetTypeByMetadataName(classFullName))
.Where(cls => cls != null);
}
}
}
Recently I've done similar thing where I was trying to find the reference of a method in complete solution.
To use FindReferenceAsync you have create symantic model first and find the symbol from there. Once you have the symbol you can use the FindReferenceAsync.
Here's the snippet that I used and it's working:
var solution = msWorkspace.OpenSolutionAsync(solutionPath).Result;
foreach (var project in solution.Projects)
{
foreach (var document in project.Documents)
{
var model = document.GetSemanticModelAsync().Result;
var methodInvocation = document.GetSyntaxRootAsync().Result;
InvocationExpressionSyntax node = null;
try
{
node = methodInvocation.DescendantNodes().OfType<InvocationExpressionSyntax>()
.Where(x => ((MemberAccessExpressionSyntax)x.Expression).Name.ToString() == methodName).FirstOrDefault();
if (node == null)
continue;
}
catch(Exception exception)
{
// Swallow the exception of type cast.
// Could be avoided by a better filtering on above linq.
continue;
}
methodSymbol = model.GetSymbolInfo(node).Symbol;
found = true;
break;
}
if (found) break;
}
foreach (var item in SymbolFinder.FindReferencesAsync(methodSymbol, solution).Result)
{
foreach (var location in item.Locations)
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("Project Assembly -> {0}", location.Document.Project.AssemblyName);
Console.ResetColor();
}
}
Here's the complete working code. If you want to visualize the Roslyn tree then you can try using Roslyn tree visualizer to see code structuring.
Update
As per discussion in comments with OP Installing Roslyn SDK fixed the issue. Assuming that there might be some updates in SDK to generate Symbols information as compared to Nuget dlls when using GetCompilationAsync.

C# dll using issue

I am having an issue with compiling a C# dll from a piece of code I wrote. it compiles just fine with no errors, but when I try to include it to a visual studio 2010 C# application the namespace does not show up while trying to call it from the "using" command.
Here is the code of the .dll file:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using PAD_SCRIPT;
using System.Threading;
namespace PARKER_SCRIPT
{
class PScript
{
//LOAD_REF
private void loadCustomScripts()
{
}
//END_REF
PADScript p = new PADScript();
private void loadScripts()
{
}
public PScript()
{
}
public void runFile(string file)
{
if (file.ToLower().Contains(".pb"))
{
//file = file.Replace(".pb", "");
p.executeLuaWithThread(file);
Console.ReadLine();
}
else
{
Console.WriteLine("ERROR 1: must be .pb file");
Console.ReadLine();
}
}
Core cp = new Core();
public PADScript loadScipt(PADScript l)
{
loadScripts();
loadCustomScripts();
l.addLuaCommand("runFile", this);
return l;
}
//this will be dynamically be updated when custom cs code gets added
private string[] getPatchNotes()
{
string[] info = null;
try
{
info = System.IO.File.ReadAllLines("info\\patch_notes.txt");
return info;
}
catch (Exception i)
{
Console.WriteLine(i.Message);
return info;
}
}
private string getVersion()
{
string info = null;
try
{
info = System.IO.File.ReadAllLines("info\\version.txt")[0];
return info;
}
catch (Exception i)
{
Console.WriteLine(i.Message);
return info;
}
}
}
}
I don't think the functions in the .dll file is an issue, but by compiling it on the command line I think I am missing a key parameter or something. I know when I compile it in visual studio it works just fine implementing it to a new project. Thank you in advance.
edit: here is the command line I did:
C:\WINDOWS\Microsoft.Net\Framework\v4.0.30319\csc.exe /out:Release\ParkerScript.dll /target:library /platform:x86 /reference:core\LuaInterface.dll /reference:core\System.Speech.dll /reference:core\PAD_SCRIPT.dll /reference:core\lua51.dll core\Program_lib.cs core\AssemblyInfo.cs core\lib\*.cs
The class definition is internal, which will not show up when you reference it.
Define your class like this:
namespace PARKER_SCRIPT
{
public class PScript
{
//Code goes here...
}
}

Compiled class in compiling file

I have a promblem with run-time compiled classes. I have something like this 2 classes:
first class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Program.Bullet {
public class Class1{
private int i;
public Class1(int j){
i=j;
}
}
}
and second class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Program.Bullet;
namespace Program.Object {
public class Class2{
public Class2(){
Class1 c1 = new Class1(5);
}
}
}
This two classes I would like to compile in run-time and use them in my project. So I have function to compile it (XmlNode has data about fullPath etc):
private ModuleBuilder moduleBuilder;
private List<string> isCompiled;
private void Compile(XmlNode compileingClasses) {
foreach (XmlNode compileingClass in compileingClasses) {
string fullPath = compileingClass.Attributes["path"].InnerText;
string type = compileingClass.Attributes["name"].InnerText; ;
if (!isCompiled.Contains(type)) {
isCompiled.Add(type);
var syntaxTree = SyntaxTree.ParseFile("../../File1/File2/" + fullPath);
var comp = Compilation.Create("Test.dll"
, syntaxTrees: new[] { syntaxTree }
, references: metadataRef
, options: comilationOption
);
// Runtime compilation and check errors
var result = comp.Emit(moduleBuilder);
if (!result.Success) {
foreach (var d in result.Diagnostics) {
Console.WriteLine(d);
}
throw new XmlLoadException("Class not found " + fullPath);
}
}
}
}
Is it possible to get the reference on Class1 to Class2?
Edit: Better question
Is it possible to create MetadataReference on compiled Class1?
Something like:
string fullName = bullet.Attributes["fullName"].InnerText;
var o = moduleBuilder.GetType(fullName);
metadataRef.Add(new MetadataFileReference(o.Assembly.Location));
This throw NotSupportedException
You're trying to reference the assembly which is currently being built and I don't think Roslyn can do that.
What you can do instead is to create a single Compilation from all your classes (probably having a separate SyntaxTree for each class). If you do that, you won't need any references.

Categories