Home > Articles > Programming

This chapter is from the book

This chapter is from the book

Using Better Tracing to Understand the Doc/View Architecture

This article was contributed by Mark Messer.

Figure 3.2Figure 3.2

Purpose

One of the hard things about learning the Document/View architecture is that it is full of marvelous functions, but nobody tells you which function gets called when. You are supposed to override functions when you want to add functionality, but it is not clear which function is the right one.

One way to find out which function you want to override is to override each function you want to know about and put a TRACE statement in it. Now each time one of your functions is called, a line of text appears in the Debug window. Start the program; see what happens. Click the mouse; see what happens.

This is fine as far as it goes. But to understand the Document/View architecture, you want to see a lot of functions. The Debug window quickly turns into a mess. You don't learn anything. It would help if the Debug window was more readable.

The CIndentedTrace class helps with this. You can use it to make the Debug window look more like C++ code. By adding a line of code to a function like the CMyClass member function MyFunc(), CIndentedTrace prints

CMyClass::MyFunc() {

when it is called. When the function exits, CIndentedTrace prints

}

Functions called by MyFunc() can be treated the same way, except that their text will be indented. If MyFunc() calls SubFunc(), the Debug window looks like this

CMyClass::MyFunc() {
 CMyClass::SubFunc() {
 }
}

CIndentedTrace can also be used to add lines of text that look like C++ comments. For example,

CMyClass::MyFunc() {
 // This explains something that goes on inside MyFunc().
}

CIndentedTrace contains other utility functions for such things as hex dumps of strings and displaying text strings for GetLastError() messages.

TRACE is a macro whose behavior depends on whether _DEBUG is defined. When compiled with a debug configuration, TRACE does its job. When compiled with a release configuration, it disappears. The CIndentedTrace header file contains macros to do the same thing. If you use these macros instead of calling CIndentedTrace yourself, CIndentedTrace will only be called in debug versions of your program.

Running the Demos

You may want to try out the demos before seeing how they work.

The TraceEasy demo shows an easy example of using the CIndentedTrace class without macros. It is for understanding the class.

The TraceMacro demo is an example of how to use all the features of the CIndentedTrace class with macros.

The TraceSDI and TraceMDI demos use CIndentedTrace to make the inner workings of the single and multiple document interface visible.

All demos work only from the development environment with debugging active. No compiled code has been included.

To run the demos:

  1. Download and unzip them. Put each one in a separate directory. Each demo should have some source files and three or four files in a res subdirectory.

  2. Make sure tracing is enabled. This is done with the Tracer utility found in the VC++ tools (Task bar, Start button, Programs, Microsoft Visual C++, Microsoft Visual C++ Tools, Tracer). The Enable Tracing check box should be checked. You will probably not want any of the other check boxes checked under most circumstances.

  3. Use the Visual C++ development environment to open the workspace file (the dsw file). In most cases, this can be done by double-clicking on the file in Windows Explorer.

  4. Make sure a debug configuration is selected. Check it on the Build menu, Set Active Configuration option.

  5. Compile the demo application. On the Build menu, choose the Build option.

  6. If you have just finished compiling, the output window is probably open and displaying 0 error(s), 0 warning(s). If not, on the View menu, select the Output option.

  7. There is a row of tabs along the bottom of the Output window that say Build, Debug, and so on. When the program runs, TRACE output and other messages go in the Debug window.

  8. Run the demo from the development environment. On the Build menu, select the Start Debug option, and the Go suboption.

  9. At any time while the program is running or after it has stopped, switch back to the development environment and look in the Debug window.

The SDI and MDI demos trace MFC code through a single document interface application and a multiple document interface application. In each, a vanilla application was created with the App Wizard. The Class Wizard was used to override many functions. The IT_IT() macro was added to each override. This macro uses CIndentedTrace to produce formatted TRACE output.

Some functions were not overridden because they produce too much output. For example, the mouse move message handler produces several messages every time the mouse is touched. The screen drawing functions and idle message handlers are also left out. It is good to trace these to see what they do, even if you don't leave macros in them more than long enough to find out.

Tip: Stepping through the MFC source code is another good way to learn about MFC. Add a breakpoint to the function you want to inspect, run the program, and step into the MFC source code. This works only in MFC code. Microsoft does not supply source code for the C runtime library, SDK functions, or other code. Darn it.

