Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

Method Call Interception


The Mci class is similar to the AutoTrace class previously mentioned. Listing One contains the Mci class. Mci just notifies its sink that some method has entered or left (by the program counter). It also provides a lot of information about this method to the sink. This information consists of the filename and line where this specific instance of Mci is located and also a method info struct, which contains the class name, method name, and type of each argument. The sink is registered by calling the static Register method. The GetSink() method is an interesting hack; it allows using a static variable without declaring it in a .cpp file. Although the C++ Standard allows declaring and initializing a static variable in the class definition, VC++ 6.0 (which I use) doesn't. The GetSink() method returns a reference to an internal static object and thus circumvents the problem. This means that all the instances of Mci in each and every method in your code notifies this single sink. On the surface, it looks like the same code is executed for every method, but in practice, the registered sink may employ a filtering and classification system based on the method information passed to it and dispatch the events accordingly to different handlers. For example, the initial sink may dispatch events according to groups of filenames (dispatch all events from files a.cpp, b.cpp, and c.cpp to Handler_1 and all other events to Handler_2). The important point here is that Mci—the event's source—is completely unaware of the entire procedure. It doesn't even know the true type of the original sink. All Mci knows is that someone registered an IMciEvents pointer to which it sends all the events.

Listing One

(a)


#ifndef MCI_H
#define MCI_H

#include <string>
#include "MethodAnalyzer.h"

struct IMciEvents;

class Mci  
{
public:
    Mci(const std::string & filename, int lineNumber, std::string line);
    ~Mci();
    static IMciEvents * & GetSink();
    static void Register(IMciEvents * pSink);
private:
    std::string GetLine(const std::string & filename, int lineNumber);
private:
    std::string     m_filename;
    int             m_lineNumber;   
    MethodInfo      m_methodInfo;
};
#endif // !defined(__MCI_H__)

(b)

#include "Mci.h"
#include "IMciEvents.h"
#include "MethodAnalyzer.h"
#include <fstream>

using std::string;
using std::ifstream;

Mci::Mci(const string & filename, int lineNumber, string line) :
        m_filename(filename),
        m_lineNumber(lineNumber)
{
    if (!GetSink())
        return;
    if (line.empty())
        line = GetLine(filename, lineNumber-2);
    m_methodInfo = MethodAnalyzer::Analyze(line);
    // verify corectness of class name using typeinfo
    GetSink()->OnEnter(m_filename, m_lineNumber, m_methodInfo);
}
Mci::~Mci()
{
    if (!GetSink())
        return;

    GetSink()->OnLeave(m_filename, m_lineNumber, m_methodInfo);
}
IMciEvents * & Mci::GetSink() 
{ 
    static IMciEvents * pSink = 0; 
    return pSink; 
}
void Mci::Register(IMciEvents * pSink) 
{ 
    GetSink() = pSink; 
}
string Mci::GetLine(const string & filename, int lineNumber)
{
    ifstream f;
    f.open(filename.c_str());
    const int BUFF_SIZE = 1024;
    char buff[BUFF_SIZE];
    for (int i = 0; i < lineNumber; i++)
        f.getline(buff, BUFF_SIZE);
    return string(buff);
}

