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++

Component Objects & Distributed Computing


October 1996: Component Objects & Distributed Computing

Component Objects & Distributed Computing

SOM classes for user chatting

Jeff Rush

Jeff is a software consultant for AmigaDOS, OS/2, PC-DOS, and embedded systems. He is also the creator of the Echomail conferencing facility used in Fidonet. Jeff can be contacted at [email protected].


As enterprise-wide systems continue to link up with desktop PCs, distributed computing is moving into the mainstream of information technology at an accelerating pace. What makes distributed computing a reality are interoperable, component objects that deal with much of the complexity-naming, address-space conversion, transport protocols, interface description, and so on-inherent in distributed systems.

What makes an object interoperable is its object model-a system-level technology that addresses the problem of tight binary coupling between an application and the objects it relies upon. Among the leading object models are IBM's System Object Model (SOM) and Microsoft's Component Object Model (COM). In this article, I'll use Version 2.1 of IBM's SOM to create desktop objects for the OS/2 Workplace Shell that use Distributed SOM (DSOM) to invoke methods on remote objects-all implemented in C++ using the Direct-to-SOM feature of IBM's VisualAge C++ compiler.

This application (available electronically) uses SOM classes for user chatting between machines. One class resides on a set of desktops as a Workplace object, where each user types to the other room members; and the other acts behind the scenes as traffic controller by distributing keystrokes to all chatroom participants. In the process, I'll examine how to write Workplace SOM classes in native C++, interact with remote objects, and use the VisualAge C++ OpenClass library for the graphic elements of the Workplace SOM class. The code I present runs on any PC running OS/2 Warp. Without the Workgroup Enabler for OS/2 (downloadable on IBM's SOMware), however, you'll only be able to chat between two instances on one PC, not across a network. Warp ships with the Workstation Enabler, which allows different processes in the same machine to communicate using shared memory to replace the LAN. The distinction is transparent to the source code.

Operation Overview

When the program is installed, you see two icons representing instances of the TChat class on your desktop. Since the script installs two, you can chat from one window to the other if you don't have the necessary network environment. Each TChat object is defined by the name of the chatroom it should connect to and the user alias to use while in that room. In the code, nothing is done with the user alias, although you could easily add popup lists of participants, iconic faces of members, and the like.

When you open a TChat object, you see a horizontally split window that lets you type in the lower pane, with incoming keystrokes appearing in the top pane. The status line at the bottom indicates the state of the connection to the chatroom object. Figure 1 shows two open TChat objects on a desktop linked to a common chatroom.

To communicate with other users, your keystrokes must be distributed to them. This is the role of the Chatroom object. For each room, there is a Chatroom object somewhere on the network. It acts as a server by receiving keystrokes from members and redistributing them back out again.

As each TChat object is initialized, it locates and registers with the appropriate Chatroom object. When you type a keystroke into the lower pane of a TChat object, it gets sent to the Chatroom via a remote method call. The Chatroom object iterates over its participants and invokes a remote method in each TChat object, which delivers the keystroke into the top pane of any open views of that instance. In addition, if a TChat object receives keystrokes when it has no views open, the TChat object changes its desktop icon to another shape to alert the user. Figure 2 is an overview of how the classes interact.

TChat Class Implementation