If you do this, be careful about breakpoints in window activation handlers or screen drawing functions. When you are done stepping through such a function, you might continue running the program. The debugger activates the program widows and redraws them. This calls the message handler you just finished stepping through, triggers its breakpoint, and shows you the code you just tried to get out of. To get out of this loop, you must turn off the breakpoint.

Using CIndentedTrace in Your Programs

  1. Before beginning to deal with a program, tracing must be enabled. This is done with the Tracer utility found in the VC++ tools (Task bar, Start button, Programs, Microsoft Visual C++, Microsoft Visual C++ Tools, Tracer). The Enable Tracing check box should be checked. You will probably not want any of the other check boxes checked under most circumstances.

  2. Make sure you are using a debug configuration. Open your project with Visual Studio. On the Build menu, choose the Set Active Configuration option. Choose the debug configuration. Recompile if needed.

  3. Before the class or macros can be used, the IndentedTrace.cpp file must be added to the project (Project, Add to Project, Files...). Just having the file present in the project directory is not enough. The compiler will not compile a source file and the linker will not use the object file unless the source file is part of the project. If the linker doesn't find the CIntendedTrace functions referenced in the code, it will complain with LNK2001 errors.

    The IndentedTrace.h file can be added to the project or not. If not, CIndentedTrace will not appear in the Visual Studio Class View window and IndentedTrace.h will be listed as an external dependency instead of a header file in the File view.

  4. 4Each project cpp file where CIndentedTrace functions or macros are to be used must reference IndentedTrace.h. This can be done by adding

    #include "IndentedTrace.h"

    near the top of the cpp file or to a header file that the cpp file includes. Perhaps the best way is to just add it once to stdafx.h.

  5. In each function you want to trace, add an IT_IT() macro. It should be the first line so that any code called by the function is properly included inside the function's braces. The IT_IT() macro takes a string argument. The string usually contains the function's class and name. The class should be added because the output window may contain output from many different classes. But you may use any text you want to appear in the output window.

    Within a function, an IT_IT() macro must appear before any other IT_ macro can be used. Only one IT_IT() macro can be used in any function.

  6. To add a comment to the output window, add an IT_COMMENT() macro somewhere after the IT_IT().

    Variables can be displayed with a format like TRACE or printf(). IT_COMMENT1() takes one variable. For two or three variables, use IT_COMMENT2() and IT_COMMENT3().

The code you write might look like this:

#include "IndentedTrace.h" // This may go in stdafx.h

CMyClass::MyFunc( int iArg1, double dArg2 )
{
 IT_IT( "CMyClass::MyFunc()" );
 SubFunc( iArg1 );
 IT_COMMENT( "This explains something that goes on inside MyFunc()." );
 // ...
}

CMyClass::SubFunc( int iArg1 )
{
 IT_IT( "CMyClass:: SubFunc()" );
 IT_COMMENT1( "The value of iArg1 is %d", iArg1 );
 ...
}

How CIndentedTrace Works

The TraceEasy demo shows how CIndentedTrace works. Only a few CIndentedTrace features are used. No macros are used to make it easy to follow when stepping into CIndentedTrace code.

Look in CTRaceEasyView to find code that uses CIndentedTrace. In OnLButtonUp(), try putting a breakpoint at the CIndentedTrace variable declared. Step into the CIndentedTrace constructor and destructor. To see the destructor, wait until the cursor reaches the closing brace of OnLButtonUp(), and step into the brace.

When a local variable of type CIndentedTrace is created, the CIndentedTrace constructor is called immediately, and the CIndentedTrace destructor is called at the end of the function body when local variables go out of scope. The most important things the constructor does are print its argument and a "{" at the current indent level and increment the indent level. The most important things the destructor does are decrement the indent level and print a "}".

Note that if the CIndentedTrace variable is declared on the first line of a function, it will be constructed before any other local variables. Its destructor will be called last. This is important.

CIndentedTrace keeps track of the indent level with the static member variable ms_iTraceDepth. Because ms_iTraceDepth is static, all CIndentedTrace objects must share a single copy of it. This makes it an ideal way for CIndentedTrace objects to share information about the current indent level. Each time a new CIndentedTrace object is created, ms_iTraceDepth is incremented. Each time one is destroyed, ms_iTraceDepth is decremented.