IMciEvents. This interface (abstract class) should be implemented by some object and registered with the Mci class by calling the static Mci::Register() method; see Listing Two. There is nothing much to say about this interface, except that it provides an empty implementation for the events in case some sink doesn't care about one of the events. If the events were declared pure virtual, the implementing sink is compelled to implement all the events, even if it is only interested in the OnLeave event. This is not a big deal for an interface with two methods, but I call it consideration and putting the client first. You may also notice that the return type is void since Mci doesn't care what the sink does with the information it sends. The <string> header is included, although a forward declaration would have been good enough. Unfortunately, it is forbidden by the Standard to add declarations or definitions to namespace std (to let vendors add their own extensions without collisions with user's code).

Listing Two

#ifndef MCI_EVENTS_H
#define MCI_EVENTS_H

#include <string>

struct MethodInfo;
struct IMciEvents
{
    virtual void OnEnter(const std::string & filename, 
                                        int line, const MethodInfo & mi) {}
    virtual void OnLeave(const std::string & filename, 
                                        int line, const MethodInfo & mi) {}
};
#endif

MethodAnalyzer. This class is responsible for analyzing the current method and populating a MethodInfo struct. Example 3 contains a censored definition of the class. MethodAnalyzer exposes a single static method Analyze(). This method accepts as input a string that contains the text line from the source where the method was declared. The important thing about the Analyze method is that it is called dynamically every time a method is entered by the code, even if the same method is called lots of times. It could be wasteful if the analysis results were always the same for each method. In this case, some sort of caching per method would be helpful. However, it is likely that the analysis may also include the values of input/output arguments, and the return value of the method in the future. Clearly, this is a classic time/space trade-off.

Example 3: Class MethodAnalyzer.

class MethodAnalyzer  
{
public:
    static MethodInfo Analyze(std::string line);
private:
    ...
};

Automating MCI

Putting an Mci object at the beginning of each method in your code is a tedious task, and if you want to use it on a large existing project, it becomes daunting. To remedy this and cater to the natural programmer's laziness, I present some automation options. The objective is to have a project where all source files #include <Mci.h> and all methods contain as their first statement the line:

Mci m(__FILE__, __LINE__, "Method(ArgType1 arg1, ArgType2 arg2...");

To achieve this objective automatically, I came up with the following algorithm:

  1. Identify all the project source files.
  2. #include <Mci.h> in every source file.
  3. Scan each source file.
  4. Identify every method (or function).
  5. Extract the string that the Mci constructor requires as a third parameter.
  6. Place a proper Mci line at the beginning of the method.

This automation procedure can be done offline in any language. I use Python, which is great in general and superb for such text-processing tasks. The script I wrote is naive and you are encouraged to modify it, or write a completely new automation script. Listing Three contains the InjectMci.py script. I will not delve into all the gory details. The basic idea is to detect lines that contain a method definition (using regular expressions), generate an Mci line, and inject it in the proper place. I put in a moderate amount of flexibility, such as working with several brace styles and whitespace filtering.

Listing Three

#!/usr/local/bin/python
import os, sys, glob, re

index = 0
text  = ''

def InjectMci(text, selective):
   if selective and text.find('INJECT_MCI') == -1:
      return text
   index = 0
   text = InjectMciHeader(text)
   text = InjectMciObjects(text, selective)
   return text

def InjectMciHeader(text):
   if text.find('#include "Mci.h"') != -1:
      return text

   lines = text.split('\n')

   index = 0
   # find index of last line that contains #include (0 if no #include
   is found)
   for i in range(len(lines)-1, 0, -1):
      if lines[i].find('#include') != -1:
         index = i+1
         break

   text = '\n'.join(lines[:index])
   text += '\n#include "Mci.h"\n'
   text += '\n'.join(lines[index:])

   return text

def GetMciLine(line):
   mci_mask = '\tMci m(__FILE__, __LINE__, "%s");'
   line = line.replace('{', ' ').strip()
   return mci_mask % line

def InjectMciObject(base, index, lines, new_lines, selective):
   line = lines[index]
   if selective:
      if lines[index+base+1].find('INJECT_MCI') != -1:
         if base == 1:
            new_lines.append(lines[index+1])
         new_lines.append(GetMciLine(line))
         index += base+2; # skip the INJECT_MCI line
      else:
         index += 1
   else:
      if base == 1:
         new_lines.append(lines[index+1])
      new_lines.append(GetMciLine(line))

      index += base+1;
   return index

def InjectMciObjects(text, selective):
   method_re = r'[ \t]*.+[ \t]+.+::.+\(.*\)[ \t]*'
   open_par_re = r'[ \t]*{[ \t]*'
   p1 = re.compile('%s$' % method_re)
   p2 = re.compile('%s$' % open_par_re)
   p3 = re.compile('%s%s$' % (method_re, open_par_re))
   lines = text.split('\n')

   new_lines = []

   index = 0
   while index < len(lines)-2:
      line = lines[index]
      new_lines.append(line)
      if p1.match(line) and p2.match(lines[index+1]):
         index = InjectMciObject(1, index, lines, new_lines, selective)
      elif p3.match(line):
         index = InjectMciObject(0, index, lines, new_lines, selective)
      else:
         index += 1

   return '\n'.join(new_lines)

if __name__ == "__main__":
   selective = len(sys.argv) > 1 and sys.argv[1] == 'selective'
   cpp_fi les = glob.glob('*.cpp')
   for f in cpp_files:
      print '-'*20
      text = open(f).read()
      text = InjectMci(text, selective)
      print text
      open(f, 'w').write(text)


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.