Managed C++: Debugging with the StackTrace Class

As a C++ developer, you're more accustomed than most to looking at the call stack during your debugging sessions. As an extension of that, many times you need a way to programmatically retrieve stack information during the execution of your application. Unfortunately, for a long time the best you had for this task was the standard __FILE__ and __LINE__ directives. This week's tip introduces the StackTrace and StackFrame classes, which enable you to walk up the entire stack, gathering specific information about each method (such as method name, return type, accessor level, and parameter). This should make your debugging life a bit easier in situations where you need to know which methods have been called (and in which order).

The StackTrace and StackFrame Classes

The StackTrace object represents the call stack at the point that it is instantiated and includes an ordered list of one or more StackFrame objects; each of which represents a method in the call stack. The following code shows an example of one method (Foo) calling another (Bar), where the second enumerates the stack and retrieves each method name in the stack:

protected: System::Void Foo()
{
  // Instantiate the StackTrace object here or else
  // its info will include the Foo method
  Bar(new StackTrace());
}

protected: System::Void Bar(StackTrace* stack)
{
  int frames = stack->FrameCount;

  // Enumerate the stack frames starting at the current
  // method (0). (Frames can only be retrieved by index value.)
  StackFrame* frame;
  MethodInfo* methodInfo;

  String* methodName;
  for (int i = 0; i < frames - 1; i++)
  {
    frame      = stack->GetFrame(i);
    methodInfo = static_cast<MethodInfo*>(frame->GetMethod());
    methodName = methodInfo->Name;
  }
}

Note the following three things about the code:

  • The only way to retrieve a StackFrame object from a StackTrace object is via the StackFrame object's index value. This value starts at 0 for the last—or most recently called—method.
  • Once you have a StackFrame object, you must retrieve its internal MethodBase object (cast to MethodInfo) in order to determine specific method information (such as method name, parameter information, and so on). This is done via the StackFrame::GetMethod method.
  • The previous example would list the Foo method as the last method on the stack because the StackTrace object was created in that method and not in the Bar method.

Retrieving Method Information

Once you have a MethodInfo object, you can query it for the salient information that your application needs.

  • Method accessor level and static versus instance—There is no single enumeration value to hold the accessor level value. Therefore, querying for this information involves calling each of four separate MethodInfo properties: IsPublic, IsPrivate, IsFamily, and IsAssembly. The IsFamily method represents a method defined as protected and the IsAssembly method represents a method defined as internal. You can determine whether a method is defined as static or instance by calling the IsStatic method:

    if (methodInfo->IsPublic) ...
    else if (methodInfo->IsPrivate) ...
    else if (methodInfo->IsFamily) ...
    else if (methodInfo->IsAssembly) ...
    
    if (methodInfo->IsStatic) ...
    
  • Return value—The return value of a method is retrieved via the MethodInfo::ReturnType property:
    String* returnType = methodInfo->ReturnType;
  • Method name—Retrieving the method name is also straightforward. It involves simply referring to the MethodInfo::Name property:
    String* methodName = methodInfo->Name;
  • Method parameters—Retrieving parameter information requires first obtaining an array of ParameterInfo objects via the MethodInfo::GetParameters method. The ParameterInfo::IsByRef property is true if the parameter is being passed by reference and false if the parameter is being passed by value. The parameter name and parameter type values are retrieved via the Name and ParameterType properties, respectively. Here's an example code snippet of retrieving those values:
    ParameterInfo* params[] = methodInfo->GetParameters();
    for (int i = 0; i < params->Count; i++)
    {
      ParameterInfo* param = params[i];
    
      String* paramDisp = ((param->ParameterType->IsByRef) ?
                            S"ByRef" : S"ByVal");
      String* paramType = param->ParameterType->Name;
      String* paramName = param->Name;
    }
    
  • File name and line number—For this to work, you must first instantiate the StackTrace object by passing it a value of true. Then you can use the GetFileName and GetFileNumber methods to retrieve that information. The following example first checks to make sure that you can retrieve the file name and line number, and then it crops that name from the end to the last backslash character so that you get only the file name and not its full folder qualification:
    String* fileName = frame->GetFileName();
    int lineNumber   = frame->GetFileLineNumber();
    if (fileName && lineNumber)
    {
      String* truncatedName;
      int idx = fileName->LastIndexOf(S"\\");
      if (-1 != idx)
        truncatedName = fileName->Substring(idx+1,
                                            fileName->Length -
                                            idx - 1);
      else
      truncatedName = fileName;
    }
    

Exceptions and the StackTrace Class

There are many ways to instantiate a StackTrace object, so I won't go through them all. However, I will mention one that is especially useful when handling exceptions. When you pass an Exception object to the StackTrace constructor, only one StackFrame object will be created and it will represent the method that threw the exception.

As an example of that, consider the following code where the btnDisplayStack_Click method calls the BadMethod that throws an exception. Because the StackTrace object is constructed with the caught exception, the only StackFrame object within the StackTrace object will represent the method that threw the exception, which is sometimes all you need—as opposed to the entire stack (The passing of the true value denotes that I also want file name and line number information.):

private: System::Void btnDisplayStack_Click(System::Object *  sender,
                                            System::EventArgs *  e)
{
  try
  {
    BadMethod();
  }
  catch(Exception* e)
  {
    DisplayStackFrames(new StackTrace(e, true));
  }
}

private: System::Void BadMethod()
{
  throw new Exception(S"Failed");
}


About the Author

Tom Archer - MSFT

I am a Program Manager and Content Strategist for the Microsoft MSDN Online team managing the Windows Vista and Visual C++ developer centers. Before being employed at Microsoft, I was awarded MVP status for the Visual C++ product. A 20+ year veteran of programming with various languages - C++, C, Assembler, RPG III/400, PL/I, etc. - I've also written many technical books (Inside C#, Extending MFC Applications with the .NET Framework, Visual C++.NET Bible, etc.) and 100+ online articles.

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • On-demand Event Event Date: May 16, 2017 Developers who use popular JavaScript frameworks like React to build user interfaces often have to create components to build the interface. Building components is time consuming, has integration and maintenance risks, and worst of all, distracts them from the task at hand — actually building the app. Watch this webinar to see how to quickly and easily add more than 115 professionally supported and tested components from Sencha to your React apps. In this …

  • Live Event Date: June 29, 2017 @ 1:00 p.m. ET / 10:00 a.m. PT Security teams looking to extend their network perimeter defenses with an attack and breach detection capability are discovering that a hosted SIEM gets them up and running faster. IBM QRadar on Cloud is a market–leading, trusted solution that can be easily extended with apps and add–ons for user behavior analytics and cognitive security. If you are still relying on basic log collection tools or an aging collection of security point …

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date