The three base classes of the Workplace Shell (WPS) are: WPAbstract (for those objects that do not correspond to a file or directory), WPFileSystem (for those that do), and WPTransient (for objects that don't survive a reboot). I chose WPAbstract because I want TChat objects to stick around. Since they don't act like containers/folders, you'll want to store their room and user-name properties in the system .INI files where WPAbstract objects live. WPAbstract objects have no default view other than what you give them.

TChat is implemented using Direct-to-SOM (DTS), a C++ compiler feature that lets you define classes as C++ classes (with a few additional pragmas) and have them compiled into SOM classes. The class declarations in file TChat.hh are shown in Listing One. DTS uses file extensions of .hh to represent DTS SOM classes. Files of that extension can be run through the compiler by themselves to generate .idl Interface Definition Language (IDL) files. (IDL is the portable, class-description language, defined in the CORBA standard, on which SOM is based.) Once in IDL format, class declarations can be processed into a variety of forms, including code stubs and linker .def files.

SOM maintains two databases of information about its classes on each machine-the Interface Repository and the Implementation Repository. The Interface Repository contains the calling sequences and parameter declarations of class methods and is stored in SOM.IR files along the path defined by the SOM.IR environment variable. To store a class into the Interface Repository, you invoke the SOM compiler (sc.exe) with the name of an .idl file. The makefile takes care of this.

The Implementation Repository advertises where SOM classes are available to execute. These two databases must either be created and copied to each machine, or located on a shared drive that all machines can access.

In Listing One, TChat is actually represented as two C++ classes, TChat and M_TChat. The latter is the metaclass of TChat and is required with WPS classes but not with all SOM classes. Classes are true run-time objects under SOM (they are compile-time entities in C++). Methods that are global to all instances of TChat (class methods) are implemented in M_TChat. These include accessors of class static data, such as what icon to use or the title of the class, as well as a background thread used to communicate with the Chatroom to keep network operations from interfering with the desktop GUI.

In each class declaration, you see a dozen pragma directives specific to SOM. Some of these are obvious: giving the version and name of the class (in case it differs from the C++ name), turning off the mangling of C++ names (so that other languages can access your classes), and specifying the name of the DLL that will represent our class (tchat.dll). At its core, SOM is a binary packaging technology and usually stores its classes as DLLs, although you can create statically linkable SOM classes as well. One important pragma is SOMCallStyle() -in the case of TChat, it is "old IDL". For historic reasons, all WPS classes are old IDL. The other style is new IDL, with the difference being that old IDL does not pass a hidden SOM environment parameter and new IDL does. Throughout this example, I generally ignore any environment parameters, but I still must be consistent in declarations or a process trap will result.

The compiler knows that TChat is a SOM class and not a C++ class because it either inherits from a class rooted at SOMObject, or is declared within the range of a SOMAsDefault(on) pragma. In this case, I have both. Within the body of the declaration there is a SOMAsDefault(pop) in a critical location. If you wish to declare any nested classes or structures, whether they are inside or outside of SOMAsDefault(), the compiler will treat them as SOM or C++ entities. This is important when you want to pass a structure of data to a SOM method but the structure is not a SOM class in itself.

It is also important to note situations where your SOM class needs to contain non-SOM datatypes, as in the TRequest structure (request to the background thread) and BackThread (pointer to the background thread itself) types. IDL requires complete knowledge of all types so that it can store them into the Interface Repository, but this can be awkward under DTS when you want to drop in a few non-SOM instance variables. My solution is to use the SOM type SOMFOREIGN as a placeholder in the IDL files. For example, I use the SOMIDLDecl() pragma to tell the compiler that when it emits the BackThread IDL type declaration, it should put out the string typedef SOMFOREIGN BackThread. Although IDL accepts such foreign types, it does require you to provide the "implementation context" within which the type is valid, and the length of the datatype. I satisfy this by using the SOMIDLPass() pragma to pass an IDL pragma of #pragma modifier BackThread: impctx = C, length = 0;. It's cryptic, nonobvious, and poorly documented, but it keeps the DTS compiler happy.

Method declarations are another case where you control what goes into the IDL output. When you pass a pointer to a datatype (say, a string) under C++, it isn't always obvious whether you are passing data into or out of the function. Does the function receive a string or does it return a string by filling in a passed buffer? Ordinarily, it doesn't matter, but SOM needs to know whether to bundle up the contents of the buffer and send it to the remote method, or whether to receive a string and put it into the local buffer. The const C++ keyword can be used to clarify your intention, but in some cases, you must use the SOMIDLDecl() pragma to pass through the specific IDL statement you want. For example, TChat has a method declared as void queueRequest (TRequest *req), so you tell DTS to use the IDL declaration void queueRequest(in TRequest p__arg1). IDL understands the keywords in, out, and inout.

In the M_TChat class, I maintain items global to all classes, such as a count of TChat instances present in this process space, handles of the two icons I use for TChat on the desktop (one for idle and one for user alert), and a pointer to the single background thread. I provide new methods to access these variables and one (queueRequest) that a TChat instance can use to issue a request to the background thread. I also override eight WPS methods to obtain debugging information and to provide the class-specific icon and name. At the end of the M_TChat class declaration, I add a SOMReleaseOrder pragma to specify the ordering of the new methods. This SOM feature, called Release-to-release binary compatibility (RRBC), lets you add new methods over the life of a class. Any existing clients won't need to recompile if they don't need the new methods. The ordering pragma ensures that the additions won't rearrange the dispatch tables and render the new version binary-incompatible with the old.

In the TChat non-metaclass declaration, you have the same set of pragmas as before, but also a SOMMetaClass pragma to point back to the metaclass M_TChat. For variables, you have the two strings of the room name and user name, the state of the link to the chatroom, a pointer to the remote-chatroom object, and a pointer to the metaclass (a convenience only). Since network communication to the chatroom takes time, a TChat object can be in one of four states: DISCONNECTED, CONNECTED, CONNECTING, and DISCONNECTING (the last two being transient states). The status line at the bottom of any open views indicates these states to the user.

The displayText() method is invoked by a chatroom object to put text into the top pane of any open views. The emitText() method is invoked by the TChat view object when the user types into the lower pane to send text to the Chatroom object.

The TChat object locates or registers with the Chatroom object when the TChat object is created or the WPS boots, or the user issues a WinSetObjectData() call to change the settings of a TChat object. I could have added a Settings notebook as well, but I left it out for simplicity.

The lifecycle of a WPS object is a bit complex. When an object is created, it goes through several steps of initialization by the WPS, ending with a call to the wpObjectReady() method (which I override). Unlike most C++ objects, WPS objects are not fully created at the time of invocation of their constructor. When wpObjectReady() is invoked, I inform M_TChat so it can track TChat instances and create the background thread (if this is the first such instance). It will also queue a "join" request to the background thread. You queue a request instead of issuing a method call to the chatroom object to prevent the desktop from freezing while the transaction takes place. If the chatroom is unavailable, you don't want the desktop to freeze for the several seconds required to timeout the request.

Something similar happens when a TChat object is deleted. There is no reverse equivalent to the wpObjectReady() method, so you override the wpDelete() method to queue a "quit" request to the background thread. Since this can take time as well, and I don't want to confuse the user by leaving an icon on the desktop, even briefly, I hide the TChat object. Later, when the background thread completes the disconnection process, it performs the real deletion.

Between creation and deletion, a TChat object behaves like a normal icon, with a single view you can open. That view is implemented as a split canvas to represent the two panes, using IBM's OpenClass C++ library. When a view is created, it receives a pointer to the TChat SOM object it represents, so that it can issue method calls back to TChat to send keystrokes to the Chatroom object. Like other WPS objects, TChat keeps a list of its open views and, when its displayText() method is invoked by the Chatroom object, it invokes each view's displayText() method to cause the received text to appear in the top pane of each.

Chatroom Class Implementation

DSOM runs a daemon process called "SOMDD.EXE" that listens for invocations of classes defined in the Implementation Repository and spawns the appropriate server processes, one of which is the Chatroom class.

The Chatroom class is a DTS SOM class derived from the SOMDServer class. Listing Two shows the declarations for the Chatroom class, which has no dependencies upon the WPS. Each such instance of a Chatroom is a separate process and represents a specific room. There can be many such rooms on a network and even on the same machine.

Being derived from SOMDServer means that as soon as SOMDD spawns a server process, it instantiates an instance of Chatroom within it to represent that server. When remote objects "talk" to the server, they are, in fact, invoking methods enherited from SOMDServer and any you choose to add in Chatroom.

Listing Two doesn't define a metaclass, but instead uses SOMMSingleInstance, ensuring there can never be more than one in any process. The Chatroom class is also defined using the new IDL call style, meaning that there is an invisible SOM environment parameter being passed to all methods. I don't do anything with it, but you could add SOM exceptions and have them propagate back to the caller. SOM exceptions are not asynchronous and have nothing to do with C++ exceptions or OS/2 process exceptions.

The Chatroom class is packaged just like TChat but in chatroom.dll instead. How does SOMDD.EXE turn this DLL into a new process? Where is the .exe file for the process? Well, it is defined in the Implementation Repository. Each possible "server of objects" must have an entry in the Implementation Repository defining, among other things, a textual name or alias, the name of the .exe to invoke to start it, and the name of a class derived from SOMDServer to instantiate within it. Each entry also gets assigned an implementation id, which is a long, unique identifier used by SOM to track server implementations.

I use the server name as the name of the chatroom. To create more chatrooms, simply define more servers, all with the same .exe and class name. Whenever a client tells SOMDD.EXE that it wants to talk to a server with a specific name, SOMDD.EXE will look up that name and spawn the .exe, passing it the name of the implementation to set up within it.

The class name in the repository is obviously class Chatroom and the .exe name is somdsvr.exe. The .exe is a generic program that comes with SOM to start new servers.

I define three methods for the Chatroom class: joinSession(), registers a remote TChat object in the room; quitSession(), unregisters that TChat object; and postSession(), broadcasts a text string to all participants.

Conclusion

SOM is a powerful technology, but has many dark corners that are not well documented. Consequently, I've started a Web page devoted to SOM at http://rampages .onramp.net/~jrush/som where we can explore together.

References

SOMobjects: A Practical Introduction to SOM and DSOM. IBM Redbook GG24-435-7.

SOMobjects: Management Utilities for Distributed SOM. IBM Redbook GG24-447-9.

Auerbach, Alan. "Writing Workplace Shell Objects (Using the IBM Open Class User Interface Library," IBM Developer Connection Newsletter #9.

IBM SOMware. http://www.software .ibm.com/objects/somobjects/

Figure 1: Two open TChat objects talking.

Figure 2: Overview of class interaction.

Listing One


// File: tchat.hh

#ifndef __TCHAT_HH__
#define __TCHAT_HH__

#include <os2.h>
#define INCL_WPCLASS
#include <pmwp.h>

class M_WPAbstract;
#pragma SOMClassName(M_WPAbstract,   "M_WPAbstract")

class M_WPObject;
#pragma SOMClassName(M_WPObject,     "M_WPObject")

#include <som.hh>
#include <WPObject.hh>
#include <WPAbs.hh>

/**********************************************************************
 *           Distributed-Chatroom Desktop Interface Class
 * This class represents an icon on the OS/2 desktop that listens to
 * keystrokes posted into a remote 'chatroom' object.  When opened,
 * the keystrokes are displayed and any entered by the user are sent
 * to the 'chatroom' for broadcasting to others.
 * If a keystroke comes in when the icon is closed, it changes shape
 * to alert you that people are talking.
 * The possibilities for expansion are unlimited; sound effects,
 * graphic 'person speaking' icons, per-line input buffering, etc.
 * NOTE: A similar class could be defined that did nothing but log
 * the conversation taking place, without any Workplace Shell work at all.
 **********************************************************************/
class BackThread;
class TRequest;
class Chatroom;

/**************************************************
 * Metaclass (NOT Parent) of TChat Abstract Object
 *  (The Real Runtime Class Itself ala Smalltalk)
 **************************************************/
#pragma SOMAsDefault(on)
class M_TChat : public M_WPAbstract { // Declares Class Methods
    #pragma SOMClassVersion(*, 1, 1)     // This is Version 1.1
    #pragma SOMClassName(*, "M_TChat")   // My Class Name is...
    #pragma SOMNoMangling(*)             // Don't C++ Mangle My Symbols
    #pragma SOMCallStyle(OIDL)           // WPS Classes are Old IDL Style
    #pragma SOMIDLPass(*, "Implementation-Begin", \
                          "dllname = \"tchat.dll\";")
    #pragma SOMAsDefault(pop)
    #pragma SOMIDLPass(*, "Begin", "#include \"wpabs.idl\"")
    typedef M_WPAbstract Parent;         // Convenient Ref in Code
    #pragma SOMIDLDecl(BackThread, "typedef SOMFOREIGN BackThread")
    #pragma SOMIDLPass(*, "Begin", \
"#pragma modifier BackThread: impctx = C, length = 0;")
    #pragma SOMIDLDecl(TRequest, "typedef SOMFOREIGN TRequest")

    #pragma SOMIDLPass(*, "Begin", \