Each CIndentedTrace object keeps track of the indent level it was created at with m_nLocalTraceDepth. Since this member variable is not static, no other CIndentedTrace object can touch it. It remains fixed for the life of the object that created it.

Other CIndentedTrace functions, such as Comment(), can only be called in functions that have defined a CIndentedTrace variable. This is obvious in TraceEasy, but when macros are used, the variable definition is hidden in a macro.

Comment() just prints "// " and its argument at the local indent level.

The TraceMacro demo shows how to use most CIndentedTrace features, including some explained in the "For More Advanced Users" section that follows. TraceMacro is a little more complex, but it mostly does the same things as TraceEasy.

One difference is that TraceMacro uses macros for all CIndentedTrace variable definitions and function calls. The macros are defined at the end of the IndentedTrace header file.

The IT_IT macro creates a local variable with the unlikely name of _IT_vnirrrdaewu in debug compiles. This name was chosen because, in most programs, it will not already be used by another variable. _IT_vnirrrdaewu stands for "Variable Name I Really, Really, Really Doubt Anyone Else Will Use." If you do not share my doubts, please feel free to rewrite the macros with a name based on a GUID.

Most of the other macros call a CIndentedTrace function. They require that the variable _IT_vnirrrdaewu be defined. This means they can only be used after IT_IT() or IT_EMBEDDED_IT has been used. IT_IT() is intended for the usual local variable case. IT_EMBEDDED_IT is intended for a special case where CIndentedTrace must be a class member variable.

For More Advanced Users

A constructor with an initialization list may need to be handled a little differently. The initialization list is executed before entering the constructor body. An IT_IT() macro in the constructor body makes it look like the initialization list is not part of the constructor. The thing to do is put CIndentedTrace in the initialization list too. This means it has to be a member variable of the class.

Recall that the order of the constructors in an initialization list is set by the order that member variables are declared in the header, not the order in the initialization list. If this is not familiar, see Effective C++ by Scott Meyer. In any case, the CIndentedTrace member variable must be declared first in the class.

CIndentedTrace prints a "{" and indents when constructed, and unindents and prints a "}" when destroyed. This means there will be a "{" when the class is constructed and a "}" when it is destroyed. If you want the class constructor and destructor each bracketed with { and }, you will have to call Entry() and Exit() to put them there yourself. A couple of Comments() may be useful as well.

Macros can be used for all of this. The IT_EMBEDDED_IT macro creates an uninitialized CIndentedTrace member variable named _IT_vnirrrdaewu. This member variable should be initialized in the constructor initialization list with IT_EMBEDDED_INIT(). You will probably want to add IT_EXIT to the constructor body and IT_ENTRY() to the destructor body. You may want to add IT_COMMENT() as well.

Example header file:

class CMyClass
{
 IT_EMBEDDED_IT; // Must come before other member variables.
 CEmbeddedClass m_EC;

 CMyClass();
 ~CMyClass();
 // ...
}

Example cpp file:

CMyClass::CMyClass()
 : IT_EMBEDDED_INIT("CMyClass::CMyClass - "
          "beginning of constr init list" ),
          m_EC( int iSomeArg )
{
 IT_COMMENT( "CMyClass::CMyClass - beginning of c'tor body " );
 // Other initialization
 IT_EXIT();
}

CMyClass::~CMyClass()
{
 IT_ENTRY("CMyClass::~CMyClass - beginning of destructor body ")
 // Other destruction
 IT_COMMENT( "CMyClass::~CMyClass - End of destructor body");
 // m_EC will be destroyed after the destructor body is done.
}

Note that if the IT_IT() macro is used in a member class, a member variable and a local variable with the same name have been declared. This is okay. The local variable hides the member variable. This means that if you add IT_COMMENT() to the function, it will invoke the local CIndentedTrace and print at the local function's indent level.

If you have any doubts about which instance of CIndentedTrace produces what output, add IT_ENABLE_SERIAL_NUM(bEnable). This calls a static function and so can be used before any CIndentedTrace variables have been defined. The output will identify itself with serial numbers.

