WorkPlace Shell Programming In Assembler/2

Original Work by Micho Durdevich

Introduction
In this article we are going to highlight the most important steps in creating WorkPlace Shell objects using the machine language paradigm. The only tools that are necessary to build the corresponding dynamic link libraries are: the Watcom assembler (WASM), the linker program (WLINK) and the resource compiler (RC). The IBM Toolkit/2 should be installed, too.

At a first sight it might appear silly to try WorkPlace Shell in assembler: As is well known, the OS/2 graphical user interface is built on completely object-oriented grounds of SOM by IBM (System Object Model). The WPS design is truly unique, and establishes a "universe of objects" on its own. Traditionally, it is thought that such object-oriented programming can be formulated only within appropriate so-called high-level languages (like C or C++), which somehow contain the philosophy of objects implicitly or explicitly built in their syntax and semantic rules. In our opinion, this view is entirely wrong: The object orientation has not much to do with the programming language, but with the general vision of the programming model.

As we already mentioned in the introduction to this series, assembly language is really interpretable as the highest level programming language, if we adopt the viewpoint of the language expressive power. Therefore, in principle it should be possible to express any idea written in a "high-level" language (like C or C++) directly in terms of assembler. Of course, such a conversion might not be nice or easy at all, and in order to get a meaningful assembly-level result it might be necessary to rewrite the entire execution environment ensuring the existence of the objects in question. As an example of such a poorly designed system from the assembly viewpoint, we can mention a charming Qt toolkit by Trolltech.

Fortunately, in the case of WPS and SOM for OS/2, thanks to a high internal simplicity and elegance of the API set, we are able to proceed directly with assembly language programming without having to change the internals of the universe of objects.

Initialization Structures
At first, let us mention that every WPS class DLL should possess the following entry procedure declared as public and starting point: Next, there are initialization routines for global (class-level) and local (instance) attributes. Here is the corresponding code, concretised to the case the object in question is derived from WPDataFile class (taken from our first sample, see below). During its startup, WorkPlace shell will execute the {xxx}NewClass (local attributes) routine for every registered class. This routine, in its turn, calls the global "sister" routine. Global symbols are almost always prefixed with "M_". The global sister routine is a little simpler... Let us also observe that in the loops involving somParentNumResolve, we had to push/pop the loop counting ecx register. This was necessary because the register is not preserved across the call to this particular API. In general, return codes of OS/2 APIs are stored in the eax register, but many of the OS/2 APIs do not care much about other registers like ebx, ecx and edx. This property should be carefully taken into account. In future versions of eComStation, we will be systematically replacing the APIs so that all non-return-type registers are preserved across the OS calls (it is worth mentioning here that FreeBSD kernel fully complies with this important property).

Object & Class Data
Let us have a look at important data structures figuring in the initialization calls of the previous section. At first, we have cls_parentmethods_table and obj_parentmethods_table. These data structures are linear lists of dwords of the form parent_wpsmethod   dd token_wpsmethod At the beginning of each list, we have a static variable num_parentmethods_cls and num_parentmethods_obj respectively. During the class initialization, original token_wpsmethod values (from the class in which these methods are originally introduced) are replaced by the flat addresses of the corresponding parent methods. In this way, the selected parent methods become available to our objects.

Next interesting data structure is 8 bytes long and suffixed by ExtraData string. In the listings above, we have 2 of them: myObjectExtraData and M_myObjectExtraData. The first dword is reserved for the corresponding parent method table list address. It is filled during the processing of somBuildClass. The second dword is filled out with the address of the object data retriever routine with the help of which we can access global and local variables the object is using. A typical call to this routine would be: After this, variable somThis contains the flat offset of the allocated global or local data. Almost always, the somSelf pointer is simply given by somSelf = dword [ebp+0x00000008]

Finally, the only exported data structures are global and local ClassData-dwords. They are filled with the addresses of the associated token method tables (during somBuildClass processing). This allows other objects to gain access to the specific methods of our object.

Object & Class Tables
There are 2 critical complex data structures that "coordinate" all aspects of a given WPS class (corresponding to global and local aspects, as always). We shall call them class_information_object and class_information_global. They are passed as parameters to somBuildClass calls. Both are incarnations of a fundamental SOMClassInformation structure, which is defined below. Non-applicable parameters are usually left zero, when instantiating the structure. In the above structure, we can see pointers to several other important data objects. At first, we see of2ClassName and of2ClassMeta, they have the 2-fold pointer form In the case of class_information_global, the field for of2ClassMeta is always zero (no meta^2)! Another interesting entry corresponds to of3ParentName. It is a 3-fold pointer, realized as follows (global/local): Perhaps the most important SOMClassInformation entry is the pointer to method overrides table. This table has the form of the linear list of the pairs where we have again nice 3-fold pointers and new_implementation_proc is the procedure that overrides the method parent_method_name.

Furthermore, let us examine another very important entry: tblStatic. It is a pointer to the table of static methods introduced by the given class. The static method table is a linear listing of the following 6-fold entries, one for each method: where method_procedure is the procedure that implements the method, method_index is offset to the method entry in the ClassData structure, while @ff_methodNameBase and @ff_methodNameFull are nice 3-fold pointers entangled in the following structure (recommended, to avoid duplications): The last 2 dwords in the above 6-fold method table entry point to associated redispatch and apply stubs procedures. In the simplest scenario, these fields should be 0xFFFFFFF and 0x0000000 respectively (no redispatches/apply stubs).

