
Introduction
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:
- Retrieval of the full path to the workspace (.dsw or .vcw file).
- Retrieval of the current project.
- Retrieval of the current filename.
- External parsing of the workspace .dsw or .vcw file to retrieve projects in the
workspace.
- External parsing of the project's .dsp, .vcp, or
.vcproj (Visual C++ 7.0) file to retrieve files within the project.
- Resolution of filenames containing environment variables and relative paths.
- Quick scans of workspace and project files for changes.
- Addition of projects not part of the workspace for all facilities provided
by WWhizInterface.
- Retrieval of file lists for the global workspace, and individual projects
- Fast creation of tag lists for any files in the workspace.
- Determination of tag scope for a given file and line number.
- XML queries of project information.
- ... and more.
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.
Working With Workspaces, Projects, and Files
Creating a WWhizInterface object
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.
Workspace Name
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.
Obtaining the current file
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))
{
WWhizFile* curFile = g_wwhizInterface->GetFileList().Find(currentFilename);
}
Filename resolution
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;
g_wwhizInterface->ResolveFilename(rootDirectory, relativeFilename);
g_wwhizInterface->ResolveFilename(rootDirectory, environmentFilename);
Project Retrieval
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();
Retrieving the Project List
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();
Retrieving filenames
WWhizInterface maintains several file lists:
- A complete file list, comprised of every file from every project in every
workspace registered with WWhizInterface. Accessed through
WWhizInterface::GetFileList()
.
- A per project file list containing every file within a given project. Accessed
through
WWhizProject::GetFileList()
.
- A list of files recursively found within the Global Include and Source paths.
Obtained through
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);
}
Clearing out the file list
To completely clear the file list, use the function WWhizInterface::RemoveAllFiles()
.
This invalidates all registered workspaces and projects.
g_wwhizInterface->RemoveAllFiles();
Querying the Global Include and Source directories
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();
Example: Backup Projects Add-in
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.
WWhizInterface* g_wwhizInterface = WWhizInterface2Create(AfxGetInstanceHandle(), m_pApplication);
g_wwhizInterface->RefreshFileList();
WWhizProjectList& projectList = g_wwhizInterface->GetProjectList();
for (int i = 0; i < projectList.GetProjectCount(); ++i)
{
WWhizProject* project = projectList.GetProjectByIndex(i);
if (!project->IsWorkspaceProject())
continue<;
if (AfxMessageBox("Backup the project " + project->GetName() + "?", MB_YESNO) == IDNO)
continue<;
WWhizFileList& fileList = project->GetFileList();
for (int j = 0; j < fileList.GetCount(); ++j)
{
WWhizFile* file = fileList.Get(j);
CString existingFilename = file->GetCaseFullName();
CString newFilename = existingFilename + ".bak";
::CopyFile(existingFilename, newFilename, FALSE);
}
}
WWhizInterface2Destroy();
Using Tags
Overview
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:
- A complete sorted tag list, comprised of every tag from every file in every project in every
workspace registered with WWhizInterface (phew!). Accessed through
WWhizInterface::GetTagList()
.
- A per project sorted tag list containing every tag in every file within a given project. Accessed
through
WWhizProject::GetTagList()
.
- A per file sorted tag list containing every tag in the file. Accessed
through
WWhizFile::GetTagList()
.
- A per file ordered tag list containing every tag in the order
it appears in the file. Accessed through
WWhizFile::GetOrderedTagList()
.
Tags
A single tag contains a myriad of information about the identifier.
- The access type (
public
, protected
, private
, or
friend
).
- The implementation type (abstract, virtual, pure virtual).
- The tag type (class, declaration, define, enum, enum member, file, function, class
member, namespace, structure, typedef, union, variable).
- The filename where the tag resides.
- The regular expression search string to find the tag.
- The parent identifier name (class name if the tag is a class member, etc).
- The line number the tag resides.
- The namespace.
- The "buddy" tag (declaration if the tag is a definition and vice
versa).
Example: Show Functions Add-in
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.
// Get the current file.
CString currentFilename;
g_wwhizInterface->GetCurrentFilename(currentFilename);
// If there isn't a file currently open, then don't show the dialog.
if (!currentFilename.IsEmpty())
{
CFunctionsDialog dlg;
dlg.DoModal();
}
CFunctionsDialog::OnInitDialog()
does the work of filling in the functions in
the list box. It first refreshes the file list and tag list.
g_wwhizInterface->RefreshFileList();
g_wwhizInterface->RefreshTagList();
The next step is to retrieve the ordered tag list from the currently open
file.
CString currentFilename;
g_wwhizInterface->GetCurrentFilename(currentFilename);
WWhizFile* file = g_wwhizInterface->GetFileList().Find(currentFilename);
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.
int curSel = m_functionList.GetCurSel();
if (curSel == LB_ERR)
return;
ObjModelHelper objModel;
objModel.GetActiveDocument();
int tagNumber = m_functionList.GetItemData(curSel);
WWhizTag* tag = m_orderedTagList->Get(tagNumber);
objModel.MoveTo(tag->GetLineNumber(), 1, dsMove);
WWhizTag* nextTag = NULL;
if (tagNumber + 1 < m_orderedTagList->GetCount())
{
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);
}
CString text = objModel.GetText();
m_functionCode.SetWindowText(text);
Using the XML Interface
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.
The XML Project File 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.
Adding XML Support to your Project
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.
Retrieving the XML Project Data
WWhizProject::GetXmlData()
returns a reference to the internal XmlData
object containing the project information.
WWhizProject& project = g_wwhizInterface->GetCurrentProject();
XmlData& xmlData = project.GetXmlData();
.
Iterating the XML Project Data
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.
Retrieving Attributes
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()
.
Example: Show Groups Add-in
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> (!parentNode)
return;
XmlNode* node = (XmlNode*)parentNode->GetFirstChildNode();
while< (node)
{
if (node->GetName() == "File")
{
XmlNode::Attribute* attr = node->FindAttribute("RelativePath");
if (attr)
{
cursor.AddTail(attr->GetValue());
}
}
else if (node->GetName() == "Filter")
{
XmlNode::Attribute* attr = node->FindAttribute("Name");
CString name = "Unknown";
if (attr)
{
name = attr->GetValue();
}
CTreeCursor childCursor = cursor.AddTail(name);
RecurseFileNodes(node, childCursor);
}
node = (XmlNode*)node->GetNextSiblingNode();
}
}
Redistribution
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:
- To distribute the WWhizInterface module in its entirety, the WWhizInterfaceInstaller must
be used.
- Individual source modules from the WWhizInterface source code made be
inserted into an application. In this case, distribution of the
WWhizInterfaceInstaller is not required.
License
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.
Conclusion
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