CIndentedTrace is not thread safe. It would not be hard to protect the static variables with a critical section. But a multithreaded program may already have thread synchronization code. If CIndentedTrace had a critical section, the program using it could wait on two resources at the same time. This creates a potential for deadlock. The cause of a deadlock can be hard to find, particularly when some of the code is buried in macros. The developer of a multithreaded program should make the decision to add a critical section to CIndentedTrace if he wants it.

In the meantime, CIndentedTrace works after a fashion in multithreaded programs. Sometimes the indentation is messed up. This is not a big problem because indentation is messed up anyway when output from two execution paths are interleaved.

If you are willing to overlook this shortcoming, you can add IT_ENABLE_ THREAD_ID(bEnable). This calls a static function and so can be used before any CIndentedTrace variables have been defined.

The output produced outside the main thread will identify itself with a thread ID. This can be used together with serial numbers.

There is another approach to tracing a multithreaded app. IT_EMBEDDED_IT and IT_EMBEDDED_INIT create a CIndentedTrace member variable in a class. The member variable has the right name, so all the macros can be used in any class function. All the output they produce will have the same indent level, but this can be an advantage in a multithreaded application.

From the Forum

Question: Accessing Doc/View variables (from Salim S):

I am using an SDI application and have three classes (View, Doc, and CTest). In CTest, how can I access variables of View and Doc classes?

Answer (from sounder):

CFrameWnd *pWnd= AfxGetApp()->m_pMainWnd;

// To access the view's members:

CUrView *pView= ((CUrView*) pWnd->GetActiveView());

pView->variable; // you can access public data members or functions.

// To access the document's members:

CUrDoc *pDoc= (CUrDoc*) pWnd->GetActiveDocument();

pDoc->variable;

InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.

Overview


Pearson Education, Inc., 221 River Street, Hoboken, New Jersey 07030, (Pearson) presents this site to provide information about products and services that can be purchased through this site.

This privacy notice provides an overview of our commitment to privacy and describes how we collect, protect, use and share personal information collected through this site. Please note that other Pearson websites and online products and services have their own separate privacy policies.

Collection and Use of Information


To conduct business and deliver products and services, Pearson collects and uses personal information in several ways in connection with this site, including:

Questions and Inquiries

For inquiries and questions, we collect the inquiry or question, together with name, contact details (email address, phone number and mailing address) and any other additional information voluntarily submitted to us through a Contact Us form or an email. We use this information to address the inquiry and respond to the question.

Online Store

For orders and purchases placed through our online store on this site, we collect order details, name, institution name and address (if applicable), email address, phone number, shipping and billing addresses, credit/debit card information, shipping options and any instructions. We use this information to complete transactions, fulfill orders, communicate with individuals placing orders or visiting the online store, and for related purposes.

Surveys

Pearson may offer opportunities to provide feedback or participate in surveys, including surveys evaluating Pearson products, services or sites. Participation is voluntary. Pearson collects information requested in the survey questions and uses the information to evaluate, support, maintain and improve products, services or sites, develop new products and services, conduct educational research and for other purposes specified in the survey.

Contests and Drawings

Occasionally, we may sponsor a contest or drawing. Participation is optional. Pearson collects name, contact information and other information specified on the entry form for the contest or drawing to conduct the contest or drawing. Pearson may collect additional personal information from the winners of a contest or drawing in order to award the prize and for tax reporting purposes, as required by law.

Newsletters

If you have elected to receive email newsletters or promotional mailings and special offers but want to unsubscribe, simply email information@informit.com.

Service Announcements

On rare occasions it is necessary to send out a strictly service related announcement. For instance, if our service is temporarily suspended for maintenance we might send users an email. Generally, users may not opt-out of these communications, though they can deactivate their account information. However, these communications are not promotional in nature.

Customer Service

We communicate with users on a regular basis to provide requested services and in regard to issues relating to their account we reply via email or phone in accordance with the users' wishes when a user submits their information through our Contact Us form.

Other Collection and Use of Information


Application and System Logs

Pearson automatically collects log data to help ensure the delivery, availability and security of this site. Log data may include technical information about how a user or visitor connected to this site, such as browser type, type of computer/device, operating system, internet service provider and IP address. We use this information for support purposes and to monitor the health of the site, identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents and appropriately scale computing resources.

Web Analytics