Related to static methods are also entries {numStaticMethods, numClassDataEntries, numMaxMethods}. When defining ClassData structures, enough space should be left to accommodate all method tokens, and the class information (the first entry).

Finally, let us observe that the field instanceDataSize determines the amount of memory reserved for object data. It is exactly this memory area which is getting mapped by calling WPSObjExtraData[4] with somSelf as the unique argument. As we already mentioned, the result is the value of the somThis pointer.

Object Description
In the accompanying sample code, we are presenting a simple yet sufficiently illustrative WPS object, based on the random-rectangles PM program (discussed in detail within the PM-assembling article). We are constructing a child of WPDataFile object, displaying randomly fluctuating rectangles as the default view. The random number generator routine is the same as the one used in the PM example, based on a powerful multiply-with-carry algorithm. However here we control the rectangles via a special timer (WinCreateTimer, WinSetTimer) while in the PM example it was a simple cyclic thread created by DosCreateThread. Our object also features: All the samples are available at our download section.
 * A special settings page, controlling the state of the rectangles system: speed, stop/go. The mentioned settings page is introduced by overriding wpAddBecomePage method;
 * A possibility to save the state via wpSaveDeferred method;
 * Modification of the object pop-up menu, so that the quantum rectangles view properly appears.

How To Compile
The creation of the class DLL is very simple, in 3 steps: assembling, linking and resource-compiling. Explicitly, wasm qr.asm wlink @qr rc qr qr.dll Here, the linking info is stored in the file qr.lnk and the resource info is within qr.rc file. Don't forget to check the size of the DLL :)

In order to use the newly created class, it is necessary to register it: Object instances are created in the standard way. For example,

Connecting PM and WPS stuff
An interesting problem appears when interconnecting PM and WPS code. Since PM is not inherently aware of WPS object-related entities like somSelf and somThis, how to keep this information available to PM windows? The solution is to reserve the memory during the window creation, and then save the pointer to this reserved memory during the window initialization procedure.

More precisely: Here are relevant pieces of code corresponding to the outlined methodology. At first, window creation. This is a fragment of the initialization procedure specified in the object-specific (overridden) version of wpOpen (the creation of the window in which a quantum-fluctuating rectangles will be displayed).
 * When calling WinRegisterClass, specify the amount of reserved memory for the WPS-related stuff.
 * Create frame and client windows using WinCreateWindow function;
 * When creating the client window, pass the pointer to a reserved memory location containing the somSelf pointer, as an appropriate argument to WinCreateWindow;
 * During the processing of WM_CREATE message, setup a pointer to the reserved memory location, with the help of WinSetWindowPtr.

Now, the fragment of the window WM_CREATE processing procedure. Here the pointer to the reserved memory is passed as the third argument to the window procedure with arguments {hwnd, ulmsgid, mp1, mp2}, hence mp1=[ebp+0x00000010]. From this point on, the data will be easily accessible from any window subroutine, by calling WinQueryWindowPtr.

Examble B: Very Simple Object
It is one of the simplest possible WPS objects. A child of WPDataFile, with only 4 simple overrides: {wpclsQueryIconData, wpclsQueryTitle, wpclsQueryInstanceType, wpclsQueryDefaultView}. The library creation involves only assembling and linking, as there are no any resources defined.

Example C: A Dangerous Folder
In this example we construct a derived class qHole from WPFolder class. We override wpDrop method, in order to introduce a couple of new options, besides the standard drop behavior: Based on the value of an instance variable, the drop operation will The instance variable is controlled via a special settings page, introduced on top of other settings pages by overriding wpAddSettingsPages method.
 * Call the parent method (standard folder behavior);
 * Erase the dropped object (and its element objects, if the dropped object is a folder type); * Erase only subobjects that are not of folder-type (leaving the "skeleton" of an initial folder);
 * Allow entry to objects of qHole only

Our qHole object features two simple instance methods, qholeSetState and qholeGetState, controlling the above mentioned variable.

In constructing this sample (available at our download section) we were inspired by a well known Black Hole class [4].

Here is the main destroyer procedure. It checks first the dropped object type, and if the dropped object is a folder then it would enter a recursive loop to handle the subobjects. In case of WPFileSystem objects, the procedure would reset the file attributes, before deleting. It would also reset the object flags in general, before invoking the (sub)object-specific version of wpFree (calculated via somResolve).

The compilation goes in a straightforward way: To play with the library, we have to register the class qHole and create its objects, for example using the appropriate REXX scripts.

Concluding Remarks
There is a lot of fun in constructing WPS objects in assembler. In the above discussed examples, we tried to emphasize the simple internal structure of objects, and therefore we have not always optimized the code for maximum performance (for example, by holding certain variables in registers instead of using memory). We also used the ebp-frame format for majority of procedures, and stack space for procedure arguments... All 3 examples feature custom icons (standard and animation, in case of qHole). These icons are fixed by overriding class methods wpclsQueryIconData and wpclsQueryIconDataN. We decided to specify icons as resources from the main WPS library PMWP.DLL.

And no doubts, it takes more efforts to code a WPS library in assembler, than using pre-defined macros linked with the Interface Definition Language. However all the difficulties are non-essential, and there are quite non-trivial advantages in using our programming model: In forthcoming articles, we shall discuss more complex situations, including sophisticated requester WPS objects from our UAME2 package for diskless remote-booting.
 * Complete control of objects behavior;
 * The best possible optimization;
 * Enhanced creativity;
 * Removal of junk code;
 * Deeper understanding of the WPS internals, and OS/2 in general.