|
Microsoft's Visual C++, like many Microsoft applications, exposes part of its functionality through a COM automation interface. Although Microsoft's intent was undoubtedly to put the power of Visual Studio in users' hands, those who have ever tried to do anything complex with the automation interface just become frustrated. Unlike the Microsoft Office products, which expose a rich automation interface, Visual C++'s looks like it was hacked in at the last moment.
Some time ago, Microsoft announced the Visual Studio Integration Program. Through the VSIP, developers would get full access to the headers and libraries to communicate with the various Visual Studio applications. The VSIP supposedly costs tens of thousands of dollars a year with a 5-year minimum commitment. For anyone who isn't a big company, this is a Bad Thing (TM).
Despite Microsoft's reluctance to provide average developers with the detailed Visual Studio specifications they need to create tightly integrated add-in applications, that has not stopped the ingenuity of several authors who have created some of the most phenomenal add-ins out there. Oz Solomonovich, for instance, writes an add-in called WndTabs that subclasses the main Visual C++ window to seamlessly insert a bar with window tabs into the user interface. Jerzy Kaczorowski's CvsIn integrates CVS with Visual C++, providing a powerful, free, alternative to source control systems such as Visual SourceSafe.
The purpose of this article is to describe the WWhizInterface SDK that complements Microsoft's Visual Studio automation interface. WWhizInterface was born of years of work developing the Visual C++ add-in Workspace Whiz! and its predecessor, the Workspace Utilities. WWhizInterface is a C++ interface providing access to certain Visual C++ capabilities the automation interface left out. The current iteration of WWhizInterface has been used in Jerzy Kaczorowski's CVS integration add-in, CvsIn, and Oz Solomonovich's Project Line Counter add-in. A previous form of WWhizInterface powers Mirec Miskufovic's Replace All Across Project Files add-in.
As mentioned previously, WWhizInterface and the Visual C++ automation interface work hand-in-hand. WWhizInterface exposes functionality the automation interface left out. WWhizInterface has an added benefit; most of its functionality can work without Visual C++ being active. WWhizInterface works seamlessly with Visual C++ 5.0, Visual C++ 6.0, and eMbedded Visual C++ 3.0.
WWhizInterface provides the following capabilities over the Visual C++ automation interface:
The latest WWhizInterface can be found online at http://workspacewhiz.com/WWhizInterface.html. The Workspace Whiz! source distribution, which contains the sample code described below, and the source documentation (viewable online and in an archive), is available from there, in addition to far more information about WWhizInterface.
Note: If any sample crashes in a Debug build, it is likely that WWhizInterface2D.mod could not be found (the error checking in the samples is only so-so). Either add the HKLM\Software\WWhizInterface\DebugPath value to the registry or copy WWhizInterface2D.mod to the Working Directory. If any sample crashes in a Release build, be sure to have run WWhizInterfaceInstallerWithCtags212.exe first.
To use WWhizInterface, add WWhizInterface2Loader.cpp, WWhizInterface2Loader.h, and WWhizInterface2.h to your project.
First, we need an instance of the WWhizInterface
object. Retrieve this instance
by calling the function WWhizInterface2Create()
, declared in WWhizInterface2Loader.h:
WWhizInterface* __cdecl WWhizInterface2Create(HINSTANCE hInstance, IApplication* pApplication);
hInstance is AfxGetInstanceHandle()
in an MFC application.
A console application may just pass in NULL
.
pApplication is the Visual Studio automation interface
IApplication
pointer. If the application is not a
Visual Studio add-in, then NULL
may be passed instead.
In a console application, initialization would be performed like:
WWhizInterface* g_wwhizInterface; g_wwhizInterface = WWhizInterface2Create(NULL, NULL);
It is possible for WWhizInterface2Create()
to fail. The function
first checks the working directory for the appropriate WWhizInterface2.mod
or WWhizInterface2D.mod. If it is not there, it relies on a path stored in
the registry at HKLM\Software\WWhizInterface\Path (or HKLM\Software\WWhizInterface\DebugPath
if using a Debug build). The WWhizInterface\Path key is created by the
WWhizInterfaceInstaller. The WWhizInterface\DebugPath key must be
created through REGEDIT.
A significant function of WWhizInterface is the retrieval of the active workspace's filename. This takes advantage of a property of Visual C++ described by Nick Hodapp in his article Undocumented Visual C++ which appeared on the Code Project. A helper .pkg file installed in the Common\MSDev98\Bin\IDE directory by the installer used to distribute WWhizInterface. For a user of WWhizInterface, the retrieval of the name is merely a function call.
CString workspaceName = g_wwhizInterface->GetWorkspaceName();
The name returned is the full path to the workspace's .dsw file, not the actual name of the workspace.
If the application is a Visual Studio add-in, WWhizInterface::GetCurrentFilename()
may be used to obtain the active file's filename. Visual Studio's automation functionality
can do the same thing but not without a lot of COM setup pain.
CString currentFilename; if (g_wwhizInterface->GetCurrentFilename(currentFilename)) { // Retrieve the WWhizFile object for the currentFilename: WWhizFile* curFile = g_wwhizInterface->GetFileList().Find(currentFilename); // Do something with curFile... }
Many filenames come in relative path form. Some filenames include an environment variable embedded in them
formatted as $(ENV)\Filename.ext. WWhizInterface::ResolveFilename()
will
resolve any given filename to its absolute path.
CString relativeFilename = "test.cpp"; CString environmentFilename = "$(HOMEDRIVE)\\test.cpp"; CString rootDirectory; // Empty = current directory g_wwhizInterface->ResolveFilename(rootDirectory, relativeFilename); // fullPath now contains the absolute path to test.cpp. g_wwhizInterface->ResolveFilename(rootDirectory, environmentFilename); // environmentFilename now contains the absolute path to $(HOMEDRIVE)\test.cpp.
Another of WWhizInterface's capabilities is the retrieval of every file in every project of a workspace. Unlike Visual Studio, WWhizInterface can work with multiple workspaces at a time. This powerful function is the basis behind Workspace Whiz!'s Extra Files feature. Extra Files makes information about extra workspaces and projects available for all supported Workspace Whiz! functions.
Whether the application is a Visual Studio add-in or not, workspaces and projects
may be added to WWhizInterface. This is done by passing a Visual Studio-compatible
filename to WWhizInterface::AddProject()
.
g_wwhizInterface->AddProject("d:\mfc.dsp"); g_wwhizInterface->AddProject("$(HOMEDRIVE)\WorkspaceWhiz\Src\WorkspaceWhiz60.dsw");
If the WWhizInterface-enabled application is a Visual Studio add-in, then the active workspace and active projects are automatically added by refreshing the file list.
When the caller is ready to access the information from added projects and
workspaces, call WWhizInterface::RefreshFileList()
.
g_wwhizInterface->RefreshFileList();
The project list may be retrieved via a call to WWhizInterface::GetProjectList()
.
WWhizProjectList& projectList = g_wwhizInterface->GetProjectList();
The project list is made up of all workspaces and projects registered with
WWhizInterface. To print the names of all projects in the active workspace,
each project must be queried through WWhizProject::IsWorkspaceProject()
.
for (int i = 0; i < projectList.GetProjectCount(); ++i) { WWhizProject* project = projectList.GetProjectByIndex(i); if (project->IsWorkspaceProject()) { AfxMessageBox(project->GetName()); } }
If WWhizInterface is used within an add-in, it is possible to retrieve the
current project as a WWhizProject
.
WWhizProject* project = g_wwhizInterface->GetCurrentProject();
WWhizInterface maintains several file lists:
WWhizInterface::GetFileList()
.WWhizProject::GetFileList()
.WWhizInterface::GetGlobalFileList()
.Upon obtaining a file list, iterating its members is performed via the WWhizFileList::GetCount()
and
WWhizFileList::Get()
functions:
WWhizFileList& fileList = g_wwhizInterface->GetFileList(); for (int i = 0; i < fileList.GetCount(); ++i) { WWhizFile* file = fileList.Get(i); const CString& fullName = file->GetFullName()); printf("%s\n", fullName); }
To completely clear the file list, use the function WWhizInterface::RemoveAllFiles()
.
This invalidates all registered workspaces and projects.
g_wwhizInterface->RemoveAllFiles();
Some applications require the files from the global Include and Source directories. WWhizInterface makes it easy to obtain those files.
g_wwhizInterface->RefreshGlobalFileList(); WWhizFileList& globalFileList = g_wwhizInterface->GetGlobalFileList();
Using the information above, a small add-in will be built that makes a
backup copy of every file in the workspace. This is done by simply making a copy of the
file to a file ending in the extension .bak
.
The code below is from CCommands::BackupProjectsAddinCommandMethod()
in the Src/Samples/BackupProjectsAddin directory of the Workspace
Whiz! source distribution.
// Create the WWhizInterface. WWhizInterface* g_wwhizInterface = WWhizInterface2Create(AfxGetInstanceHandle(), m_pApplication); // Refresh all the files. g_wwhizInterface->RefreshFileList(); // Get the project list. WWhizProjectList& projectList = g_wwhizInterface->GetProjectList(); // Iterate the projects in the list. for (int i = 0; i < projectList.GetProjectCount(); ++i) { // Get the project at the specified index. WWhizProject* project = projectList.GetProjectByIndex(i); // Is it a member of the workspace? If not, then skip it. if (!project->IsWorkspaceProject()) continue<; // Ask if we should backup the project. if (AfxMessageBox("Backup the project " + project->GetName() + "?", MB_YESNO) == IDNO) continue<; // Get the project's file list. WWhizFileList& fileList = project->GetFileList(); // Iterate all members of the file list. for (int j = 0; j < fileList.GetCount(); ++j) { // Get the file at the specified index. WWhizFile* file = fileList.Get(j); // Make a copy of it. CString existingFilename = file->GetCaseFullName(); CString newFilename = existingFilename + ".bak"; ::CopyFile(existingFilename, newFilename, FALSE); } } // Destroy the WWhizInterface. WWhizInterface2Destroy();
WWhizInterface builds a variety of tag lists for every file in the workspace. Tag lists contain information about almost every type of identifier known within the C++ language.
WWhizInterface maintains several tag lists:
WWhizInterface::GetTagList()
.WWhizProject::GetTagList()
.WWhizFile::GetTagList()
.WWhizFile::GetOrderedTagList()
.A single tag contains a myriad of information about the identifier.
public
, protected
, private
, or
friend
).To show off a few of the capabilities of WWhizInterface's tag interface, we'll build an add-in to show all the functions in the current file (similar to Workspace Whiz!'s Find Tag Special command). Our add-in, though, will show the body of the function in another window as the user clicks on a function.
When the user presses the 'ShowFunctionsAddin' button in Visual C++, the method
CCommands::ShowFunctionsAddinCommandMethod()
is called. This function first
checks to see if there is an active document. If there is, the CFunctionsDialog
is displayed.
CFunctionsDialog::OnInitDialog()
does the work of filling in the functions in
the list box. It first refreshes the file list and tag list.
// Refresh all the files. g_wwhizInterface->RefreshFileList(); // Refresh the tags. g_wwhizInterface->RefreshTagList();
The next step is to retrieve the ordered tag list from the currently open file.
// Get the current filename. CString currentFilename; g_wwhizInterface->GetCurrentFilename(currentFilename); // Get the WWhizFile pointer based on the current filename. WWhizFile* file = g_wwhizInterface->GetFileList().Find(currentFilename); // Retrieve the ordered tag list. m_orderedTagList = &file-;>GetOrderedTagList();
Lastly, we need to iterate the ordered tag list looking for functions. When a function is found, the fully qualified name should be inserted into the function list box.
// Iterate the ordered tag list looking for functions. UINT tagListCount = m_orderedTagList->GetCount(); for (UINT i = 0; i < tagListCount; ++i) { // Get a pointer to the tag. WWhizTag* tag = m_orderedTagList->Get(i); // Is it a function? if (tag->GetType() == WWhizTag::FUNCTION) { // Yes, build the qualified name. CString fullName = tag->GetParentIdent() + CString("::") + tag->GetIdent(); // Add it to the list box. int index = m_functionList.AddString(fullName); // Set the list box item's data to be the index of the tag within // the ordered tag list. m_functionList.SetItemData(index, i); } }In the list box's OnSelChange()
, we need to retrieve
the block of text representing the function source code (an approximation,
anyway) and display it in
the rich edit control. To simplify the COM automation and potentially add
portability to Visual C++ 5.0 and eMbedded Visual C++ 3.0, the helper class ObjModelHelper
from the Workspace Whiz! source
distribution is used.
// Get the current selection. int curSel = m_functionList.GetCurSel(); if (curSel == LB_ERR) return; // Set up the object model for the active document. ObjModelHelper objModel; objModel.GetActiveDocument(); // Retrieve the ordered tag list index from the current selection's item data. int tagNumber = m_functionList.GetItemData(curSel); // Get the selected tag. WWhizTag* tag = m_orderedTagList->Get(tagNumber); // Move to the line number the selected tag starts at. objModel.MoveTo(tag->GetLineNumber(), 1, dsMove); // Now extend the selection to the top of the next tag. WWhizTag* nextTag = NULL; if (tagNumber + 1 < m_orderedTagList->GetCount()) { // There was another tag to go off. Use it. nextTag = m_orderedTagList->Get(tagNumber + 1); objModel.MoveTo(nextTag->GetLineNumber(), 1, dsExtend); } else { / The selected tag was the last one in the file. objModel.EndOfDocument(dsExtend); } // Retrieve the text. CString text = objModel.GetText(); // Set it into the rich edit control. m_functionCode.SetWindowText(text);
In version 2.12, Workspace Whiz!
introduced Visual C++ 7.0 project support through WWhizInterface. Visual C++ 7.0
project files are written in XML form and have the file extension .vcproj. Some
helper classes, XmlData
and XmlNode
(both of which reside in the
Src/Shared/
directory of the source distribution) allow simple parsing and manipulation of
XML files.
The XML project files are so convenient to iterate through that a large part
of Visual C++ 5.0 and 6.0 .dsp
files are internally converted to the XML form.
Every WWhizInterface project provides access to the internal representation of
the XML format.
At this time, only the groups and files between the # Begin Target
and # End Target
of a .dsp
file are converted into XML
form. Configuration data may be converted at a later time, based on demand.
<VisualStudioProject ProjectType="Visual C++" Version="6.00" Name="Project Name"> <Files> <Filter Name="Source Files"> <File RelativePath=".\StdAfx.cpp"> </File> <Filter Name="Subgroup"> </Filter> </Filter> </Files> </VisualStudioProject>
Full XML project information is available for .vcproj files. Please refer to
the .vcproj
file for more information on other XML sections available.
In order to access WWhizInterface's XML functionality, four files need to be added to your project: XmlData.cpp, XmlData.h, Node.cpp, and Node.h. These files exist in the Src/Shared/ directory of the Workspace Whiz! source distribution.
Be sure to #include "XmlData.h"
in the appropriate
source files.
WWhizProject::GetXmlData()
returns a reference to the internal XmlData
object containing the project information.
WWhizProject& project = g_wwhizInterface->GetCurrentProject(); XmlData& xmlData = project.GetXmlData();.
Since the only guaranteed section available is <Files>
, we
need to ask the XmlData
object for the XmlNode
pointing to <Files>
.
XmlNode* filesNode = (XmlData*)xmlData.Find("Files");
If filesNode
is NULL
, then there was no section in
the XML file called <Files>
.
An XmlNode
is derived from a base class called Node
.
Node
provides basic tree traversal through the functions GetParent()
,
GetNextSiblingNode()
, GetPrevSiblingNode()
, GetFirstChildNode()
,
and GetLastChildNode()
. Iteration of XmlNode
s is done
through the Node
traversal functions.
Various attributes are stored within the WWhizInterface XML node representation. The retrieval of these attributes is performed by either calling a function that finds an attribute by name or by iterating the attribute list.
Finding an attribute by name occurs through the function XmlNode::FindAttribute()
. It returns an
XmlNode::Attribute
pointer. If the
pointer is NULL
, the attribute was not found.
XmlNode::Attribute* attr = fileNode->FindAttribute("RelativePath");
Iterating through the attribute list is done via the XmlNode::GetAttributeHead()
and
XmlNode::GetAttributeNext()
functions. They are
very similar in design to CList::GetHeadPosition()
and CList::GetNext()
.
To iterate the supported WWhizInterface hierarchy, a recursive function must
be used. The sample code, Src/Samples/ShowGroupsAddin
, shows this process.
static void RecurseFileNodes(XmlNode* parentNode, CTreeCursor cursor) { // If the parent is NULL, then abort. if> (!parentNode) return; // Start with the first child. XmlNode* node = (XmlNode*)parentNode->GetFirstChildNode(); // Iterate the children until there are no more. while< (node) { // Is it a <File> node? if (node->GetName() == "File") { // Get the relative path of the filename. XmlNode::Attribute* attr = node->FindAttribute("RelativePath"); if (attr) { // Add it to the appropriate place in the tree control. cursor.AddTail(attr->GetValue()); } } // Else is it a <Filter> node? else if (node->GetName() == "Filter") { // Get the name of the filter (the group name). XmlNode::Attribute* attr = node->FindAttribute("Name"); // Give it a default, in case something is wrong. CString name = "Unknown"; if (attr) { name = attr->GetValue(); } // Add it to the appropriate place in the tree control. CTreeCursor childCursor = cursor.AddTail(name); // Recurse deeper into the XML. RecurseFileNodes(node, childCursor); } // Go to the next XML node. node = (XmlNode*)node->GetNextSiblingNode(); } }
A custom installer made just for WWhizInterface, called the WWhizInterfaceInstaller, is available to ease end-user installations. Although not publicly available at this time, the WWhizInterfaceInstaller's MiniInstaller source code will be available soon. Please continue checking http://workspacewhiz.com/ for it. Special thanks to Jeremy Collake for his excellent PECompact software which compresses one version of the WWhizInterfaceInstaller executable to only 51k.
WWhizInterface is freely redistributable subject to the following restrictions:
The license from the Workspace Whiz! source distribution (which includes WWhizInterface) reads:
Workspace Whiz! - A Visual Studio Add-in Source Code
(http://workspacewhiz.com/) is Copyright 1999-2001 by Joshua C. Jensen
(jjensen@workspacewhiz.com).The code presented in this source distribution may be freely used and
modified for all non-commercial and commercial purposes so long as due credit
is given and the source file header is left intact.If the source module is from another author, that module may be used
subject to the restrictions of the author.Workspace Whiz! and its accompanying files are provided "as is."
The author can not be held liable for any damages caused through the use of
this software, except for refund of the purchase price.
WWhizInterface has been invaluable for a number of add-in projects myself and others have created. It continues to be enhanced as Workspace Whiz! evolves and as users request new features. It is my hope that the functionality WWhizInterface provides can be of benefit to other add-ins and tools authors.
Thanks,
Joshua Jensen
Joshua Jensen is a gamer at heart and as such, creates games for a living. He is currently employed at Microsoft's games division in Salt Lake City, UT where he is enjoying making titles for the Xbox.
In his spare time, he maintains a Visual C++ add-in called Workspace Whiz! Find it at http://workspacewhiz.com/.
Click here to view Joshua Jensen's online profile.
Premium Sponsor |
|
Home >>
Macros and Add-ins >>
DevStudio Add-ins
Advertise on The Code Project |
Article content copyright Joshua Jensen, 2001 everything else © CodeProject, 1999-2002. |
DevelopersDex • DevGuru • Programmers Heaven • Tek-Tips Forums • TopXML • VisualBuilder.com • W3Schools • XMLPitstop • ZVON • Search all Partners |