Pearson may use third party web trend analytical services, including Google Analytics, to collect visitor information, such as IP addresses, browser types, referring pages, pages visited and time spent on a particular site. While these analytical services collect and report information on an anonymous basis, they may use cookies to gather web trend information. The information gathered may enable Pearson (but not the third party web trend services) to link information with application and system log data. Pearson uses this information for system administration and to identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents, appropriately scale computing resources and otherwise support and deliver this site and its services.

Cookies and Related Technologies

This site uses cookies and similar technologies to personalize content, measure traffic patterns, control security, track use and access of information on this site, and provide interest-based messages and advertising. Users can manage and block the use of cookies through their browser. Disabling or blocking certain cookies may limit the functionality of this site.

Do Not Track

This site currently does not respond to Do Not Track signals.

Security


Pearson uses appropriate physical, administrative and technical security measures to protect personal information from unauthorized access, use and disclosure.

Children


This site is not directed to children under the age of 13.

Marketing


Pearson may send or direct marketing communications to users, provided that

  • Pearson will not use personal information collected or processed as a K-12 school service provider for the purpose of directed or targeted advertising.
  • Such marketing is consistent with applicable law and Pearson's legal obligations.
  • Pearson will not knowingly direct or send marketing communications to an individual who has expressed a preference not to receive marketing.
  • Where required by applicable law, express or implied consent to marketing exists and has not been withdrawn.

Pearson may provide personal information to a third party service provider on a restricted basis to provide marketing solely on behalf of Pearson or an affiliate or customer for whom Pearson is a service provider. Marketing preferences may be changed at any time.

Correcting/Updating Personal Information


If a user's personally identifiable information changes (such as your postal address or email address), we provide a way to correct or update that user's personal data provided to us. This can be done on the Account page. If a user no longer desires our service and desires to delete his or her account, please contact us at customer-service@informit.com and we will process the deletion of a user's account.

Choice/Opt-out


Users can always make an informed choice as to whether they should proceed with certain services offered by InformIT. If you choose to remove yourself from our mailing list(s) simply visit the following page and uncheck any communication you no longer want to receive: www.informit.com/u.aspx.

Sale of Personal Information


Pearson does not rent or sell personal information in exchange for any payment of money.

While Pearson does not sell personal information, as defined in Nevada law, Nevada residents may email a request for no sale of their personal information to NevadaDesignatedRequest@pearson.com.

Supplemental Privacy Statement for California Residents


California residents should read our Supplemental privacy statement for California residents in conjunction with this Privacy Notice. The Supplemental privacy statement for California residents explains Pearson's commitment to comply with California law and applies to personal information of California residents collected in connection with this site and the Services.

Sharing and Disclosure


Pearson may disclose personal information, as follows:

  • As required by law.
  • With the consent of the individual (or their parent, if the individual is a minor)
  • In response to a subpoena, court order or legal process, to the extent permitted or required by law
  • To protect the security and safety of individuals, data, assets and systems, consistent with applicable law
  • In connection the sale, joint venture or other transfer of some or all of its company or assets, subject to the provisions of this Privacy Notice
  • To investigate or address actual or suspected fraud or other illegal activities
  • To exercise its legal rights, including enforcement of the Terms of Use for this site or another contract
  • To affiliated Pearson companies and other companies and organizations who perform work for Pearson and are obligated to protect the privacy of personal information consistent with this Privacy Notice
  • To a school, organization, company or government agency, where Pearson collects or processes the personal information in a school setting or on behalf of such organization, company or government agency.

Links


This web site contains links to other sites. Please be aware that we are not responsible for the privacy practices of such other sites. We encourage our users to be aware when they leave our site and to read the privacy statements of each and every web site that collects Personal Information. This privacy statement applies solely to information collected by this web site.

Requests and Contact


Please contact us about this Privacy Notice or if you have any requests or questions relating to the privacy of your personal information.

Changes to this Privacy Notice


We may revise this Privacy Notice through an updated posting. We will identify the effective date of the revision in the posting. Often, updates are made to provide greater clarity or to comply with changes in regulatory requirements. If the updates involve material changes to the collection, protection, use or disclosure of Personal Information, Pearson will provide notice of the change through a conspicuous notice on this site or other appropriate way. Continued use of the site after the effective date of a posted revision evidences acceptance. Please contact us if you have questions or concerns about the Privacy Notice or any objection to any revisions.

Last Update: November 17, 2020