Introduction
This article shows you how to get a list of the currently running Visual Studio IDE instances. For each instance, you can get a reference to the DTE object - the top-level object in the Visual Studio automation object model. Once you have a reference to this object, you can query for information about a solution, projects in a solution, start builds, etc.
MsdevManager is a very simple test application which exercises the provided functionality: it displays the running instances, and allows you to double-click to bring a selected IDE to the foreground. It does this by grabbing the HWnd
for the IDE's MainWindow from the DTE object, and using that to raise the IDE to the foreground. While there are obviously other ways to accomplish this particular bit of functionality, the point is to demonstrate it using the DTE object. For more detailed automation examples, you can look at the MSDN documentation, but this article should help you get started with a reference to the object model.
Running Object Table
Many applications, such as Visual Studio and Microsoft Office, register a COM automation server in the Running Object Table (ROT) on startup. Automation clients such as MsdevManager can then get a pointer to this object to manipulate the server. Visual Studio .NET 2003 registers itself in the ROT as "!VisualStudio.DTE.7.1:pid", where "pid" would be replaced by the process ID of the corresponding devenv.exe process. Visual Studio .NET 2002 registers itself as "!VisualStudio.DTE.7:pid".
The DTE Object
DTE stands for "Development Tools Extensibility" and is the object's coclass. It implements the interface "_DTE
". You can find more information about the DTE object in the Visual Studio documentation under "DTE object", and in the document "Referencing the DTE object". The O'Reilly book "Mastering Visual Studio .NET" also has a chapter with some useful information.
Using the code
The file MsdevManager.cs contains a class Msdev
which defines a few of the relevant methods. The first thing we need is to be able to get at the system's Running Object Table. To do this, we'll need access to a couple of functions that live in ole32.dll:
public class Msdev
{
[DllImport("ole32.dll")]
public static extern int GetRunningObjectTable(int reserved,
out UCOMIRunningObjectTable prot);
[DllImport("ole32.dll")]
public static extern int CreateBindCtx(int reserved,
out UCOMIBindCtx ppbc);
Now that we can get at these two functions, let's write a method to enumerate the objects in the ROT, giving us a snapshot of the contents:
public static Hashtable GetRunningObjectTable()
{
Hashtable result = new Hashtable();
int numFetched;
UCOMIRunningObjectTable runningObjectTable;
UCOMIEnumMoniker monikerEnumerator;
UCOMIMoniker[] monikers = new UCOMIMoniker[1];
GetRunningObjectTable(0, out runningObjectTable);
runningObjectTable.EnumRunning(out monikerEnumerator);
monikerEnumerator.Reset();
while (monikerEnumerator.Next(1, monikers, out numFetched) == 0)
{
UCOMIBindCtx ctx;
CreateBindCtx(0, out ctx);
string runningObjectName;
monikers[0].GetDisplayName(ctx, null, out runningObjectName);
object runningObjectVal;
runningObjectTable.GetObject( monikers[0], out runningObjectVal);
result[ runningObjectName ] = runningObjectVal;
}
return result;
}
From here, it's easy to write another method that just gets us a list of the instances of the Visual Studio IDE. We'll also include a parameter that allows us to specify that we only want IDE instances that have open solutions:
public static Hashtable GetIDEInstances( bool openSolutionsOnly )
{
Hashtable runningIDEInstances = new Hashtable();
Hashtable runningObjects = GetRunningObjectTable();
IDictionaryEnumerator rotEnumerator = runningObjects.GetEnumerator();
while ( rotEnumerator.MoveNext() )
{
string candidateName = (string) rotEnumerator.Key;
if (!candidateName.StartsWith("!VisualStudio.DTE"))
continue;
_DTE ide = rotEnumerator.Value as _DTE;
if (ide == null)
continue;
if (openSolutionsOnly)
{
try
{
string solutionFile = ide.Solution.FullName;
if (solutionFile != String.Empty)
{
runningIDEInstances[ candidateName ] = ide;
}
}
catch {}
}
else
{
runningIDEInstances[ candidateName ] = ide;
}
}
return runningIDEInstances;
}
Finally, once we've got a reference to the DTE for the IDE we would like to automate, we can do something useful:
[DllImport("user32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);
private const int SW_RESTORE = 9;
[DllImport("user32.dll")]
private static extern bool ShowWindowAsync(IntPtr hWnd,int nCmdShow);
[DllImport("user32.dll")]
private static extern bool IsIconic(IntPtr hWnd);
public static void ShowIDE( EnvDTE.DTE ide )
{
System.IntPtr hWnd = (System.IntPtr) ide.MainWindow.HWnd;
if (IsIconic(hWnd))
{
ShowWindowAsync(hWnd,SW_RESTORE);
}
SetForegroundWindow(hWnd);
ide.MainWindow.Visible = true;
}
History
- 8/14/04 - Initial version.