WPS Programming the Easy Way - Part 1

From EDM2
Revision as of 23:11, 27 May 2018 by Ak120 (Talk | contribs)

Jump to: navigation, search
WPS Programming the Easy Way
Part 1 Part 2

Written by Frank Matthijs

Source code: edm0208s.zip

Introduction

This is the first of two articles about WPS programming. As this is an introduction, you don't have to know anything at all about WPS programming in order to follow the articles. In fact, they may be too basic for some of you. But if you always wondered why all those things on your desktop are called objects, and how you are supposed to make your own objects, read on.

After reading these articles, you'll be able to explore the WPS on your own, so you can start making your own objects. You can follow the articles with your compiler ready, so you can actually make the objects described in the articles and see for yourself how it all works.

Overview

Welcome to the magical world of WPS programming. Since this is a relatively new area of programming, there are very few sources of information for beginning WPS programmers to find the info they need. Of course, there are the PM Reference and SOM Reference documents, but a reference is only useful when you already know something about the subject, or else it rapidly becomes an overwhelming collection of unknown terms.

Another source are the numerous sample WPS objects in the IBM toolkit (er, actually there is only one, the Car class). It is very interesting to study this example, but unfortunately, it contains a few nasty bugs so that some features only nearly work. Furthermore, it is somewhat difficult to grasp the underlying principles just by studying a "working" example.

That's why you're now reading the first of a two-part series on WPS programming. They are intended for as many programmers as possible. Therefore, a few very basic concepts are briefly explained (as far as they relate to WPS programming), such as inheritance. Experienced WPS programmers will not find these articles of any use, but they could have already guessed so from the title of the series.

What You Will Find

This first article will explain some basic concepts of WPS programming. Topics covered include object oriented programming (classes, objects, inheritance) and SOM programming (class definition file, creating the class DLL, registering the DLL). After that follows a first introduction to the practice of WPS programming. We will build a new (very basic) WPS object, step by step.

In the second article our object will be extended to make it more useful, and especially to demonstrate how to use some of the methods and what they're for. Our object will offer only part of the functionality of the Car example, but the things that are broken there will work here. Because the articles are intended to be a starting point for your own explorations in WPS land, you'll be able to add your own functions easily.

After reading the articles and following the examples, you should be able to understand most of the explanations in the PM/WPS and SOM references, although the descriptions are sometimes too vague or inaccurate to be of any help. I hope there will eventually be enough WPS programmers around to get some interesting discussions going in one of the electronic conference areas or mailing lists.

What You Will Need

In order to be able to actively follow the programming examples in the articles and to get into WPS programming yourself, you need the SOM compiler. As far as I know, this is a problem for programmers using Borland C++ or the GNU GCC or EMX development tools. That is probably the only obstacle on your way to becoming a proficient WPS programmer, but it is of course a very serious one. I'm afraid you won't be able to enjoy WPS programming if you use one of these tools, since I know of no public domain SOM compiler. Perhaps if you bother Borland enough, they'll license the SOM compiler from IBM?

If you're still with us, fine. You'll also find the SOM reference and the part of the PM reference about the Workplace Shell useful, though not as useful as it could have been due to vague or incomplete descriptions (but we're used to that, aren't we?).

In the examples, I'll assume you use C-Set/2 or C Set++. If you use another compiler, you may have to modify the makefile, and you should substitute your own make program for NMAKE.

What You Need to Know

Since this is an introduction to WPS programming, you don't need to know anything about it before you start. Of course, since the WPS uses the Presentation Manager API a lot, it doesn't hurt if you are at least somewhat familiar with PM programming. In these introductory articles, I will stress basic WPS stuff, so you don't need to be a PM expert to be able to understand what is said.

Because the WPS is based on SOM, and the "O" in "SOM" means "Object", some knowledge of object related topics will be a real plus, but I'll try to explain the most relevant items in what follows, in case you have never heard of objects before (mmm, where have you been the last couple of years, then?).

Object oriented programming

[OO experts, please note: the following explanation is oversimplified and may even be inaccurate. Its purpose is only to give programmers who know absolutely nothing about OO programming enough information to get them started with SOM and WPS. OK?]

In this section, I'll explain some terms and concepts related to OO programming. There's nothing exceptionally spectacular about it, but you'll have to know it if you want to fully understand what follows.

Object

An object is nothing more than a collection of data and code. The code is a collection of routines, usually called methods. A linked list for example can be an object. The data could consist of two pointers (start and end of the list) and a count of the elements in the list. Some methods would be provided for adding and removing elements to the list, and for determining the number of elements in the list.

Good practice dictates that the data be kept private, i.e. unavailable to other objects. If this data must be queryable, you should provide methods to do so. This way, you can prevent other objects from tampering with the internal representation of your object. Moreover, if you later decide to change the internal workings of your object, you can do so without any problems, provided the external specification remains the same. This separation of the object interface from the implementation is a very big advantage of OO programming.

