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");
}

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read