Understanding How OpenDoc "Ticks" Using Trace and Debug Tools

by Robert Tycast

There once was a child who would take apart the family clock during the day and have it put back together and working by the time his parents returned. While this was a bit risky (given that this was Switzerland and knowing the correct time was important), it was an effective way to understand how time pieces work. Well, it must have worked because the child grew up to be a master watchmaker.

OpenDoc is not a watch and its parts are not quite so small. Its workings are intricate, though. The OpenDoc documentation does a good job of describing all of the objects and subsystems that make up OpenDoc. The careful reader will certainly learn about all of the methods of ODPart and ponder many a recipe along the way. But how to pull it all together and make sense of it?

The problem is that text in a document is static, while the interactions among the objects are anything but! The answers to the questions, "What is the order in which my methods are called?" or even, "Is this method going to be called?" are complex. We can say with certainty that SOMINIT will be called first, SOMUNINIT last, and PartDraw when your part becomes visible. Beyond that, it depends on the circumstances of events and the capabilities of the part itself.

So why not learn with dynamic tools? Trace and debug(1) are not only good for finding and fixing problems, they are excellent learning tools as well. This article provides a brief tutorial on using the System Object Model (SOM) trace facility to follow the flow of execution in an OpenDoc part handler. We'll also look at a second approach, adding the debugger tool to allow greater control of the learning experience. We will use the Hello World part found in the OpenDoc Toolkit to demonstrate these techniques.(2)

Tracing OpenDoc Parts
OpenDoc is based on the SOM technology developed by IBM. SOM has a built-in trace facility. Every method invocation produced by the SOM emitter(3) includes a call to SOM_DEBUG. For example, in the Hello World PartDraw method, the following code was produced by SOM: What we are interested in is the call to HelloPartMethodDebug. By default, this macro does not generate any code. But when a global variable named SOM_TraceLevel is initialized, the code generated outputs a line containing the class name, HelloPart, and the method, HelloDraw, to the standard output device.

Because OpenDoc runs in a completely graphical environment, a printf would at best be put into a file(4) and, at worst, would go into the bit-bucket. In any case, we wouldn't get the real-time display that we want. So OpenDoc has a small modification that makes it possible to have our trace and see it too! The -s option to DOCSHELL.EXE tells it to output the messages to PMprintf(5), a tool that displays the lines it receives in a Presentation Manager (PM) window.

Note: Before using DOCSHELL.EXE and OPENDOC.DLL, copy them from the BIN\DEBUG directory to the BIN directory. For more information, see the online book Using the OpenDoc Toolkit, which is created in the OpenDoc folder when you install OpenDoc.

Figure 1 shows a typical trace of an OpenDoc part demonstration. First we added the following line to HELLO.CPP(6): int SOM_TraceLevel = 1; and then rebuilt it with the nmake command. After copying the new HELLO.DLL to the \BIN directory, we ran DOCSHELL with the docshell -s command. An OpenDoc document is opened with the container part as the default root part. Once we embed the Hello World part in the document, the PMprintf window (which appears in the top half of the example screen) comes alive.



''Figure 1. Tracing the Hello World part with PMprintf''

As you can see, HelloSomInit calls Hello World's methods, from the various drawing-related methods down to where the part is completely rendered and events start to drive it a la the HelloHandleEvent method. Hello World is a very simple part - most of the work is actually done by its parent, the container part. If the container part source CNTNRPRT.CPP(7) is rebuilt with tracing turned on, we can see much more of the behind-the-scenes operation of OpenDoc. (You might want to try this as an exercise.)

Stepping Through the Part Code
Sometimes it is most instructive to be in control of the part while it executes, instead of being a passive observer. We can easily arrange this using breakpoints. But it might be difficult to know where to put the breakpoint. We can resolve this dilemma by using a combination of conventional breakpoints piggybacked on the SOMMethodDebug facility described above. Here's how it's done.

First we make a small modification to our part. In addition to initializing the SOM_TraceLevel variable, we define our own version of the SOM_Trace macro(8): This provides a convenient place to put our breakpoint when we are stepping through the part handler code. In this case our version is a "null" routine, but you could add any code you like (such as a PMprintf call).



''Figure 2. Setting a breakpoint for the Hello World part''

Next, use the following command to start OpenDoc using the debugger: ipmd docshell Once the debugger comes up, the easiest way to grab control during the target part handler's execution is to use a load occurrence breakpoint (see Figure 2). In OS/2, part handlers are implemented as dynamic link libraries (DLLs). A load occurrence breakpoint will stop execution of a program at the point at which the specified DLL (in this example, HELLO.DLL) is loaded into memory but before any code within it has been executed. Setting this breakpoint is easy. You can select Load Occurrence from the Breakpoints menu to bring up the dialog box shown in Figure 2.

TIP: If you save restart information when you leave the debugger, the load occurrence breakpoints will be preserved.

Start the program execution with the RUN command, or click on the image of the green circle, and a document window will open. Now is a good time to arrange your screen so that the session control window, source window, stack window, and document window are all tiled (that is, they don't overlap). This is important because we will be stepping through event-driven code. If the windows overlap, then we will continually generate redraws of the document window during the course of operating the debugger. Each step will bring us right back to the Draw method, which will cause a breakpoint, which will cause another redraw, and so on. Try it, and you'll see what I mean.

Bring the DLL into memory by embedding the Hello World part in the document. You can do this by dragging it from a template or by using the Embed menu. Once the DLL is loaded into memory, you can expand its component list in the Debug Session Control window (as shown in Figure 3). Notice that some method names seem to be repeated. For example, the common initialization method is in the list twice, once as HelloCommonInitPart and a second time as HelloPart::CommonInitPart. The latter is an artefact of the SOM bindings and usually not of interest to the programmer (unless you are curious to see how the "innards" of SOM work).



''Figure 3. Expanding the components of Hello World part''

In this demonstration we scroll to the bottom of the components list to find our Trace routine. Double-clicking it brings up the source (see Figure 4). In this case, Trace is on line 83. Double-click on the line number to set the breakpoint.



''Figure 4. Setting a breakpoint on Trace''

With the breakpoint on Trace set, we continue with the execution of our sample part. Now, at each method invocation our breakpoint will be triggered. In Figure 5, we have captured the screen while sitting at a breakpoint shortly after Hello World has rendered itself in the document. Notice that you can read the calling sequence from the stack window. You can see quite nicely how an update event in OpenDoc leads to calling methods on the facet (for example, FacetUpdate and FacetDraw) and ultimately triggers the cascade of methods on the part - from HelloDraw up through HelloSetOrigin.



''Figure 5. Stepping through the execution of the Hello World part''

You might want to try this on the container part (CNTNRPRT.CPP) from which Hello World is inherited. That way you can get a more complete picture of how OpenDoc parts work.

Wrapping It Up
Now that you've had a chance to see how easy it can be to get at the innards or OpenDoc, you're ready to continue on your own. I have included a reading list at the end of this article to help guide you along. By reading and tinkering with the tools described in this article, you should become familiar enough with the workings to become a part maker in no time!