For example: if you make a List, you could specify the methods Add, Remove, and QuerySize. The set of all methods, together with a specification of how to use them, forms the interface of your List. Everyone using a List must use QuerySize to get the number of elements in the List, Add to add an element to the List, and so on. You can change and improve the actual implementation of your List (for example, using pointers instead of an array) without this affecting its use. In other words: we can hide the actual implementation through the use of abstraction.

Class

Consider the concept Cat. You can say a lot of general things about Cat, for example it is an animal, has four legs and it purrs. These general features actually describe a whole class of animals: the class Cat. The cat of the neighbours and the one on your lap are specific instances of the class Cat.

This may sound a little artificial when applied to cats, but these terms are also used in OO programming. A class is a general description of a collection of very related objects. Each of those objects is an instance of that class.

For example, the List in the previous section really is a class (the List class). Every real list is an instance of the List class.

Inheritance

Some classes are related: a Cat is an Animal, a Rectangle is a Shape, and so on. This "is a" relationship is very common and defines a whole hierarchy of classes. Inheritance is the mechanism that allows programmers to express this relationship in an object oriented environment.

When some class A "is a" B (e.g. a Data File "is a" File), we can derive class A from class B. The result is that class A inherits all methods of class B. All you can do with an object of class B can also be done with an object of class A. Of course, it is still possible to define extra functionality for objects of class A, by adding methods to the inherited ones. If you derive A from B, B is called the parent and A the child.

One of the reasons why inheritance is a powerful mechanism, is that you can reuse most of the code you wrote for B. Suppose B is an Editor class, implementing a simple text editor, and you want to write a programmable editor, you can do so by making a new class, say ProgrammableEditor, and deriving this class from Editor. That way, you inherit all methods from Editor, so you don't have to recode the basic editor stuff like moving the cursor and storing the text.

Another interesting feature of inheritance is that, when A is derived from B, any function that expects an object of class B, can actually be passed instances of class A as well. So you could make a class List with method Add, accepting objects of class Link. The result is you cannot only add objects of class Link to your List, but also any object of a class derived from Link. This feature is called polymorphism: you can have a List containing objects of different types.

Besides adding methods in a derived class, it is also possible to replace the functionality of some inherited methods. Doing so is called overriding these methods. When A overrides a method it would inherit from B, it is asking to call its own version of the method instead of B's version. Luckily, B's version is still available, so A can choose to do its job and then call B's version of the method. This is somewhat similar to chaining interrupt vectors in good old DOS.

For example, if we have a class Rectangle, and we want to make a new class Square, we can derive Square from Rectangle, because a Square really is a (special sort of) Rectangle. If Rectangle has a method QueryDiagonal, Square inherits this method, so you can also query the diagonal of a Square. You could make use of some specific property of a Square in calculating the length of the diagonal (e.g. all sides have the same length), so it could be useful to override the method QueryDiagonal in Square and provide the optimized calculation instead of the old one. In this example, the old version of QueryDiagonal is not called.

