In a Visual Studio Extension, get the line range of the function in which the debugger is stopped - visual-studio

I have a Visual Studio extension that hooks into debugging events. When the debugger stops at a line of code, my IDebugEventCallback2 callback gets called, and I can find out the filename and line number where the debugger has stopped via IDebugThread2::EnumFrameInfo.
I'd like to know the range of source code lines that the current function spans.
I'm hoping it's possible to derive the information I need from the debugger interfaces - the debugger must know the line range of functions. If that's not possible, I'm open to any other methods. In an ideal world the solution would work without the project system - many people, myself included, use Visual Studio as a stand-alone debugger without using the project system. (Also, I can't rely on Roslyn - it needs to work in existing versions of Visual Studio.)
Edit: Carlos's method of using FileCodeModel works well, as long as the file is part of a project. I'd still love to know whether there's a method that doesn't require the project system.

Given a FRAMEINFO retrieved with IEnumDebugFrameInfo2.Next, you can use the following code to get the file name, the first line of code of the current frame and the current line of code:
IDebugStackFrame2 stackFrame = frmInfo.m_pFrame;
if (stackFrame != null)
{
TEXT_POSITION[] begin = new TEXT_POSITION[1];
TEXT_POSITION[] end = new TEXT_POSITION[1];
IDebugDocumentContext2 debugDocumentContext2;
stackFrame.GetDocumentContext(out debugDocumentContext2);
if (debugDocumentContext2 != null)
{
string fileName;
debugDocumentContext2.GetName((uint)enum_GETNAME_TYPE.GN_FILENAME, out fileName);
debugDocumentContext2.GetSourceRange(begin, end);
}
}
FWIW, the IDebugDocumentContext2 interface has a Seek method that allows you to advance lines or statements of code in the stack frame. I guess you can advance until failure to get the end line of code of the stack frame.
To get info about code elements and start/end points using the project system (and without Roslyn) you have to use the automation model (EnvDTE.ProjectItem.FileCodeModel). Given a EnvDTE.ProjectItem and a line of code, you can use for example: HOWTO: Get the code element at the cursor from a Visual Studio .NET macro or add-in

Related

Behaviour at opening or referencing in Excel for an XLL having a DLL as dependency

Take an XLL. You can
open it from excel by doing "File" then "Open" etc or by dragging and dropping it in a blank (or not) Excel spreadsheet (it is the same)
reference it from Excel using Excel's Add-ins manager by (in recent Excel versions) doing "File", "Options", "Add-ins" and in "Manage" choose "Excel Add-ins", then "Go", then browse to point to the XLL then "OK" so that it appears and is ticked in the Add-ins list, then "OK" to reference it. (That's the "OK" I will I refer to in what follows.)
Now I have an XLL1 that I compile with only one visual studio project. Opening it or referencing it doesn't make any difference and I have no problem. Great.
The code of XLL1 is basically lots of XLL functions that process inputs in the Excel C Api format to convert them in some internal (and Excel C Api independant) format and then feed these processed inputs to underlying C++ functions that performe intricated numerical calculations etc. I decided to separe the Excel C API stuff (the interface) from this underlying Excel C API independant numerical stuff by plugging the latter in a C++ DLL (that I will simply call "DLL" here) that the XLL's project now links to. I call the resulting XLL "XLL2".
Now if I open (in the sense defined above) XLL2 I get the following error :
the file format and extension of 'XLL2.xll' don't match. The file could be corrupted of unsafe. Blah Blah
that you usually get when you have an architecture mismatch between your Excel and your XLL, which is not my case here as everything (my Excel and my projects platforms) is 64 bits -- I confirmed it with Dependency Walker. (Plus, I don't see why architecture would matter suddenly for XLL2's "execution" when it doesn't for XLL1's compilation.)
If I try to reference (in the sense defined above) XLL2 in Excel, at the final "OK" I get the following error :
Microsoft Excel cannot access the XLL2.xll". There are several possible reasons : the file name or path does not exist ; the file is being used by another program ; the workbook you are trying to save has the same name as a currently open workbook.
Of course, none of this three reasons is verified in my case.
I naturally suspected that at the opening or referencing of XLL2 Excel cannot get the information about XLL2 depending on DLL, hence yells the only error message it is allowed to yell at that point.
Indeed, when I put XLL2 and DLL next to EXCEL.exe in the same folder (vomiting smiley) I had no issue with open or referencing it. But sadly, I don't intend to ship my code inside EXCEL.exe's folder so that option isn't a real one for me. Naturally, I tried to add "$(UniversalCRT_LibraryPath_x86)" or "$(UniversalCRT_LibraryPath_x64)" to the additional libraries directories in XLL2's properties in "linker --> general" (where I naturally already add the path to the DLL XLL2 links to). It just allowed me to open1 XLL2 at debug in visual studio. In the normal utilisation, outside of visual studio, the problem remained, while both in debug or normal execution, referencing XLL2 wasn't working.
Then I told myself that the only solution is in fact a proper use of LoadLibrary not just before Excel "loads" the XLL, but after that and just before it loads the XLL functions (that depend on DLL). I modified the Excel C API template code as follows :
// Excel calls xlAutoOpen when it loads the XLL.
__declspec(dllexport) int WINAPI xlAutoOpen(void)
{
static XLOPER12 xDLL; // The filename of this XLL.
int i;
#if DEBUG
debugPrintf("xlAutoOpen\n");
#else
#endif
// Fetch the name of this XLL. This is used as the first arg
// to the REGISTER function to specify the name of the XLL.
Excel12f(xlGetName, &xDLL, 0);
if (xDLL.xltype == xltypeStr)
{
std::wstring wpath(xDLL.val.str);
std::string path(wpath.begin(), wpath.end());
path.erase(0, 1);
path = std::regex_replace(path, std::regex("XLL2.xll"), "DLL.dll");
std::wstring wpath2 = std::wstring(path.begin(), path.end());
LPCWSTR lpath = wpath2.c_str();
HINSTANCE hinstLib = LoadLibrary(lpath);
if (hinstLib == NULL)
return -1;
}
#if DEBUG
debugPrintf("XLL Name : %S\n", xDLL.val.str);
#else
#endif
// Loop through the g_rgUDFs[] table, registering each
// function in the table using xlfRegister.
for (i = 0; i < g_rgNumUDFs; i++)
by adding the if (xDLL.xltype == xltypeStr) part and choose to delay the DLL loading in XLL2's linker properties in VIsual Studio. And I also added $(UniversalCRT_LibraryPath_x64) (or $(UniversalCRT_LibraryPath_x86)) in the additional library directory for the linker in XLL2 project's properties.
And indeed, at debug or at execution, I can open XLL2 and use XLL2 as I was opening and using XLL1 : without any problem. (Important : would I not have add $(UniversalCRT_LibraryPath_x64) as described above, open the XLL2 would have triggered no error, but no functions of XLL2 would have been seen from Excel.)
But would I try to reference XLL2, I still have the same issue. (And referencing it matters as it allows for XLL2 to be loaded next times the user opens Excel, while just opening the XLL2 opens it only for the current Excel instance, not for the next ones.)
What puzzles me is that when you reference an XLL for the first time, Excel calls the XLL's xlAddInManagerInfo12 function (see https://learn.microsoft.com/en-us/office/client-developer/excel/xladdinmanagerinfo-xladdinmanagerinfo12 for instance) while when I put a breakpoint at the beginning of that functions in my XLL2's code, I never break at it at debug when I reference XLL2, I just directly have the Microsoft Excel cannot access the XLL2.xll". There are several possible reasons error message after doing "OK" and that's it. (Also, checking DLLs loaded by that Excel instance in Process Explorer doesn't show XLL2 nor DLL.)
I would like to know the proper way to achieve using XLL2 as I was using XLL1 (opening or referencing).

TypeScript cast (assertion) results in Syntax Error compiler warning?

I've got a TypeScript script that has to interact with a third-party vendor that uses global functions as callbacks (you can't pass in a callback). For instance, to "listen" for a result from their "API", you define the function SetElqContent. E.g.,
window.SetElqContent = function(){/* handle result */};
When the TypeScript compiler sees this line, it complains that The property 'SetElqContent' does not exist on value of type 'Window'.
I thought I could get around this by simply casting to type "any". Actually, this isn't type casting but type assertion, but I think of it as casting, although I understand it's not quite the same. So, I tried:
(<any>window).SetElqContent = function(){/* handle result */};;
To m y surprise, this results in Syntax error, and the line number and column points to the < character in the <any> cast. I tried a few other variants, and I get Syntax error on the initial < of the cast no matter what kind of cast I was doing:
var windowAny = <any>window;
var docElement = <HTMLElement>window.document;
What is it about my type assertions that is invalid syntax?
I'm using Visual Studio 2013 with Update 2, which has a "compile on save" feature for TypeScript files. That's how I'm compiling my .ts files, and it's from in Visual Studio where the Syntax error message is emitted.
UPDATE: Apparently this is related to Visual Studio. When I use the standalone tsc compiler to compile the same file, it emits no errors or warnings.
Apparently my syntax is correct but there is a bug in the Visual Studio tooling. I can't provide exact reproduce steps, and in fact, deleting everything in the .ts file, saving, then restoring the code (via ctrl-z) and resaving caused the "syntax error" warning to disappear.
If I can determine any more specifics about what causes this issue to manifest, I'll report back.
Best way is to create a type definitions file for it
If the library name is eloqua.js, you create a eloqua.d.ts file and refer to it in your .js file like
/// < reference path="../typings/eloqua.d.ts" />
There are many type definition files online available at definitelyTyped website.
https://github.com/borisyankov/DefinitelyTyped
You can contribute yours to there as well.
If you extend the Window interface definition, you'll remove the error:
interface Window {
SetElqContent: Function;
}
window.SetElqContent = function(){/* handle result */};
Here is how you can do the assertion properly:
function SetElqContent(){/* handle result */};
// FINE
(<any>window).SetElqContent = SetElqContent;
or
// FINE
(<any>window).SetElqContent = function SetElqContent(){/* handle result */};
However you should avoid asserting and just do what Steve Fenton recommends as it is more discoverable
Update
Demo in VS:

Inspect value of inline return during debug

I'm doing some mobile development and sometimes things are easiest to debug by attaching to the server. For the sake of brevity, I like to write something like this:
Public Function GetData(parameters) As FuzzBomb
Using data As New PersistentDataAccessLayer()
Return data.MakeStateChangingCall(parameters)
End Using
End Function
However, if you have a string of function calls written this way (i.e. Return GetValueFromSomeFunction), it's really hard to inspect the value being returned while debugging.
Since there are side effects, I can't simply copy/paste the function call into the watch window. I could assign the results to a temporary variable... but that seems ugly to me:
Using data As New PersistentDataAccessLayer()
Dim result = data.MakeStateChangingCall(parameters)
Return result
End Using
Is there a better way?
This features is supported in Visual Studio 2013, .NET 4.5.1. Needed values will be appear in Autos window. You can find feature description in Somasegar's blog.
In old version of Visual Studio and .NET you can use Immediate Window: just write data.MakeStateChangingCall(parameters) from debugged function and Visual Studio will evaluate target value (result of function).

How to programatically add a tracepoint for Visual Studio?

I am looking for a method to monitor a running program that I have the source code. Basically, when the user runs it, I need to know what functions and parameter is called sequentially.
I can write a trace output code to all functions to achieve this. However, I am not allowed to modify the source code.
I found out that Tracepoint in Visual Studio 2005 allows me to do this - output log info without modifying the source. But I need to add them to all functions.
As I have thousands of files and functions, I need to find a way to programatically do this. I found out about DTE.Debugger.Breakpoints.Add that able to add a breakpoint. However, I couldnt find any way for tracepoint. Or where is the breakpoint info for a project stored? I couldnt find it in sln or vcproj. Or is there a way to convert breakpoint to tracepoint programatically? I see that I can change it manually by changing the "When Hit" property dialog.
Thanks!
You should cast your breakpoints to EnvDTE80.Breakpoint2. Then you'll be able to use
breakpoint.BreakWhenHit = false;
breakpoint.Macro = "YourMacro";
A .NET profiler will allow you to see which methods are executed and how long each takes without modifying the source code. It basically injects special code into the compiled assembly.
I think this is the solution... this macro adds a breakpoint the Main method of your program, and then turns all breakpoints into tracepoints.
Sub AddBreakpointToMain()
Dim bp As EnvDTE80.Breakpoint2
Dim bps As EnvDTE.Breakpoints
bps = DTE.Debugger.Breakpoints.Add("Main")
For Each bp In bps
bp.Tag = "SetByMacro"
bp.BreakWhenHit = False
bp.Message = "Hi, this is your tracepoint calling."
Next
End Sub
You could also look at Aspect Oriented coding. By my understanding this will change the compiled assembly controlled by attributes and is typically used to add tracing to all methods/properties.
How can I add a Trace() to every method call in C#?
First part of the solution:
DTE.ExecuteCommand("EditorContextMenus.CodeWindow.Breakpoint.InsertTracepoint")
It opens the TP window for the line where the cursor was. You will still have to hit Return to select OK, though. Enough for my needs--at least you don't have to right-click, etc.

