This is the 3rd post related to the LeakMon tool. In this post, we will see how the DumpViewer
(DumpViewer
is an application used to analyze the leak dump created by LeakMon
) is able to open a particular file in Visual Studio and highlight the specified line.
Step 1: Finding Out the Version of Visual Studio to Use
It is 2011 now and we have Visual Studio 2010. But there are people or companies who still use Visual Studio 6. Things have changed a lot from Visual Studio 6 to Visual Studio 10, even the EXE name has changed. So the first task is to find out which version of Visual Studio to use.
DumpViewer
uses the same Visual Studio version that is registered as the default program for documents such as .c, .cpp, .h, .hpp on your machine. For instance, say we have the following line shown in DumpViewer
:
func2 t:\naveen\pgms\cpp\2008\mulitthread\mulitthreaddlg.cpp(178)
DumpViewer
will extract the file path and line number from the above line and pass it to the FindExecutable function. This function will return the path of executable that is registered for the above file types, which will usually be Visual Studio. If it is not Visual Studio, sorry, it will fail!
In the file name of the executable returned by FindExecutable function, we will check whether there is a string
"msdev.exe" or "devenv.exe". If there is "msdev.exe" in the string
, code that does the automation of Visual Studio 6 is called, otherwise the code to handle higher version of Visual Studio is called.
const LPCTSTR VC_6_APP_NAME = _T("msdev.exe");
const LPCTSTR VC_8_APP_NAME = _T("devenv.exe");
if( 0 == csExe.CompareNoCase( VC_6_APP_NAME ))
{
OpenUsingVS<IApplication>( IID_IDispatch, L"MSDEV.Application",csFileName_i, nLineNo );
}
else if( 0 == csExe.CompareNoCase( VC_8_APP_NAME ) )
{
OpenUsingVS<_DTE>( EnvDTE::IID__DTE,
L"VisualStudio.DTE",csFileName_i, nLineNo );
}
Step 2: Getting a Pointer to Already Running Instance of Visual Studio
Since we are going to open a file through automation, we need a pointer of either IApplication
or _DTE
(IApplication
for Visual Studio 6 and _DTE
for Visual Studio 7 and higher). We can create an instance of the above type using the CoCreateInstance
by passing the IID
and CLSID
. But this will create a new instance of Visual Studio each time we try to open a new file. So what if we want is to get a pointer to IApplication
/ _DTE
of an already running instance of Visual Studio if any. This can be done by enumerating the Moniker in the ROT (Running Object Table).
template<class T>
bool OpenUsingVS( IID riid, CString csProgID, CString csFileName_i, int nLineNo)
{
HRESULT hRes;
CLSID clsid;
CComPtr<IUnknown> punk;
CComPtr<T> dte;
RETURN_ON_FAIL( ::CLSIDFromProgID( csProgID.operator LPCTSTR(), &clsid) );
CComPtr<IRunningObjectTable> ROT;
RETURN_ON_FAIL( GetRunningObjectTable( 0, &ROT ) );
CComPtr<IBindCtx> bindCtx;
RETURN_ON_FAIL( CreateBindCtx( 0, &bindCtx ) );
CComPtr<IEnumMoniker> enumMoniker;
RETURN_ON_FAIL( ROT->EnumRunning( &enumMoniker ) );
CComPtr<IMoniker> dteMoniker;
RETURN_ON_FAIL( CreateClassMoniker( clsid, &dteMoniker ) );
CComPtr<IMoniker> moniker;
CComPtr<IMoniker> moniker2;
ULONG monikersFetched = 0;
while ( enumMoniker->Next( 1, &moniker, &monikersFetched ) == S_OK)
{
moniker2 =NULL;
moniker->Reduce( bindCtx, MKRREDUCE_ALL, NULL, &moniker2 );
IMoniker* pMon = (0 != moniker2)?moniker2:moniker;
if ( moniker2->IsEqual( dteMoniker ) )
{
hRes = ROT->GetObject( moniker, &punk );
if ( hRes == S_OK )
{
dte = punk;
if ( dte )
{
if( OpenFile( dte,csFileName_i, nLineNo ))
return true;
dte = 0;
}
}
punk = NULL;
}
moniker = NULL;
}
if ( !dte )
{
RETURN_ON_FAIL( ::CoCreateInstance
( clsid, NULL, CLSCTX_LOCAL_SERVER, riid , (LPVOID*)&punk ) );
dte = punk;
if ( !dte )
return false;
OnLaunch( dte );
OpenFile( dte,csFileName_i, nLineNo );
}
return true;
}
As you can see in the above code, if we failed to get a valid instance pointer of Visual Studio, we create a new one using CoCreateInstance
. In Visual Studio 7 and higher, as soon as we release the com pointer that we got from CoCreateInstance
, Visual Studio will terminate. To prevent Visual Studio from exiting like this, we need to call put_UserControl
of DTE interface.
Step 3: Opening a File and Selecting a Line from it
From this part on, the processing is different for Visual Studio 6 and other versions of Visual Studio.
In Visual Studio 6
#define dsWindowStateMaximized 1
bool OpenFile(IApplication* m_spApplication,CString csFileName_i, int nLineNo )
{
CComPtr<IDispatch> pDispatch;
m_spApplication->put_Visible( VARIANT_TRUE );
m_spApplication->put_WindowState( dsWindowStateMaximized );
m_spApplication->get_Documents( &pDispatch );
CComQIPtr<IDocuments> pDocs = pDispatch;
CComVariant type="Auto";
CComVariant read="False";
pDispatch = 0;
pDocs->Open( CComBSTR(csFileName_i),type, read, &pDispatch );
CComQIPtr<ITextDocument> pDoc = pDispatch;
pDispatch = 0;
pDoc->get_Selection( &pDispatch );
CComQIPtr <ITextSelection> pTextSelection = pDispatch;
CComVariant varReserved( FALSE );
pTextSelection->StartOfDocument( varReserved );
pTextSelection->LineDown( varReserved, CComVariant(nLineNo-1));
Sleep( 1000 );
pTextSelection->SelectLine();
return true;
}
In Visual Studio 7 and Higher
bool OpenFile( _DTE* pDTE,CString csFileName_i, int nLineNo )
{
HRESULT hRes = 0;
CComPtr<ItemOperations> Operations;
RETURN_ON_FAIL(pDTE->get_ItemOperations( &Operations ));
CComPtr<Window> Wnd;
RETURN_ON_FAIL(Operations->OpenFile( CComBSTR( csFileName_i),
CComBSTR(vsViewKindTextView), &Wnd ))
CComPtr<Document> pDoc;
for( int nIdx =0; nIdx < 5; nIdx ++ )
{
pDTE->get_ActiveDocument( &pDoc );
if( pDoc )
{
break;
}
if( nIdx == 4 )
{
return false;
}
Sleep( 100 );
}
CComPtr<Window> pMainWnd;
RETURN_ON_FAIL(pDTE->get_MainWindow( &pMainWnd ))
pMainWnd->Activate();
CComPtr<IDispatch> pDisp = 0;
RETURN_ON_FAIL(pDoc->get_Selection( &pDisp ));
CComQIPtr<TextSelection> pSelection = pDisp;
pSelection->GotoLine( nLineNo, VARIANT_TRUE);
return true;
}