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.