Here is what I'm trying to do:
Create an executable that loads the Visual Studio DTE
Access methods of an Addin that is loaded
Here is my code, as followed loosely from this blog.
[STAThread]
static void Main(string[] args)
{
EnvDTE80.DTE2 dte;
object obj = null;
System.Type t = null;
MessageFilter.Register();
// Get the ProgID for DTE 10.0.
t = System.Type.GetTypeFromProgID("VisualStudio.DTE.10.0", true);
obj = System.Activator.CreateInstance(t, true);
var addin = GetAddInByProgID(dte, "MyAddin");
if (addin != null)
{
addin.Connected = true;
var connectObj = addin.Object;
var conObjType = connectObj.GetType();
var methods = conObjType.GetMethods(); // mscorlib methods
var asm = conObjType.Assembly; // is mscorlib
}
...
}
The problem I'm running into is I can't get access to the Addin's assembly. It appears that conObjType's assembly is mscorlib - but I want access to Myaddin.dll. Any ideas?
Any 3rd party add-in may not expose any method at all other than those required to implement the add-in interface (OnConnection, etc.). Its methods can be internal (not public) or even can be obfuscated.
If it is your add-in, a better approach would be that the add-in provides commands to perform actions, and given your external DTE instance, you can call DTE.ExecuteCommand("MyAddIn.MyCommand").
Related
I created one console application to create visual studio project pragmatically, here i am not able install Nuget packages, always
var componentModel = (IComponentModel)Package.GetGlobalService(typeof(SComponentModel)); statement returns null values. for your reference i added my code below. Help me to resolve this issue.
static void Main(string[] args)
{
//InstallNuGetPackages.InstallNuGet("");
string ProjectName = "WebAPIProj";
string SolutionName = "EmptyTemplate";
System.Type type = System.Type.GetTypeFromProgID("VisualStudio.DTE.11.0");
Object obj = System.Activator.CreateInstance(type, true);
EnvDTE.DTE dte = (EnvDTE.DTE)obj;
dte.MainWindow.Visible = true; // optional if you want to See VS doing its thing
// create a new solution
dte.Solution.Create("C:\\"+ SolutionName + "\\", SolutionName);
var solution = dte.Solution;
// create a C# WinForms app
solution.AddFromTemplate(#"C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\ProjectTemplatesCache\CSharp\Web\1033\EmptyWebApplicationProject40\EmptyWebApplicationProject40.vstemplate",
#"C:\NewSolution\"+ ProjectName, ProjectName);
InstallNuGetPackages.InstallNuGet(dte);
foreach (var p in dte.Solution.Projects)
{
InstallNuGetPackages.InstallNuGet((Project)p, "Microsoft.AspNet.WebApi version1.1");
}
// save and quit
dte.ExecuteCommand("File.SaveAll");
dte.Quit();
}
Code to install Nuget Packages
public bool InstallNuGetPackage(Project project, string package)
{
bool installedPkg = true;
try
{
var componentModel = (IComponentModel)Package.GetGlobalService(typeof(SComponentModel)); //Always this statement returns null
IVsPackageInstallerServices installerServices = componentModel.GetService();
if (!installerServices.IsPackageInstalled(project, package))
{
var installer = componentModel.GetService();
installer.InstallPackage(null, project, package, (System.Version)null, false);
}
}
catch (Exception ex)
{
installedPkg = false;
}
return installedPkg;
}
(Turned this into an answer for better readability and more room)
created one console application - you only have access to the ServiceProvider from Visual Studio if you run your code inside of it, i.e. from an extension and/or package.
Running this from a console application cannot work. Visual Studio internally does a lot more setup for all the services and general environment than creating an instance of DTE.
To persue your route, although I'm not sure how feasible that is, invoke nuget.exe or NuGet.Core code to achieve similar.
I have a utilities library (dll) which contains the class (shown below) for working with embedded resources. In Visual Studio 2013 it works as expected and loads the resources from the assembly which calls the function in the utilities library.
In Visual Studio 2015 when I call the functions the code attempts to load the resources from the utilities library instead of the calling library. They are seperate assemblies.
Please can you help me understand why this is happening, and how I can get it to work in Visual Studio 2015?
Thanks in advance.
public static class EmbeddedResources
{
public static string[] GetAllResourceNames()
{
Assembly _assembly = Assembly.GetExecutingAssembly();
return _assembly.GetManifestResourceNames();
}
public static string ReadQueryResource(string resourceName)
{
Assembly _assembly;
StreamReader _textStreamReader;
try
{
_assembly = Assembly.GetExecutingAssembly();
_textStreamReader = new StreamReader(_assembly.GetManifestResourceStream(resourceName));
if (_textStreamReader.Peek() != -1)
return _textStreamReader.ReadToEnd();
}
catch
{
//MessageBox.Show("Error accessing resources!");
}
return null;
}
}
Assembly.GetExecutingAssembly()
Gets the assembly that contains the code that is currently executing.
Assembly.GetCallingAssembly()
Returns the Assembly of the method that invoked the currently executing method.
I'm writing a visual studio extension and I'm completely confused about how and where and when it's loading assemblies. What I have is this:
My Visual Studio extension project (let's call it MyExtension) references several assemblies including an assembly called Foo.dll.
MyExtension contains a class called FooManager that will be instantiated in response to a menu item being clicked.
When FooManager is instantiated it is passed the output path of a project in the current solution and it creates an AppDomain which should load that assembly, something like this:
public FooManager(string assemblyPath)
{
// The actual ApplicationBase of the current domain will be the one of VS and
// not of my plugin
// We need our new AppDomain to be able to find our assemblies
// without this even the CreateInstanceAndUnwrap will fail
var p = System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
var cachePath = Path.Combine(p, "Cache");
var pluginPath = Path.Combine(p, "Test");
if (Directory.Exists(cachePath))
{
Directory.Delete(cachePath, true);
}
if (Directory.Exists(pluginPath))
{
Directory.Delete(pluginPath, true);
}
Directory.CreateDirectory(cachePath);
Directory.CreateDirectory(pluginPath);
var newPath = Path.Combine(pluginPath, Path.GetFileName(assemblyPath));
File.Copy(assemblyPath, newPath, true);
var setup = new AppDomainSetup()
{
ApplicationBase = p,
ShadowCopyFiles = "true",
ShadowCopyDirectories = pluginPath,
CachePath = cachePath
};
domain = AppDomain.CreateDomain("MyTest_AppDomain", AppDomain.CurrentDomain.Evidence, setup);
// FooCollection is defined in MyExtension. but has several references
// to things defined in Foo.dll - it used MEF to load the assembly
// referenced by pluginPath
collection = domain.CreateInstanceAndUnwrap(
typeof(FooCollection).Assembly.FullName,
typeof(FooCollection).FullName,
false,
BindingFlags.Default,
null,
new object[] { pluginPath }, null, null) as FooCollection;
}
Now one property of FooManager looks like this (FooInfo is defined in
Foo.dll):
public IEnumerable<FooInfo> Spiders
{
get
{
return collection.Foos.Select(s => s.Metadata);
}
}
But when I try to access that I get a System.ArgumentException with the message Object type cannot be converted to target type. which I know happens if two copies of the same assembly are loaded from different locations, and I think, ultimately, that's what's happening here, but I can't figure out how to get it to load from the same place.
So after struggling with this a lot (and the above is only my latest attempt), I thought maybe I could serialize to byte[] and then deserialize again as a way to avoid the problem with types, so I tried something like this:
var msgBytes = collection.SerializeFooInfo();
var msg = FooInfo.DeserializeMessage(msgBytes);
Where my serialize and deserialize just use a BinaryFormatter (the classes are marked as Serializable). The serialization seems to work, but on deserialization, when I get to here:
public static List<FooInfo> DeserializeMessage(byte[] source)
{
using (var stream = new MemoryStream(source))
{
BinaryFormatter formatter = new BinaryFormatter();
var msg = formatter.Deserialize(stream);
return msg as List<FooInfo>;
}
}
msg comes back as null. If I try to run it in the debugger using the immediate window, I see that Deserialize threw a FileNotFoundException with the message:
Cannot load assembly 'C:\Users\matt.burland\AppData\Local\Microsoft\VisualStudio\14.0Exp\ProjectAssemblies\qesxy6ms01\Foo.dll'
But I don't understand where that path came from. It's not where my extension has been installed, which is C:\Users\matt.burland\AppData\Local\Microsoft\VisualStudio\14.0Exp\Extensions\MyCompany\FooTools\1.0 and was set as the ApplicationBase for my AppDomain and contains the file foo.dll. So why is it trying to load from the other mystery location? The other location seems to be created dynamically and contains only the foo.dll assembly.
I've do something very similar to this in a windows service (and using a lot of the same classes) and it works just fine, so this seems to be something particular to the way Visual Studio extensions. Can anybody shine a light here?
So I thought this might help: http://geekswithblogs.net/onlyutkarsh/archive/2013/06/02/loading-custom-assemblies-in-visual-studio-extensions-again.aspx
But if I try to attach an AssemblyResolve handler in package class as suggested it doesn't get called for anything interesting (which isn't surprising, it's not the domain I'm try to load from), but if I try to attach to the new domain I create then if I try something like this:
domain.AssemblyResolve += OnAssemblyResolve;
Then it fails because my FooManager isn't marked as serializable. So I created a proxy just for binding the AssemblyResolve, but the AssemblyResolve never fires. So I tried not setting the ApplicationBase when creating my domain, thinking that would force it to have to try to resolve, but then I can't create my proxy class in the created domain because it doesn't know where to load the assembly from!.
I have a huge application where one project of my solution makes reports.
I want to add new report (update report) without building my project, just add .dll files. I read about Assembly and
AppDomain, but I don't know is it really good way to add new dll for new report and how to update old report in runtime?
Here's my example, it takes my first dll, but second time it doesn't. First dll - sum, second - deducted.
static void Main(string[] args)
{
try
{
//first domain
AppDomain domain = AppDomain.CreateDomain("MyDomain");
AssemblyDll asb1 = new AssemblyDll();
Console.WriteLine(asb1.AssemblyMethod(1));
AppDomain.Unload(domain);
Console.ReadKey();
//second domain
AppDomain newDomain = AppDomain.CreateDomain("myNewDomain");
AssemblyDll asb2 = new AssemblyDll();
Console.WriteLine(asb2.AssemblyMethod(2));
AppDomain.Unload(newDomain);
Console.ReadKey();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
public class AssemblyDll
{
public string AssemblyMethod(int version)
{
//loading .dll
Assembly assembly = Assembly.LoadFrom(#"../../../../Assembly/DynamicDLL" + version + ".dll");
Type type = assembly.GetType("DynamicDLL.Dynamic");
object instance = Activator.CreateInstance(type);
MethodInfo[] methods = type.GetMethods();
//invoke method
object result = methods[0].Invoke(instance, new object[] { 5, 3 });
return result.ToString();
}
}
My .dll file comes from:
namespace DynamicDLL
{
public class Dynamic
{
public int DynamicMethod(int a, int b)
{
return a + b;
//return a - b;
}
}
}
If you want to write something like plugins and like the plugin approach, you should take a look at MEF http://msdn.microsoft.com/en/library/vstudio/dd460648.aspx
MEF allows you to use any assembly dynamically and even drop dlls into a folder and build a MEF catalog out of it.
Actually Visual Studio and uses MEF internally for extensiblility (Plugins...)
Assemblies are generally loaded into an AppDomain once and you cannot unload them once loaded.
You can create a new AppDomain and load your assemblies into this and when you release this the assemblies will be unloaded. However the caveat here is you cannot directly communicate between two AppDomain you have to marshal between the two using some other method like remoting.
There's been much wrote on this in terms of plugins and making plugins unloadable, a quick Google search presented these:
http://www.brad-smith.info/blog/archives/500
http://adrianvintu.com/blogengine/post/Unloadable-plugins.aspx
Hopefully these will aid you.
<## template debug="true" hostspecific="true" language="C#" #>
<## assembly name="EnvDTE80" #>
<## include file="T4Toolbox.tt" #>
<#
IServiceProvider serviceProvider = (IServiceProvider)this.Host;
EnvDTE80.DTE2 dte = (EnvDTE80.DTE2)serviceProvider.GetService(typeof(EnvDTE.DTE));
//add a file to a project and add its dependupon build property.
//I want to refresh teh solution explorer window to show the hierarchy between 2 files
//You will see this kind of relationship between Forms.cs and Form1.Designer.cs files.
EnvDTE.UIHierarchy solExplorer = dte.ToolWindows.SolutionExplorer;
solExplorer.Parent.Activate();
dte.ExecuteCommand("View.Refresh", string.Empty);
I am trying to refresh the solution explorer's tool window so I can see the newly created files nested. I know that T4 templates are executed in one application domain and calls are made into Visual Studio Appdomain using remoting. I am getting this error about serialization.
So is there a way I can refresh solution explorer tool window (solExplorer.Parent) by first activating it (I was told).
Type 'Microsoft.VisualStudio.Platform.WindowManagement.DTE.WindowBase' in Assembly 'Microsoft.VisualStudio.Platform.WindowManagement, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' is not marked as serializable.
Update: Based on Gereth's comment.
Thanks, Gereth I tried this, but it returns COMException,
I don't have an error about Serialization of Microsoft.VisualStudio.Platform.WindowManagement.DTE.WindowBase class and Activate method seems to have succeded. The error is now on dte.ExecuteCommand method.
//object dteObject = GetCOMService(serviceProvider, typeof(EnvDTE80.DTE2));
object dteObject1 = GetCOMService(serviceProvider, typeof(EnvDTE.DTE));
EnvDTE80.DTE2 dte = (EnvDTE80.DTE2)dteObject1;
COMException raised when executing this line:
dte.ExecuteCommand("View.Refresh", string.Empty);
Message "Error HRESULT E_FAIL has been returned from a call to a COM component."
Source "EnvDTE80"
StackTrace "at EnvDTE80.DTE2.ExecuteCommand(String CommandName, String CommandArgs)
ErrorCode -2147467259
What to try next?
Thanks
Rad
There's a section of DTE commands that don't stay with COM marshaling once the CLR notices that both ends of the remoting pipe are written in managed code. However, given these components are not actually set up to be .Net remotable, but ARE set up to be COM remotable, this type of error occurs.
In general, if the COM marshaling on the particular DTE object in question is set up correctly, you should be able to use the following code to get moving again.
Call it instead of your vanilla service call to get the DTE.
public static Object GetCOMService(IServiceProvider provider, Type type)
{
Object ret;
ret = provider.GetService(type);
if (ret == null)
{
return ret;
}
try
{
return Marshal.GetObjectForIUnknown(Marshal.GetIUnknownForObject(ret));
}
catch (Exception)
{
return ret;
}
}
I finally got this to work by unloading and reloading the project. The project has to be selected in the Solution Explorer otherwise you'll get a COMException.
IServiceProvider hostServiceProvider = (IServiceProvider)Host;
// see #GarethJ's answer for the following
DTE2 dte2 = GetCOMService(hostServiceProvider, typeof(EnvDTE.DTE)) as DTE2;
object dteObject1 = GetCOMService(hostServiceProvider, typeof(EnvDTE.DTE));
EnvDTE80.DTE2 dte2 = (EnvDTE80.DTE2)dteObject1;
var solExplorer = dte2.ToolWindows.SolutionExplorer;
solExplorer.Parent.Activate();
ProjectItem containingProjectItem = dte2.Solution.FindProjectItem(templateFile);
Project project = containingProjectItem.ContainingProject;
UIHierarchy solExplorerHierarchy = solExplorer.Parent.Object as UIHierarchy;
string projectSolutionPath = Path.Combine(dte2.Solution.Properties.Item("Name").Value.ToString(), project.Name);
var projectItem = solExplorerHierarchy.GetItem(projectSolutionPath);
projectItem.Select(vsUISelectionType.vsUISelectionTypeSelect);
dte2.ExecuteCommand("Project.UnloadProject", "");
dte2.ExecuteCommand("Project.ReloadProject", "");
And then any newly created nested items appear. I'm using VS2012 and T4Toolbox 11.7.
Thank you Gareth. Your solution works very well.
I have extended my "GetService" method:
private T GetService<T>(Type type) where T : class
{
IServiceProvider hostServiceProvider = (IServiceProvider)Host;
if (hostServiceProvider == null)
{
throw new Exception("Host property returned unexpected value (null)");
}
object serviceObj = hostServiceProvider.GetService(type);
try
{
serviceObj = Marshal.GetObjectForIUnknown(Marshal.GetIUnknownForObject(serviceObj));
}
catch (Exception) { }
T service = serviceObj as T;
if (service == null)
{
throw new Exception("Unable to retrieve service");
}
return service;
}