"#pragma modifier TRequest: impctx = C, length = 0;")
            HPOINTER    hAttn;     // PM Handle of Need-Attention Icon
            HPOINTER    hNorm;     // PM Handle of Normal (Non-Attn) Icon
            ULONG       instances; // Count of Instances of This Class
            BackThread *requestThread; // Ptr to Background Thread Object
public:
                       M_TChat();
    virtual           ~M_TChat();
    virtual HPOINTER   queryNormalIcon();
    virtual HPOINTER   queryAttnIcon();
    virtual void       adjCount(int delta);
    virtual void       queueRequest(TRequest *req);
    #pragma SOMIDLDecl(queueRequest, \
                       "void queueRequest(in TRequest p__arg1)")

    // Overridden Workplace Shell Class Methods
    // (We Override More than Necessary to Monitor Things)
    virtual void       wpclsInitData();
    virtual void       wpclsUnInitData();
    virtual PSZ        wpclsQueryTitle();
    virtual ULONG      wpclsQueryDefaultView();
    virtual ULONG      wpclsQueryStyle();
    virtual BOOL       wpclsCreateDefaultTemplates(WPObject *aFolder);
    virtual ULONG      wpclsQueryIconData(PICONINFO pIconInfo);

    // Define Release Order for Introduced Methods (But Not Overrides)
    #pragma SOMReleaseOrder ( \
                             "queryNormalIcon", \
                             "queryAttnIcon",   \
                             "adjCount",        \
                             "queueRequest")
};
/***** Class of TChat Abstract Object *****/
#pragma SOMAsDefault(on)
class TChat : public WPAbstract { // Declares Instance Methods
    #pragma SOMClassVersion(*, 1, 1)     // This is Version 1.1
    #pragma SOMClassName(*, "TChat")     // My Class Name is...
    #pragma SOMNoMangling(*)             // Don't C++ Mangle My Symbols
    #pragma SOMCallStyle(OIDL)           // WPS Classes are Old IDL Style
    #pragma SOMIDLPass(*, "Implementation-Begin", \
                          "dllname = \"tchat.dll\";")
    #pragma SOMMetaClass(*, "M_TChat")   // My Metaclass is...
    #pragma SOMAsDefault(pop)

    typedef WPAbstract Parent;           // Convenient Ref in Code
            M_TChat   *myClass;
            char       myUserName[80];
            char       myRoomName[80];
            short      connectState;
            Chatroom  *roomObj;
    #pragma SOMIDLDecl(Chatroom, "typedef SOMFOREIGN Chatroom")
    #pragma SOMIDLPass(*, "Begin", "#pragma modifier Chatroom: impctx = C,
                                                                  length = 0;") public: // Coordinate User-Assigned PM View IDs in a Central Place (Here)
            enum DesktopViews { // We Only Have One View Today
                       VIEWID_CHATVIEW = WPMENUID_USER
            };
            enum LinkStatus {
                       DISCONNECTED,  // Not Registered with a Chatroom
                       CONNECTING,    // Try to Get Registered...
                       CONNECTED,     // Registered with a Chatroom
                       DISCONNECTING  // Try to Get Unregistered...
            };
