2

I have a problem using external libraries for my visual studio extension. Currently I have my own NuGet server on which I host my libraries, now since I dont want functionality twice I extracted some functions out of my extension into an existing library.

The problem is however that whenever I want to use anything from that assembly I can not do so, since visual studio does not include the .dlls in the .vsix package.

So far I have tried:

  1. using Assets to include the libraries from their package location. This creates two items in the solution which also have their "Include in VSIX" property set to true, this did not work
  2. including the projects and then adding the BuiltProjectOutputGroup;BuiltProjectOutputGroupDependencies;GetCopyToOutputDirectoryItems;SatelliteDllsProjectOutputGroup; to the "Other Groups included in VSIX" property of the reference, which did not work
  3. using Assets to include the libraries as projects, which did not work

So now I am at my end since there is nothing that seems to work....

The solutions I found are already suggesting all the steps I tried, but at this point

I also found these two post which are the same as my question but those answerts did not work for me like I said.

this

this

Community
  • 1
  • 1
DokutoMekki
  • 491
  • 4
  • 17
  • Hmm, my crystal ball says that the DLLs are included just fine, VS just can't find them. [Read this](http://stackoverflow.com/questions/13674782/vsix-cannot-load-file-or-assembly-of-a-referenced-dll). – Hans Passant Sep 27 '15 at 18:16
  • Yep, I know they are included i get a FileNotFoundException, the only question is, how do I resolve the issue since none of the posts worked for me. The third option did not work for me either btw, everytime I initialize the ManualAssemblyResolver in the Initialize of the package, the package can not even be created. – DokutoMekki Sep 27 '15 at 18:39

1 Answers1

1

Okay I found a way to do it, its a combonation of two answers I found here on stack overflow.

And eventhough its a bit hacky I suppose its the only way possible.

So I simply used the existing ManualAssemblyResolver and adjusted it to my needs, the Result being this:

public class ManualAssemblyResolver : IDisposable
{
    #region Attributes

    /// <summary>
    /// list of the known assemblies by this resolver
    /// </summary>
    private readonly List<Assembly> _assemblies;

    #endregion

    #region Properties

    /// <summary>
    /// function to be called when an unknown assembly is requested that is not yet kown
    /// </summary>
    public Func<ResolveEventArgs, Assembly> OnUnknowAssemblyRequested { get; set; }

    #endregion

    #region Constructor

    public ManualAssemblyResolver(params Assembly[] assemblies)
    {
        _assemblies = new List<Assembly>();

        if (assemblies != null)
            _assemblies.AddRange(assemblies);

        AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve;
    }

    #endregion

    #region Implement IDisposeable

    public void Dispose()
    {
        AppDomain.CurrentDomain.AssemblyResolve -= OnAssemblyResolve;
    }

    #endregion

    #region Private

    /// <summary>
    /// will be called when an unknown assembly should be resolved
    /// </summary>
    /// <param name="sender">sender of the event</param>
    /// <param name="args">event that has been sent</param>
    /// <returns>the assembly that is needed or null</returns>
    private Assembly OnAssemblyResolve(object sender, ResolveEventArgs args)
    {
        foreach (Assembly assembly in _assemblies)
            if (args.Name == assembly.FullName)
                return assembly;

        if (OnUnknowAssemblyRequested != null)
        {
            Assembly assembly = OnUnknowAssemblyRequested(args);

            if (assembly != null)
                _assemblies.Add(assembly);

            return assembly;
        }

        return null;
    }

    #endregion
}

After that I used an Addition ExtensionManager to get the installation path of the extension. Which looks like this

public class ExtensionManager : Singleton<ExtensionManager>
{
    #region Constructor

    /// <summary>
    /// private constructor to satisfy the singleton base class
    /// </summary>
    private ExtensionManager()
    {
        ExtensionHomePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), Definitions.Constants.FolderName);

        if (!Directory.Exists(ExtensionHomePath))
            Directory.CreateDirectory(ExtensionHomePath);

        SettingsFileFullname = Path.Combine(ExtensionHomePath, Definitions.Constants.SettingsFileName);

        InstallationPath = Path.GetDirectoryName(GetType().Assembly.Location);
    }

    #endregion

    #region Properties

    /// <summary>
    /// returns the installationPath
    /// </summary>
    public string InstallationPath { get; private set; }

    /// <summary>
    /// the path to the directory where the settings file is located as well as the log file
    /// </summary>
    public string ExtensionHomePath { get; private set; }

    /// <summary>
    /// the fullpath to the settingsfile
    /// </summary>
    public string SettingsFileFullname { get; private set; }

    #endregion
}

Then in the Initialize() method of the Package you will need to create an instance of the ManualAssemblyResolver and provide the Path to the assemblies you need like this:

    #region Attributes

    private ManualAssemblyResolver _resolver;

    #endregion

    #region Override Microsoft.VisualStudio.Shell.Package

    /// <summary>
    /// Initialization of the package; this method is called right after the package is sited, so this is the place
    /// where you can put all the initialization code that rely on services provided by VisualStudio.
    /// </summary>
    protected override void Initialize()
    {
        _resolver = new ManualAssemblyResolver(
        Assembly.LoadFrom(Path.Combine(ExtensionManager.Instance.InstallationPath, Definitions.Constants.NameOfAssemblyA)),
        Assembly.LoadFrom(Path.Combine(ExtensionManager.Instance.InstallationPath, Definitions.Constants.NameOfAssemblyB))
        );

Note that you will need to call this before anythingelse that even touches anything from the referenced assemblies, otherwise a FileNotFoundException will be thrown.

In any case this seems to work for me now but I wish there was a cleaner way to do it. So if anybody has a better way (a way that actually includes and lloks up the assemblies from the .vsix package) then please post an answer.

EDIT: Okay now I found the real issue, it was simply the fact that the dlls were satellite dll (the had their assembly culture set) so they were not visible....

However the above fix worked when they were still satillite dlls.

DokutoMekki
  • 491
  • 4
  • 17
  • What does "the dlls were satellite dll (the had their assembly culture set) so they were not visible...." mean? – kchoi Mar 03 '16 at 18:21
  • It means that it is a dll which will only be loaded if your locale (essentially the system language of your operation system) correspond to it. So in my case I had a german OS but the sateliite dll was based on english locale. Hence I could not load it on my system. In essence staellite dlls are localized resources see http://stackoverflow.com/questions/1123758/what-is-the-difference-between-a-resource-file-and-a-satellite-dll – DokutoMekki Mar 06 '16 at 08:17