The way inheritance is implemented, depends on the object environment (or programming language). Most of the time, the methods are not actually duplicated in A, but instead B's methods are called with A's data (the code is identical, only the data differs), except for overridden methods. When you call a method A inherited from B, the system determines some way or another that A is derived from B, so it in fact calls B's method. If the method is overridden, A's version is called. What function should be called can be determined statically, at compile time, or dynamically, at run time (in C++ for example, it's done statically).

SOM programming

SOM is an object model (System Object Model), meaning it supports all concepts introduced in the previous section (and a lot more too). It is built right into OS/2, so you can immediately take advantage of it (provided you have a SOM compiler, of course).

In SOM, you can define classes and create instances of these classes (it wouldn't be of much use otherwise, would it?). Of course, you'll have to produce some code in order to create a class. So where do we put this code? This is where SOM programming differs from "normal" programming: instead of making an executable file (a program), in SOM you create classes. The code for these classes is contained in a DLL. Let's see how we can produce such a DLL (we'll be making one later on).

  1. When you want to define a class in SOM, you make a text file describing the properties of your class. Such a text file should have the extension .CSC and is called the class definition file. You can specify the name of your new class, the class you want to derive from (in SOM, you always derive from some class, you can't create "root" classes), the methods you want to override and the methods you want to add. This is always the first step when creating classes for SOM.
  2. After that, you use the SOM compiler to convert this .CSC file to a collection of source files (C, C++ or some other language; currently only C and C++ are supported, we'll assume C here). So now we have something to put our code in.
  3. The .C file contains default implementations for the methods you have overridden or added. You can then alter the generated source file (by adding you own code). When you have coded the methods, you have something you can convert to a DLL file.
    Note: when you later have to change your .CSC file, you must recompile it into new source files, but the SOM compiler is smart enough not to overwrite the changes and additions you have made (i.e. it doesn't simply generate a new generic source file).
  4. The last step in the process is informing OS/2 of your new class, so that it can be used system-wide ever after. This is accomplished by registering your DLL, using a REXX script or a Win API function. After successfully registering your class, you and everyone else can create instances of your class (again using a REXX script or a Win function) and use these instances (objects). There are also functions to deregister a class and to replace a class with another one.

One important thing to know about SOM objects is that they're persistent. This means they continue to exist, even when you shutdown your system and reboot. For this to be possible, most objects must save their state and restore it afterwards (this can be done by writing the state to one of the OS/2 INI files or to extended attributes).

Metaclasses

In SOM, the class of an object is in fact itself an instance of another class, its metaclass. This metaclass can have methods, just like a normal class. The difference with normal, instance methods (which act upon one particular instance of the class) is that these class methods act upon the class itself, and thus upon all instances of the class. We'll encounter both types of methods in WPS programming.

WPS Programming

The Workplace Shell is nothing more than a collection of complex SOM classes that make very intensive use of the OS/2 API, especially the Presentation Manager API. That's why concepts like object, class, method, inheritance, overriding, etc. were explained in the previous sections: they're needed for WPS programming because WPS is based on SOM and SOM is object oriented.

WPS provides us with a number of classes to work with, like WPFolder, defining (surprise!) a folder class. You can create instances of these classes (for example, folders) with a REXX script or a Win API function, as described in the section about SOM programming. The Workplace Shell also provides another way to make instances of classes: the Templates folder. Dragging the Folder template, for example, actually creates an instance of the class WPFolder.

You can see what classes WPS defines in the PM reference (under the heading Workplace). All objects you see on your desktop are instances of these WPS classes. For example, there are probably a few folders, instances of the class WPFolder. Other objects are instances of WPPrinter, WPShredder, WPProgram, WPDataFile, etc. The WP Class List from the IBM Toolkit is itself an object listing the class hierarchy of all WPS classes (recall from the previous sections that all SOM classes (and therefore all WPS classes) form a hierarchy, showing the relationship between them). We'll use this tool later on.

Another source of information regarding WPS classes and their use is the IBM Redbook volume 3: PM and Workplace Shell and Redbook volume 4: Application development. The latter provides very useful information about some methods and when they're called, along with more in-depth information about SOM and WPS. They are a good addition to this series and I recommend you read them thoroughly.

WPS programming is in fact the creation of new classes, most of the time derived from existing WPS classes like WPDataFile, WPFolder, WPSound, and adding or overriding methods, to obtain a class that fits our needs. For example, you can create a password-protected folder class by deriving from WPFolder and overriding some methods. Because your new folder class inherits all methods from WPFolder, you don't have to write the code that implements the folder (opens a PM window, displays icons, supports drag and drop, etc.). The only thing you would do is plug your password protection code in by overriding a few methods. The trick here is getting to know for each WPS class what methods there are and what they do, so that you know what methods to override and why.

This series will introduce some methods step by step, and show their function within WPS. Some methods are almost always overridden (the classics), while others are only used in specific cases. This introduction should encourage you to browse through the references to see what's possible.

Tips

From time to time you will encounter tips. Their purpose is mainly to keep beginning WPS programmers "on the right track". WPS programming has its fair share of problems and pitfalls, just like any form of programming. There are things you had better know about, or else you lose hours finding out what the problem is. There is nothing more frustrating than discovering that the "bug" you've been searching for all day really is a peculiarity of the environment you're working in. So do read the tips if you want to save yourself hours of useless debugging. A tip looks like this:

This is a tip.

Making your first WPS object

OK, lets get started with this WPS programming business. I encourage you to actually compile the examples you'll find in this article, in order to really get the "look and feel" of WPS programming.

To make your life easier, I've included a little program (WPS.CMD) that will ready the sample files for you. Throughout this and the next article, I will mark some points as Step 1, Step 2, etc.. When you see this, you can execute the WPS program with the number of the step as a parameter. This will give you all the needed files as they are in the article at the corresponding step, making it easy to compile and examine the examples and see the results of each step on your desktop. Please note this will only install the files that are different from the previous step, so don't skip any steps.

You must install the sample files before you can use them. To do this, unzip the files for this article and type the following at any OS/2 prompt (in the directory where you unzipped the files):
WPS INSTALL
When the program is running, you can specify a directory to install the samples in. This will be your working directory for compiling the sample classes. I suggest you assign a separate directory for this purpose.
After successfully installing the files, you can use the WPS program in your working directory to ready the correct files for each step.

First Steps

Suppose you have made a nifty program working with data files. Wouldn't it be very interesting if you could add "WPS integration" to the list of features? Why not make a WPS object class to represent your data files? That's exactly what we're going to do in this and the next article.

So where do we start? You'll remember from the explanation of SOM programming that in order to create a new class, you need to make a .CSC file. Since we want to make a new data file, we derive from WPDataFile (this is the class behind the "Plain Text" Data File objects). The layout of our .CSC file is show in listing 1 below, also illustrating the syntax of the Object Interface Definition Language used in the class definition file.

#******************************************************************************
#   Include the class definition file for the parent class
#******************************************************************************

include <wpdataf.sc>

#******************************************************************************
#   Define the new class
#******************************************************************************

class: MyDataFile,
       external stem   = dfd,
       local,
       external prefix = dfd_,
       classprefix     = dfdM_,
       major version   = 1,
       minor version   = 1;

--
-- CLASS: MyDataFile
--
-- CLASS HIERARCHY:
--
--     SOMObject
--       └── WPObject
--             └── WPFileSystem
--                   └── WPDataFile
--                         └──  MyDataFile
--

#******************************************************************************
#   Specify the parent class
#******************************************************************************

parent: WPDataFile;

release order:
    wpclsQueryTitle,
    wpclsQueryInstanceType;

passthru: C.ih;

#define INCL_WIN
#define INCL_DOS
#include <os2.h>

endpassthru;

#******************************************************************************
#   Specify methods being overridden
#******************************************************************************

methods:

#******************************************************************************
#   Specify class methods being overridden
#******************************************************************************

override wpclsQueryTitle, class;
--
-- METHOD: wpclsQueryTitle                                ( ) PRIVATE
--                                                        (X) PUBLIC
-- DESCRIPTION:
--
--   Return the string "Datafile Deluxe".
--

override wpclsQueryInstanceType, class;
--
-- METHOD: wpclsQueryInstanceType                         ( ) PRIVATE
--                                                        (X) PUBLIC
-- DESCRIPTION:
--
--    The wpclsQueryInstanceType method is called to allow the class
--    object to specify the file type for instances of its
--    class.
--
-- RETURNS:
--
--    A pointer to a string containing file type.
--

Listing 1

The comments in a .CSC file can be either preceeded by a # or by --. When you use the latter, the comment is automatically included in the .C file that will be generated by the SOM compiler. If you look at the example, you can see it's in fact quite simple (most of it consists of comments). The sections in this file are as follows:

include
You should include the definitions for the parent class here (.SC file). In our example, we need the definitions of the WPDataFile class, which are in the file wpdataf.sc.
class
Information about the new class comes here. The most important item is the class name. The external stem is sometimes used by SOM to generate filenames, but is not very important. The prefixes will be used to create unique names for all methods, e.g. wpclsQueryTitle becomes dfdM_wpclsQueryTitle with the prefix in our .CSC file. As a shortcut, you can use _wpclsQueryTitle in the code to make it more readable. The version numbers will help determine if your DLL file is recent enough to provide the intended services.
The most natural thing to do with the first version of your class would be to give it version number 1.0, wouldn't it?
Well, don't do that, because for some reason or another, SOM doesn't like version numbers to be equal to zero. So you better use 1.1 as your first version number, because if you use zeroes, your class will not register. In fact, the first attempt will only complete part of the registering process, while the second try will complete the process (try it to see for yourself).
parent
Here you specify from what class you want to derive your new class, WPDataFile in our example.
release order
This tells the compiler in what order it should generate the generic function code.
passthru
Your implementation will sometimes need specific header files to be included or things like that. The place to put these is in the passthru: C.ih section. ih stands for implementation header, which is always included in the generated .C file.
Even if you yourself don't use anything from the OS/2 API, you still have to include <os2.h> because most methods use types such as PSZ that are defined in the os2.h file. You'll get compiler errors if you don't include this file.
methods
Here you indicate all new methods you want to add, as well as the methods you want to override. Class methods are indicated by the word "class" at the end. For each method you specify here, the SOM compiler will generate a code stub for you to alter.

These are the sections you should become familiar with, since they're almost always needed in order to define a new class (other sections are possible, but we won't use them here). It's a simple syntax, so you'll get used to it quite rapidly.

You probably have noticed that our class definition file specifies two methods to be overridden. Both are class methods, and thus act upon the entire class. Let's look at what these methods do, and how we can add our own functionality.

Step 1

We will now compile our class definition file. I've included a makefile you can use for all your WPS programming (adapted from the makefile in the IBM toolkit). Remember: install the samples and then type WPS 1 in your working directory. This will give you the makefile and the class definition file from listing 1.

Before using the makefile, you should edit it to adapt it to your system. All specific information is contained in the first section called Specific settings. There you will find the paths for the SOM compiler, the include files and the libraries. Please alter these to reflect your own directory structure.

Compile the class definition file by typing the following in the working directory:

NMAKE DATADEL.IH

This will start the SOM compiler and generate a lot of files. One of these files is DATADEL.C (this is by far the most interesting one). The generated code stubs in this file look like listing 2:

SOM_Scope PSZ   SOMLINK dfdM_wpclsQueryTitle(M_MyDataFile *somSelf)
{
    /* M_MyDataFileData *somThis = M_MyDataFileGetData(somSelf); */
    M_MyDataFileMethodDebug("M_MyDataFile","dfdM_wpclsQueryTitle");

    return (parent_wpclsQueryTitle(somSelf));
}

Listing 2

This may at first seem a little crowded, but you'll learn to filter the unusual stuff out of it. First of all, read the first line as

PSZ dfdM_wpclsQueryTitle(M_MyDataFile *somSelf)

Now we have a function returning a PSZ (string) and accepting a pointer. The name of the function is wpclsQueryTitle, and in order to have unique names for your class, there's a dfdM prefixed to it. This is the prefix you specified as classprefix in the class definition file, since we're dealing with a class method. In the PM reference, you'll find all methods of each WPS class, and whether it is a normal method or a class method. For clarity, all class method contain cls as part of their name. So now you can see wpclsQueryTitle indeed is a class method. I'll explain what this method actually does later on.

The second line of the stub is commented out. This code makes sure you can access the class data of your object, but since you don't have any class data, the SOM compiler has commented it out (in fact, you will get an error otherwise). Class data is data that applies to the class, so to all objects of that class. It can only be accessed by class methods.

The third line uses the SOM system to identify this method for debugging purposes. Simply let this line be and it will be very happy.

So the only remaining thing is the actual code of this method. It is really very simple:

return (parent_wpclsQueryTitle(somSelf));

You may already have guessed it: this code calls the wpclsQueryTitle of the parent class. This means that this code returns the exact same title as the WPDataFile class. Prefixing parent_ in SOM always results in calling the corresponding method of the parent class. This should almost always be done, since it allows the parent class to do its job too (most of the time, you only want to change part of the functionality, and you can let the parent do the rest of the work).

The only thing remaining to be explained, is the somSelf pointer. This is a pointer SOM uses to know what object you're talking about. Remember: all instances of a class actually share the code of the methods. The only difference between the instances is the data. So in order to tell what particular instance a method should act upon, we pass it the somSelf pointer. In languages like C++, this is done automatically, here we must do it ourselves.

With the gained knowledge, you can see that overriding the method and using the code generated by the SOM compiler as it is, has the same effect as not overriding the method at all. After all, if you don't override it, the parent's method gets called directly. But overriding a method provides you with a means to plug in your own code. I hope you can now see what WPS programming is all about: taking an existing class, such as WPDataFile, and selectively alter its behaviour. This way, you end up with your own class, behaving in a way exactly like the parent (by means of the inherited methods), and differently in another way (by means of overriding some methods).

Now let's start changing some code. I haven't explained yet exactly what this wpclsQueryTitle method does. Well, it is used in WPS to query the title (read: name) of our class. The Workplace Shell calls this method every time it wants to know our title. This title will appear in the Templates folder, and will be the default name for all instances of the class. The first thing you should do when you create a new class in WPS is give that class its own title. Otherwise, you end up with different templates all having the same name, making it sometimes impossible to tell the difference between them. Moreover, your class will be identified by its title everywhere in the WPS, not only in the Templates folder, as we will see. For this reason, you'll need to always override the wpclsQueryTitle method.

Step 2

We will call our new class "Datafile Deluxe". We do this by changing the return statement generated by the SOM compiler to:

return ("Datafile Deluxe");

At this point, we have something we can compile to a DLL: by simply typing

NMAKE

This will build a DLL with the code for our new class (ignore the RC file for now). Now that we have the DLL, the next step is to register it. We do this by copying the DLL to a directory in our LIBPATH, and running the program REG.CMD (you should find it in your working directory):

reg MyDataFile datadel

The first parameter is the name of the class we want to register, the second parameter is the name of our DLL (without extension).

You may have added the current directory (.) to your libpath. Don't rely on this to work when registering WPS classes. The system has its own current directory, and this will probably not be the one your DLL is in. The result is the system will not be able to find your DLL and the registering process fails.

After a while, the class is registered. When you open the Templates folder, you can see the Datafile Deluxe template. Drag it to create an instance of our new class. Notice that our title also appears in the "Create another" submenu. Apart from the title, our object has exactly the same characteristics as objects of the WPDataFile class. For example, it still has the type "Plain Text" (see the settings notebook, under the tab "Type").

See how easy it is to make your own WPS objects? Not counting the class definition file, we have written exactly one line of code, and we have an object that has a context menu, can be dragged, deleted, opened, edited, copied, has a settings notebook, and so on. Compare this to normal PM programming!

So far, our new class hasn't been of much use. After all, it's nothing more than a simple data file, even if its title says otherwise. We do have a new class, however, and this already has some advantages. For example, you can tell any WPS folder only to include objects of this class: open its settings notebook and select the Include tab. Here you can see part of the WPS class hierarchy, not with the actual class names like WPDataFile, MyDataFile and so on, but with the class titles (this is another place where the class titles appear). You can easily see here that our class is derived from WPDataFile: it appears indented under the Data File item. To only include objects of our class, deselect Object, and select Datafile Deluxe (click somewhere else, e.g. in the Name field, to activate your last selection). Now we have a folder that only displays objects of our class.

Perhaps more useful is the ability to find all objects of our class, wherever they are. To test this, drag a few templates to a variety of folders, then select Find from the desktop context menu. Here again we have part of the class hierarchy (see why the class title is so important?). Again deselect Object and select Datafile Deluxe. Don't forget to search all subfolders. The system will now find all Datafile Deluxe objects for you, on the desktop and in all subfolders.

When you don't need the class anymore, you can deregister it. You do this by deleting all instances of the class and typing:

dereg MyDataFile
After you've deregistered the class, you'll notice the DLL is still locked by the system and there still is a Datafile Deluxe template. The next time you reboot, the DLL will be unlocked. This is in fact very inconvenient, since you would have to reboot every time you want to register a new version of your class (you can't overwrite the old DLL when it is locked).
Alas, there's not much you can do about this. However, when you manage to delete the template, the DLL will also be unlocked. Deleting the template is not as easy as it sounds, though. Fortunately, a very nice WPS class called Black Hole, written by Gregory Czaja, will do the job nicely. You can probably find this class where you found EDM/2.
Note that you also have to delete all instances of the class in order to unlock the DLL. In summary, I suggest you delete all instances, as well as the template, every time you've deregistered a class. When you later change the code for that class, you'll have no problems copying your new DLL over the old one.
This is the most convenient procedure I know (I used to kill PMSHELL.EXE before I found this one out :-) ). If you know a better way, please tell me! It's not so bad, after all: you have to get rid of the template yourself, anyway. Rebooting won't do this for you.

You can use the WP Class List from the IBM toolkit to show the class hierarchy with the actual class names (see figure 1). This is also a handy tool to register and deregister classes, and to create instances of a class. Figure 1 shows our class MyDataFile, derived from WPDataFile, which is in turn derived from WPFileSystem, and so on.

Figure 1

Step 3

Let's make our class more useful by giving it its own type. We do this by overriding the wpclsQueryInstanceType method, just like we did with wpclsQueryTitle. We will return our title as the file type:

return (_wpclsQueryTitle(somSelf));

Using NMAKE again produces our DLL. After registering it (remember to copy it to your LIBPATH), make an instance of our class (drag the template). When you open the settings notebook, you can see it indeed has the type "Datafile Deluxe". We have used the same string for the class title and the instance type. This is to be preferred in general, since doing otherwise will only confuse the user. There's an exception to this: for some types, for example Icon, it is convenient to add the corresponding extension, for example .ICO, to the title. This convention is used in the standard WPS classes.

So what's the impact of our introducing a new type on the WPS? Well, we now have an easy and unambiguous way of associating our data file with programs. To see this, make a new Program object (you know: drag the Program template), and provide a program name (for example, EPM.EXE). Then select the Association tab. Under Available types, look who's there! You can select Datafile Deluxe and put it under Current types, associating objects of our class with the program.

Having objects of a distinct type can be advantageous when you have programs that generate their own datafiles. You can associate your datafiles with your program automatically if you use objects having a type of their own as datafiles (just like all objects of class MyDataFile), and add the ASSOCTABLE resource to your program. The first string in this resource is the type you want to associate your program with, Datafile Deluxe in our case (see the PM Reference for more info).

If your program supports drag and drop, using a distinct type makes it very easy to recognize your own datafiles: the DRAGITEM structure for the item dropped on your program contains the hstrType string. This actually contains the type name of the object dropped on it. If you would drop an object of our MyDataFile class, this string would be "Datafile Deluxe". So the only thing you have to do if you want to accept your own datafiles, is using DrgVerifyTrueType with the name of the type you want to accept.

In summary, using a distinct type makes it easy to recognize your own datafiles when they are dropped on your program, and allows users to simply double click a datafile, automatically opening your program. This is all unambiguous, unlike working with extensions: it's not because a file has the extension .DOT that it is one of your datafiles! But if it has type Datafile Deluxe, you're pretty sure it indeed is one of your files.

Another View of Our Object.

Up till now, we have only looked at the object side of our datafile. However, all objects of class WPDataFile, or derived from that class, actually represent real files, whereas objects of class WPFolder or a derived class represent real directories. In general, objects of class WPFileSystem represent "things" in a file system, that is: files or directories in the FAT or HPFS filesystems (it would be nice if the filesystem could also contain other objects, so that we could see our program objects in a directory listing, for example).

Since our class MyDataFile is derived from WPDataFile, it actually represents a file. When you create an instance of the class, you actually create a new file (with zero size for now; later we'll see how to add data). So how does the WPS know this file is actually an object of class MyDataFile? The link between the file and the WPS object representing the file, is in the extended attributes of the file. There is an attribute called .CLASSINFO, where the class of the object is stored (among other things). This way, the WPS knows what methods it has to call in what DLL when the user manipulates the file.

When we give our object a new type by overriding the wpclsQueryInstanceType method, this information is also stored in an extended attribute (.TYPE).

If the files are copied or moved, OS/2 preserves the extended attributes, so the file keeps its object characteristics. The only problem is when you copy the file in plain DOS (not a DOS box). DOS knows absolutely nothing about EA's, and will only copy or move the contents of the file, not the other features. This results in lost extended attributes, in addition to demoting the object to a simple file.

Summarizing: all information about the object is stored in EA's. To put it another way (more intimidating): objects of class WPFileSystem or a derived class are stored on disk and achieve persistence through extended attributes (impress your friends with this one).

Knowing that, since our class is derived from WPDataFile, objects of the class indeed are datafiles, you know everything you need to make programs using our new objects. As far as your program is concerned, it is working with files (having a few special extended attributes). Your program can open, read, write, close the file, just like any other file. The fact that those files are WPS objects (because of the EA's) accounts for the WPS integration of your datafiles. When your program creates a new file, it has to make sure the file contains the correct EA's. The easiest way to do this is by using WinCreateObject. This function creates an instance of the class you specify. This automatically creates the file and the correct EA's.

Step 4

You've probably wondered what the RC file is doing. Well, until now, nothing at all! We'll use it now to demonstrate how to load resources from the class DLL. We need the module handle of the DLL for this, so we will first create a new method called clsQueryModuleHandle. This method will return the handle of our DLL. We store the handle in the variable hmod, so the next time anyone calls this method, we can simply return this variable.

This is the right time to discuss class data. Remember an object contains methods and data. This data is called instance data, since it is unique for each instance. In SOM, you can also define class data, for things that all instances of the class have in common. The module handle of the DLL would seem to be an ideal candidate for storing in class data: it is a value all instances of the class share. Well, conceptually this is true, but alas there is a little problem. Class data can only be accessed by class methods, not by normal (instance) methods. Since we will want to access the module handle from normal methods later on, we don't store it in class data. Instead, we use a file scope variable in the file DATADEL.C (hmod).

The class title, however, can be stored in class data. There are two things we should do to make this work: we need to declare the class data in our class definition file, and we must override the wpclsInitData method. This method is called to initialize class data, so it is the perfect method for reading our title from the resources and storing in class data.

The complete class definition file at this point is as in listing 3 (the new items are in red). It should be fairly obvious what's going on.

#**************************************************************************
#   Include the class definition file for the parent class
#**************************************************************************

include <wpdataf.sc>

#**************************************************************************
#   Define the new class
#**************************************************************************

class: MyDataFile,
       external stem   = dfd,
       local,
       external prefix = dfd_,
       classprefix     = dfdM_,
       major version   = 1,
       minor version   = 1;

--
-- CLASS: MyDataFile
--
-- CLASS HIERARCHY:
--
--     SOMObject
--       └── WPObject
--             └── WPFileSystem
--                   └── WPDataFile
--                         └──  MyDataFile
--

#**************************************************************************
#   Specify the parent class
#**************************************************************************

parent: WPDataFile;

release order:
    clsQueryModuleHandle,
    wpclsQueryTitle,
    wpclsQueryInstanceType,
    wpclsInitData;

passthru: C.ih;

#define INCL_WIN
#define INCL_DOS
#include <os2.h>
#include "datares.h"
 #include <string.h>
endpassthru;


data:

UCHAR szTitle[CCHMAXPATH], class;

 #**************************************************************************
 #   Specify methods being overridden
 #**************************************************************************
 
 methods:
 
 #**************************************************************************
 #   Specify class methods being overridden
 #**************************************************************************
 
 
override wpclsInitData, class;
--
-- METHOD: wpclsInitData
--
-- DESCRIPTION:
--
--   Initalize the class data
--

override wpclsQueryTitle, class;
--
-- METHOD: wpclsQueryTitle                                ( ) PRIVATE
--                                                        (X) PUBLIC
-- DESCRIPTION:
--
--   Returns the title of our class.
--

override wpclsQueryInstanceType, class;
--
-- METHOD: wpclsQueryInstanceType                         ( ) PRIVATE
--                                                        (X) PUBLIC
-- DESCRIPTION:
--
--    The wpclsQueryInstanceType method is called to allow the class
--    object to specify the file type for instances of its
--    class.
--
-- RETURNS:
--
--    A pointer to a string containing file type.
--


 #**************************************************************************
 #   Define class methods
 #**************************************************************************
 
 HMODULE  clsQueryModuleHandle (), class;
 --
 -- METHOD: clsQueryModuleHandle                           ( ) PRIVATE
 --                                                        (X) PUBLIC
 -- DESCRIPTION
 --
 --   This method returns the module handle of this class.  If this is the
 --   first invocation, DosQueryModuleHandle is called to save the handle
 --   for future invocations.
 --
 -- RETURN:
 --
 --   0              Unsuccessful
 --   non-zero       module handle
 --
 

Listing 3

When you use the SOM compiler (type NMAKE DATADEL.IH), it will append the new methods to DATADEL.C (you have to move the methods manually if you don't want them at the end of your source file). Looking at the generated code for clsQueryModuleHandle, we can see that adding a method is not different from overriding one, except we can't call the parent, since it doesn't have our method. Notice that the first line in each added method is now uncommented, since we have class data. This line is still commented out in the older methods, since they were generated when there was no class data, and the SOM compiler doesn't change any code already in the source file.

Step 5

Listing 4 shows the code for our clsQueryModuleHandle method. The only really new item here is the call to _somLocateClassFile. This is a method of SOMClassMgrObject, returning the pathname of the DLL that contains the code for the class whose ID is specified as the second parameter (whew). We obtain this ID with a call to SOM_IdFromString, giving it the name of our class. After we obtain the full pathname of our DLL, we simply query its handle.

SOM_Scope HMODULE   SOMLINK dfdM_clsQueryModuleHandle(M_MyDataFile
*somSelf)

{
    APIRET rc;

    M_MyDataFileData *somThis = M_MyDataFileGetData(somSelf);
    M_MyDataFileMethodDebug("M_MyDataFile","dfdM_clsQueryModuleHandle");

    if (hmod == NULLHANDLE) {
        zString zsPathName;
        zsPathName = _somLocateClassFile(SOMClassMgrObject,
             SOM_IdFromString("MyDataFile"),
             MyDataFile_MajorVersion, MyDataFile_MinorVersion);
        rc = DosQueryModuleHandle(zsPathName, &hmod);
        if (rc) {
            WinMessageBox(HWND_DESKTOP, HWND_DESKTOP,
                (PSZ) "MyDataFile::dfdM_clsQueryModuleHandle",
                (PSZ) "Cannot load module handle",
                20,
                MB_OK | MB_INFORMATION | MB_MOVEABLE);
            return (HMODULE) 0;
            }
        }
    return hmod;
}

Listing 4

Now that we have a way of obtaining the module handle of our DLL, implementing the loading of our title is really simple (see listing 5).

SOM_Scope void   SOMLINK dfdM_wpclsInitData(M_MyDataFile *somSelf)
{
    M_MyDataFileData *somThis = M_MyDataFileGetData(somSelf);
    M_MyDataFileMethodDebug("M_MyDataFile","dfdM_wpclsInitData");

    if (!WinLoadString(WinQueryAnchorBlock(HWND_DESKTOP),
            _clsQueryModuleHandle(somSelf),
            ID_TITLE, sizeof(_szTitle), _szTitle))
        strcpy(_szTitle, parent_wpclsQueryTitle(somSelf));

    parent_wpclsInitData(somSelf);
}

Listing 5

Notice in listing 5 how we access our class data: The first line of the code should not be commented out, and we use an underscore to access the title. In fact, _szTitle really is a shortcut, and it is defined as #define _szTitle (somThis->szTitle) in datadel.ih (the SOM compiler has done this for us). This explains why the first line is needed and may help you interpret compiler errors when you later forget to uncomment that line in other methods. Prefixing an underscore is not only used to access the data, but also to access the methods, as you can see in the call to clsQueryModuleHandle.

Listing 5 also illustrates how you obtain an anchor block for your WPS code: simply obtain the one from the desktop. This makes sense, since all WPS code actually runs in the PMSHELL.EXE process.

The only remaining thing to do is modifying the wpclsQueryTitle method, since it should return the title we loaded into szTitle:

SOM_Scope PSZ   SOMLINK dfdM_wpclsQueryTitle(M_MyDataFile *somSelf)
{
    M_MyDataFileData *somThis = M_MyDataFileGetData(somSelf);
    M_MyDataFileMethodDebug("M_MyDataFile","dfdM_wpclsQueryTitle");

    return (_szTitle);
}

Remember to uncomment the first line! The SOM compiler doesn't change the code we already have, so we have to do it manually. Later, we will use other resources from the DLL, so our clsQueryModuleHandle method will again be put to good use.

Feel free to experiment with the object we have created. You can simply alter the resource file if you want to replace "Datafile Deluxe" with something else. There is one thing you should know about the way OS/2 handles types, however.
OS/2 is very smart and can remember all types it has once known. Isn't that nice? Well, no! Every new type you create will be added to the list of available types, but you cannot simply delete types you don't need anymore, using standard OS/2 utilities.
The available types are stored in OS2.INI under the application name PMWP_ASSOC_TYPE. For every possible type, this application name has a key name. You'll have to use an INI editor or write a REXX script to delete the keys you no longer need.

Summary

In this article, I've introduced WPS programming by first explaining concepts from object oriented programming and SOM programming. This allows you to understand what's going on. After that, we have made our first WPS class, edited it, compiled it, registered it and used it. This first introduction should give you a fairly good idea of what a WPS class really is, and what WPS programming is all about.

The class we've made so far is not very special. In fact, some of its features can be achieved with normal OS/2 programming (for example, the .TYPE EA). But it already shows some WPS integration, for example the ability to locate all objects of the class. This minimal form of integration may be all that is needed, and it may be easier to create a WPS object than to manually write the .TYPE EA to your datafiles.

Next time, we will improve the WPS integration, by discussing more methods and showing what the effect is of overriding them.

If you have any comments, corrections, additions, or whatever, feel free to contact me (see elsewhere in this issue for information on how to reach me).