public:
                       TChat();
    virtual           ~TChat();
    // We declare the displayText() method as 'oneway' just to show it
    // can be done.  It means the Chatroom server just fires-and-forgets
    // with no hope of retry-upon-error, but it also runs faster.
    virtual void       displayText(const char *str);
    #pragma SOMIDLDecl(displayText, \
                      "oneway void displayText(in string p__arg1)")
    virtual boolean    emitText(const char *str);
    virtual const char *roomName() const { return myRoomName;  }
    virtual void       setRoomName(const char *name);

    virtual const char *userName() const { return myUserName; }
    virtual void       setUserName(const char *name);
    virtual void       reconnect(const char *room, const char *user);
    virtual Chatroom  *roomObject() const            { return roomObj; }
    virtual void       setRoomObject(Chatroom *room) { roomObj = room; }
    virtual ULONG      reallyDelete();
    virtual void       setState(short newstate);
    virtual short      state() const { return connectState; }

    // Overridden Workplace Shell Instance Methods
    // (We Override More than Necessary to Monitor Things)

    virtual BOOL       wpSetup(PSZ pszSetupString);
    virtual BOOL       wpModifyPopupMenu(HWND hwndMenu, HWND hwndCnr,
                                         ULONG iPosition);
    virtual HWND       wpOpen(HWND hwndCnr, ULONG ulView, ULONG param);
    virtual BOOL       wpMenuItemSelected(HWND hwndFrame, ULONG ulMenuId);
    virtual BOOL       wpSaveState();
    virtual BOOL       wpRestoreState(ULONG ulReserved);

    virtual void       wpObjectReady(ULONG ulCode, WPObject* refObject);
    virtual BOOL       wpSetupOnce(PSZ pszSetupString);
    virtual ULONG      wpDelete(ULONG fConfirmations);

    // Define Release Order for Introduced Methods (But Not Overrides)
    #pragma SOMReleaseOrder ( \
                             "displayText",   \
                             "emitText",      \
                             "roomName",      \
                             "userName",      \
                             "setState",      \
                             "state",         \                                                  "roomObject",    \
                             "setRoomObject", \
                             "reallyDelete",  \
                             "setUserName",   \
                             "setRoomName",   \
                             "reconnect")
};
#endif