Macro expansion in Visual Studio macro or add in

I have a VS project with an IntermediateDirectory like this: "....\temp\$(SolutionName)\$(ProjectName)".
I can read this value using a macro or add in, however, I would need the actual directory to manipulate files there. Right now, I manually replace the "$(SolutionName)" and "$(ProjectName)" with the respective values, which works fine but might become complicated when different macros or even user macros from property sheets are used.
So my question is:
Does the Visual Studio API have a built in function to expand macros like these? Or is there some other elegant solution?
There is an elegant solution! But I only know the one that applies to C++ projects.
Assuming you're in a C# add-in:
// Get the main project from the first startup project
VCProject vcMainProject = (VCProject)(_applicationObject.Solution.SolutionBuild.StartupProjects as IVCCollection).Item(1);
Project mainProj = (Project)_vcMainProject .Object;
// Get the configuration we'll be using
IVCCollection cfgs = (IVCCollection)_vcMainProject .Configurations;
VCConfiguration vcCfg = (VCConfiguration) cfgs.Item(mainProj.ConfigurationManager.ActiveConfiguration.ConfigurationName + "|" + mainProj.ConfigurationManager.ActiveConfiguration.PlatformName);
string finalString = vcCfg.Evaluate("....\temp\$(SolutionName)\$(ProjectName)");
You can also check out this page:
http://msdn.microsoft.com/en-us/library/czt44k0x%28VS.71%29.aspx
If you're not using this for C++, there should be a similar interface for the Project, Configuration, and Solution classes provided for other languages (C# and VB).
As far as i know, there is no API available that will expand those macro values. Although it shouldn't be too hard to write a quick and dirty implementation that deals with only the values that you care about.
For instance, in this case you only care about 2 values (SolutionName and ProjectName). If these are the values you are primarily interested in use a simple search and replace with the best values.
Yes this is a sub-optimal solution. But it may help to unblock your progress.

Resources