Originally Posted by
MaiN
Cool, I have never used that one. Does it allow you to recompile and reload the assembly immediately?
Yeah. You can either directly unload in your app domain inside your app, or in the case of using a proxy dll like yours the same idea. Ignore the ugly code, it works though.
Code:
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Policy;
using System.Threading;
using System.Windows.Forms;
public interface IAssemblyLoader
{
void LoadAndRun(string file, string args);
}
public class Startup
{
/// <summary>
/// The hotkey that can be pressed to re-inject your assembly
/// </summary>
private const Keys ReloadHotKey = Keys.F11;
[DllImport("User32.dll")]
private static extern short GetAsyncKeyState(int vKey);
[STAThread]
public static int EntryPoint(string filePath, string args = "")
{
var firstLoaded = false;
while (true)
//Keep the domain alive to enable reloading, this will hang while the choosen assembly is running
{
if (!firstLoaded)
{
firstLoaded = true;
new SharpDomain(filePath, args);
}
if ((GetAsyncKeyState((int) ReloadHotKey) & 1) == 1)
// ReSharper disable once ObjectCreationAsStatement
new SharpDomain(filePath, args);
Thread.Sleep(10);
}
// ReSharper disable once HeuristicUnreachableCode
#pragma warning disable 162
return 0;
#pragma warning restore 162
}
public static class DomainManager
{
public static AppDomain CurrentDomain { get; set; }
public static WSharpAssemblyLoader CurrentAssemblyLoader { get; set; }
}
public class WSharpAssemblyLoader : MarshalByRefObject, IAssemblyLoader
{
public WSharpAssemblyLoader() => AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
public void LoadAndRun(string file, string args)
{
var asm = Assembly.Load(file);
var staMethods =
asm
.GetTypes()
.SelectMany(type => type.GetMethods())
.First(methodInfo =>
{
var invariant = methodInfo.Name.ToLowerInvariant();
return invariant.Contains("dllmain")
|| invariant == "dllmain";
});
if (staMethods == null)
{
throw new ArgumentNullException(nameof(staMethods));
}
staMethods.Invoke(null, null);
}
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
if (args.Name == Assembly.GetExecutingAssembly().FullName)
{
return Assembly.GetExecutingAssembly();
}
var appDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
var shortAsmName = Path.GetFileName(args.Name);
Trace.Assert(appDir != null, "appDir != null");
Trace.Assert(shortAsmName != null, "shortAsmName != null");
var fileName = Path.Combine(appDir, shortAsmName);
if (File.Exists(fileName))
{
return Assembly.LoadFrom(fileName);
}
return Assembly.GetExecutingAssembly().FullName == args.Name ? Assembly.GetExecutingAssembly() : null;
}
}
/// <summary>
/// The actual domain object we'll be using to load and run the Dysis binaries.
/// </summary>
public class SharpDomain
{
private static readonly Random Random = new Random();
private static string RandomString(int length)
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
return new string(Enumerable.Repeat(chars, length)
.Select(s => s[Random.Next(s.Length)]).ToArray());
}
public SharpDomain(string assemblyName, string args)
{
try
{
var appBase = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
var ads = new AppDomainSetup {ApplicationBase = appBase, PrivateBinPath = appBase};
DomainManager.CurrentDomain = AppDomain.CreateDomain(
"SharpDomain_Internal_" + RandomString(20),
AppDomain.CurrentDomain.Evidence, ads);
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
DomainManager.CurrentAssemblyLoader =
(WSharpAssemblyLoader)
DomainManager.CurrentDomain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName,
typeof(WSharpAssemblyLoader).FullName);
if (appBase != null)
{
var fileToLoadAndRun = Path.Combine(appBase,
assemblyName);
DomainManager.CurrentAssemblyLoader.LoadAndRun(fileToLoadAndRun, args);
}
}
catch (Exception ex)
{
var exception = ex as ReflectionTypeLoadException;
if (exception != null)
{
var typeLoadException = exception;
var loaderExceptions = typeLoadException.LoaderExceptions;
}
SharpLoader.ShowError(ex);
// MessageBox.Show(e.ToString());
}
finally
{
DomainManager.CurrentAssemblyLoader = null;
AppDomain.Unload(DomainManager.CurrentDomain);
}
}
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
try
{
if (args.Name == Assembly.GetExecutingAssembly().FullName)
{
return Assembly.GetExecutingAssembly();
}
var assembly = Assembly.Load(args.Name);
if (assembly != null)
{
return assembly;
}
}
catch
{
// ignore load error
}
// *** NOTE: this doesn't account for special search paths
var parts = args.Name.Split(',');
var file =
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + "\\" + parts[0].Trim() + ".dll";
return Assembly.LoadFrom(file);
}
}
}
and the exports something like
Code:
public static class SharpLoader {
/// <summary>
/// The arguements for the hosted application
/// </summary>
internal static string ApplicationArguments = string.Empty;
/// <summary>
/// The application to be hosted name.
/// </summary>
internal static string ApplicationToHostName = string.Empty;
/// <summary>
/// The application path to be hosted. Must contain the ending '\' slash.
/// </summary>
[MarshalAs(UnmanagedType.LPWStr)]
internal static string ApplicationToHostDirectory = string.Empty;
/// <summary>
/// Hosts the given domain.
/// </summary>
[DllExport]
[STAThread]
public static void HostDomain() {
if (string.IsNullOrEmpty(ApplicationToHostName) || string.IsNullOrEmpty(ApplicationToHostDirectory)) {
var registryKey = Registry.CurrentUser.CreateSubKey("SOFTWARE\\NetProducts\\StylesoftNet");
ApplicationToHostDirectory = registryKey?.GetValue("StylesoftDirectory") + @"";
ApplicationToHostName = "StylesoftNet.dll";
// throw new InvalidDataException("You must set LoadDomainHostSettings before calling HostDomain()");
// return;
}
try {
if (ApplicationToHostName.EndsWith("exe", StringComparison.Ordinal) || ApplicationToHostName.EndsWith("dll", StringComparison.Ordinal)) {
Startup.EntryPoint(Path.Combine(ApplicationToHostDirectory, ApplicationToHostName),
ApplicationArguments);
}
else
MessageBox.Show(@"Invalid file type, SharpDomain can only load exe/dll files");
}
catch (Exception e) {
MessageBox.Show(e.ToString());
}
}
/// <summary>
/// Loads the domain host settings. Call this with the desired params before calling <see cref="HostDomain()" />.
/// </summary>
/// <param name="loadDirectory">The directory the domain is contained in,</param>
/// <param name="applicationName">Name of the application.</param>
/// <param name="applicationArguments"></param>
[DllExport("LoadDomainHostSettings", CallingConvention.Cdecl)]
public static void LoadDomainHostSettings(string loadDirectory, string applicationName,
string applicationArguments) {
ApplicationToHostDirectory = loadDirectory;
ApplicationToHostName = applicationName;
ApplicationArguments = applicationArguments;
}
internal static void ShowError(Exception ex) => MessageBox.Show(ex.ToString(), @"Version Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}