Listing Two


// File: chatroom.hh

#ifndef __CHATROOM_HH__
#define __CHATROOM_HH__

#include <somd.hh>     // DSOM Declarations
#include <snglicls.hh> // SingleInstance SOM Metaclass

/**********************************************************************
 *                    Chatroom DSOM Server Class
 * A single instance of the Chatroom class runs in a separate process
 * and serves DSOM requests from afar.  Its purpose is to allow clients
 * to register and then to send them echos of any keystrokes that other
 * clients send to the Chatroom.
 * There are many areas of improvement, for tracking of clients, for
 * authentication, etc.  A chatroom-to-chatroom linking might be fun
 * to, for distributing the broadcast load over slow SLIP/PPP lines.
 * There is NOTHING in this class that has to do with the Workplace
 * Shell.  It is a pure DSOM server.
 **********************************************************************/

#pragma SOMAsDefault(on)
class Chatroom : public SOMDServer {
    #pragma SOMClassVersion(*, 1, 1)        // This is Version 1.1
    #pragma SOMClassName(*, "Chatroom")     // My Class Name is ...
    #pragma SOMNoMangling(*)                // Don't C++ Mangle My Symbols
    #pragma SOMCallStyle(IDL)               // Class is in New IDL Style
    #pragma SOMIDLPass(*, "Implementation-Begin", \
                          "dllname = \"chatroom.dll\";")
    #pragma SOMMetaClass(*, "SOMMSingleInstance")  // My Metaclass is...
    #pragma SOMAsDefault(pop)
public:
                       Chatroom();
    virtual           ~Chatroom();

    // Strictly speaking, the SOMIDLDecl #pragmas are not needed for
    // these methods since the Direct-to-SOM process correctly figures
    // out my intention (direction of parameter data flow).  I put them
    // here to show how it would be done if you needed to alter them.

            boolean    joinSession(SOMObject *obj, const char *userAlias);
    #pragma SOMIDLDecl(joinSession, \
        "boolean joinSession(in SOMObject p__arg1, in string p__arg2)")
             boolean    quitSession(SOMObject *obj);
    #pragma SOMIDLDecl(quitSession, \
        "boolean quitSession(in SOMObject p__arg1)")

            boolean    postSession(const char *postText);
    // Define Release Order for Introduced Methods (But Not Overrides)
    #pragma SOMReleaseOrder( \
                            "joinSession", \
                            "quitSession", \
                            "postSession")
};
#endif


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.