I managed to load DLLs put in a different folder with this snippet:
public sealed class SetupDLLDependencies
{
static string DLLDirectory = "";
static public void Run(string dependencyDirName)
{
DLLDirectory = Path.Combine(AppContext.BaseDirectory, dependencyDirName);
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += ResolveAssembly;
AppDomain.CurrentDomain.AssemblyResolve += ResolveAssembly;
}
static private Assembly ResolveAssembly(Object sender, ResolveEventArgs e)
{
string name = Path.Combine(DLLDirectory, $"{e.Name.Split(',')[0]}.dll");
Assembly res = null;
try
{
res = System.Reflection.Assembly.LoadFile(name);
}
catch (Exception)
{
Console.WriteLine($"Failed to load {name}");
}
return res;
}
}
It works when I use the plugin DLL in an accompanying console app that I made, but I still can't get it to load in MB.
MB is able to detect it, but when I click Enable, I get the unhelpful message:
'Unable to load one or more of the requested types. Retrieve the LoaderExceptions property for more information.'
Is it possible to include the retrieved info from LoaderExceptions in this popup?
I'm of course able to load the plugin if I don't call any of the code requiring any dependencies, but that's basically all of my code (outside of MusicBeePlugin.Plugin setup)
EDIT:
I checked ErrorLog.dat, and only found this (it's in Danish but shouldn't be an issue)
15-12-2023 15:33:20 - 10.0.22631.0 - 3.5.8698.34385D - System.Reflection.ReflectionTypeLoadException: En eller flere af de anmodede typer kan ikke indlæses. Hent egenskaben LoaderExceptions for at få flere oplysninger.
ved System.Reflection.RuntimeModule.GetTypes(RuntimeModule module)
ved System.Reflection.RuntimeModule.GetTypes()
ved System.Reflection.Assembly.GetTypes()
ved #=zu4Zx8AwXW_Fbz0RgnA==..ctor(#=zOPAFmqxyWq4nEsXBtA== #=zogaPBWQ=)
EDIT2:
I think I get what's going on now.
MB seems to call
System.Reflection.Assembly.GetTypes()
before the snippet above gets to run. I tested this in the console app by trying to call Assembly.GetTypes() before and after the snippet ran. Obviously, it worked only if the snippet ran first.
Next, I tried to look for ways to get it to execute before MB uses Assembly.GetTypes(). So I changed the snippet to the following, in an attempt to get the code to execute upon load:
public sealed class LibraryEntryPoint
{
static string DLLDirectory = "";
// This static constructor will be called when the DLL is loaded
static LibraryEntryPoint()
{
Assembly thisAssem = typeof(LibraryEntryPoint).Assembly;
// Setup DLL dependencies
SetupDllDependencies(thisAssem.GetCustomAttribute<AssemblyTitleAttribute>().Title);
}
static public void SetupDllDependencies(string dependencyDirName)
{
DLLDirectory = Path.Combine(AppContext.BaseDirectory, dependencyDirName);
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += ResolveAssembly;
AppDomain.CurrentDomain.AssemblyResolve += ResolveAssembly;
}
static private Assembly ResolveAssembly(Object sender, ResolveEventArgs e)
{
string name = Path.Combine(DLLDirectory, $"{e.Name.Split(',')[0]}.dll");
Assembly res = null;
try
{
res = System.Reflection.Assembly.LoadFile(name);
}
catch (Exception)
{
Console.WriteLine($"Failed to load {name}");
}
return res;
}
}
It didn't work.
With no way to know how MB loads plugins, I think I might be on an impasse. There seems to be no way to be no way to ensure dependencies are loaded before MB uses Assembly.GetTypes(). And I guess I understand that you need to GetTypes() to check if the required types are there?
EDIT 3: It worksDisregard what I mentioned in the previous edit. I got it to work. The error I got was due to a wrong dll folder being set.
This is the correct code:
public sealed class LibraryEntryPoint
{
static string DLLDirectory = "";
// This static constructor will be called when the DLL is loaded
static LibraryEntryPoint()
{
Assembly thisAssem = typeof(LibraryEntryPoint).Assembly;
// Setup DLL dependencies
string libFolder = Path.GetDirectoryName(thisAssem.Location);
string libDepFolder = Path.Combine(libFolder, thisAssem.GetCustomAttribute<AssemblyTitleAttribute>().Title);
SetupDllDependencies(libDepFolder);
}
static public void SetupDllDependencies(string dependencyDirPath)
{
DLLDirectory = dependencyDirPath;
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += ResolveAssembly;
AppDomain.CurrentDomain.AssemblyResolve += ResolveAssembly;
}
static private Assembly ResolveAssembly(Object sender, ResolveEventArgs e)
{
string name = Path.Combine(DLLDirectory, $"{e.Name.Split(',')[0]}.dll");
Assembly res = null;
try
{
res = System.Reflection.Assembly.LoadFile(name);
}
catch (Exception)
{
Console.WriteLine($"Failed to load {name}");
}
return res;
}
}
Also remember to add this class to the Plugin class so the code gets triggered:
namespace MusicBeePlugin
{
public partial class Plugin
{
// Required so entrypoint can be called
static private LibraryEntryPoint entryPoint = new LibraryEntryPoint();
//...
}
}
Or you can just move everything in LibraryEntryPoint to Plugin.
Well, everything works, so my issue has been resolved!