Question

I'm working on a plugin for a existing C# .NET Program. It's structured in a manner where you put your custom .dll file in Program Root/Plugins/your plugin name/your plugin name.dll

This is all working well, but now I'm trying to use NAudio in my project.

I've downloaded NAudio via Nuget, and that part works fine, but the problem is that it looks for the NAudio.dll in Program Root, and not in the folder of my plugin.

This makes it hard to distribute my plugin, because it would rely on users dropping the NAudio.dll in their Program Root in addition to putting the plugin into the "Plugins" folder.

Source: SettingsView.xaml:

<Button HorizontalAlignment="Center"
    Margin="0 5"
    Width="120"
    Command="{Binding SoundTestCommand,
    Source={StaticResource SettingsViewModel}}"
    Content="Sound Test" />

SettingsViewModel.cs:

using NAudio.Wave;
.
.
.
    public void SoundTest()
    {
        IWavePlayer waveOutDevice;
        WaveStream mainOutputStream;
        WaveChannel32 inputStream;

        waveOutDevice = new WaveOut();

        mainOutputStream = new Mp3FileReader(@"E:\1.mp3");
        inputStream = new WaveChannel32(mainOutputStream);

        inputStream.Volume = 0.2F;

        waveOutDevice.Init(mainOutputStream);
        waveOutDevice.Play();
    }

How can I get C# to look for NAudio in Program Root/Plugins/my plugin name/NAudio.dll instead of looking for it in Program Root/NAudio.dll ?

I'm using VS Express 2013, Target Framework is 4.5 and Output type is Class Library.

Edit:

I found 2 ways to make this work ( I'm not sure what the pros and cons of both methods are - if anyone knows I would appreciate additional information ).

  1. Using the NuGet Package Costura.Fody.

    After installing the NuGet package, I simply had to set all other References "Copy Local" to "False" and then set "Copy Local" for NAudio to "True".

    Now when I build, the NAudio.dll is compressed and added to my own DLL.

  2. Using the AssemblyResolver outlined below.

    It didn't work right away though, so here is some additional information that may help anyone facing the same issue:

    I put Corey's code as he posted it into the Helpers folder.

    My entry point is Plugin.cs, the class is public class Plugin : IPlugin, INotifyPropertyChanged

    In there, the entry method is public void Initialize(IPluginHost pluginHost), but simply putting PluginResolver.Init(path) did not work.

    The host program uses WPF and is threaded and I had to use a dispatcher helper function of the host program to get it to work: DispatcherHelper.Invoke(() => Resolver.Init(path));

As mentioned, I'm currently unsure which method to use, but I'm glad I got it to work. Thanks Corey!

Was it helpful?

Solution

You can use the PATH environment variable to add additional folders to the search path. This works for native DLLs, but I haven't tried to use it for .NET assemblies.

Another option is to add a hook to the AssemblyResolve event on the current application domain and use a custom resolver to load the appropriate assembly from wherever you find it. This can be done at the assembly level - I use it in NAudio.Lame to load an assembly from a resource.

Something along these lines:

public static class PluginResolver
{
    private static bool hooked = false;
    public static string PluginBasePath { get; private set; }

    public static void Init(string BasePath)
    {
        PluginBasePath = BasePath;
        if (!hooked)
        {
            AppDomain.CurrentDomain.AssemblyResolve += ResolvePluginAssembly;
            hooked = true;
        }
    }

    static Assembly ResolvePluginAssembly(object sender, ResolveEventArgs args)
    {
        var asmName = new AssemblyName(args.Name).Name + ".dll";
        var assemblyFiles = Directory.EnumerateFiles(PluginBasePath, "*.dll", SearchOption.AllDirectories);

        var asmFile = assemblyFiles.FirstOrDefault(fn => string.Compare(Path.GetFileName(fn), asmName, true) == 0);

        if (string.IsNullOrEmpty(asmFile))
            return null;

        return Assembly.LoadFile(asmFile);
    }
}

(Usings for the above: System.IO, System.Reflection, System.Linq)

Call Init with the base path to your plugins folder. When you try to reference an assembly that isn't loaded yet it will search for the first file that matches the base name of the assembly with dll appended. For instance, the NAudio assembly will match the first file named NAudio.dll. It will then load and return the assembly.

No checking is done in the above code on the version, etc. and no preference is given to the current plugin's folder.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top