Workplace Shell Development 101

From EDM2
Jump to: navigation, search

Written by Björn Fahller

Introduction

Where to begin?

While flipping through the System Object Model, Guide and Reference, I thought, "There's a lot in this thin book, although it doesn't seem impossible." Later while flipping through the list of WPS classes and methods in the PM Programming Reference, I thought, "What are all these classes? What do all these methods do?" Procrastination. Maybe I'll just write a classical program for now, and take up WPS programming in the next project?

Do you recognise this line of thinking? I have done it for a long time now, but decided I have done it for too long, and it was high time to actually do something.

To avoid taking on too large a task, and yet do something useful, I decided to try and write an extended program class. There is a serious limitation to the normal program class; only one object can be dropped on it. If several objects are dropped, the associated program will start once for every object. What I want, is a class that can send all the files dropped on the program object to the associated program as a series of parameters. Can this be done? Yes, it can. Turn to the next page to see how.

What Do We Do Now That We Know What To Do?

Creating a class description file

The first thing to do is to create a class description file. Classes are described with the language neutral SOM Object Interface Definition language, or OIDL for short. This is a file with a .CSC extension. I called mine MAPROG.CSC for "Multiple Argument Program."

Inheritance

In SOM programming, unlike object-oriented programming with, for example, C++, all classes must inherit from some class. In this case, the class to inherit from is obvious. We want to make a class with the functionality of the program class, although enhanced. So, let us begin with inheriting from the WPProgram class. First, this is done by including the definition of WPProgram to the .CSC file.

include <wppgm.sc>   // We need the definition of WPProgram, so it can be
                     // inherited from.

Once this is done, the basics of the class can be declared, as well as declaring the parent class.

class: MAProg,
external stem = maprog, local,
external prefix = maprog_,
classprefix = maprogM_,
major version = 1,
minor version = 1;
parent: WPProgram;

The details of interest in the above is the name of the class, MAProg, and the parent class. The other fields are unimportant for now.

What functionality to change?

The default behaviour of WPProgram objects, when objects are dropped on them, is to start the associated program once for every object. We want to start the associated program once, and send all the objects as parameters. It seems as if wpDrop is a good candidate for a change. This is declared in MAPROG.CSC as:

methods:

override wpDrop;

If other methods needed to be overridden, they would be listed below wpDrop. The same would go for adding new methods.

r the minimalist, this is all that is needed. Running the SOM compiler would create a C source file, where the functionality of wpDrop can be changed. Let us not be minimalists, though.

Nifty Additions

Class methods

If the class was implemented as currently described, and the result registered, a template would turn up in the templates folder. The template would have the name "Program" and the icon of it would be the same as the one for the program class. Of course, we want at least the name to be different, and a different icon would be nice too. To achieve this, the two class methods wpclsQueryIconData and wpclsQueryTitle should be overridden. The following lines in MAPROG.CSC take care of that.

override wpclsQueryTitle, class;

override wpclsQueryIconData, class;

A class method operates on the class itself, while other methods operate on instances of the class, the objects.

All classes that need a template should at least override wpclsQueryTitle, so the template will have a unique name.

Impatience

So now what? We have declared what, but what about how? Patience. The answers will come.

Compiling MAPROG.CSC with the SOM Compiler will result in, among others, a MAPROG.C file. Now, let us deal with how.

Implementation

The drop

How to deal with things being dropped? In maprog.c, the following lines implement the default drop handling:

SOM_Scope MRESULT   SOMLINK maprog_wpDrop(MAProg *somSelf,
                                          HWND hwndCnr,
                                          PDRAGINFO pdrgInfo,
                                          PDRAGITEM pdrgItem)
{
   /* MAProgData *somThis = MAProgGetData(somSelf); */
   MAProgMethodDebug("MAProg","maprog_wpDrop");

   return parent_wpDrop(somSelf, hwndCnr, pdrgInfo, pdrgItem);
}

A call to this method can be handled just as a DM_DROP message being sent to a window procedure, except for one little detail. The method is called once for every object, with pdrgItem pointing to the item currently handled. That is exactly what we do not want, but there does not seem to be any way to avoid it. Instead a way around it is needed.

It is fortunate that all the DRAGITEM structures can be retrieved from the DRAGINFO structure, and some experimentation has shown that the first time the method is called, pdrgItem points to the first DRAGITEM in an array. One of many ways around the problem, is this:

   if (!DrgAccessDraginfo(pdrgInfo)||
       DrgQueryDragitemPtr(pdrgInfo, 0) != pdrgItem) // Yucky way to make
                                                     // sure we only deal
                                                     // with this method
                                                     // once/drop

      return 0;

Who's holding the queue?

wpDrop is called as a direct result of a DM_DROP message being received by the WPS, thus we are holding the message queue while processing this method call. We all know how annoying it is with programs that occupy the message queue. WPS objects occupying the message queue is many times worse, so let us avoid that.

Getting the information from the DRAGITEM structures, and starting the application with WinStartApp() is simple. To do it without unnecessarily occupying the message queue, collect the names of the objects being dropped as quickly as possible, and start a parameter parsing and application starting thread, and return from the method call. The queue is free, and the application can be started.

   for (i=0; i < arguments; i++) {
      char *s = NULL;
      PDRAGITEM p = DrgQueryDragitemPtr(pdrgInfo, i);
      int lenContainer = DrgQueryStrNameLen(p->hstrContainerName);
      int lenSource = DrgQueryStrNameLen(p->hstrSourceName);

      argument[i] = (char *)_wpAllocMem(somSelf,
                                        lenContainer + lenSource +1,
                                        FALSE);
      size+= lenContainer + lenSource;

      if (argument[i]) {
         DrgQueryStrName(p->hstrContainerName,
                         lenContainer+1,
                         argument[i]);
         DrgQueryStrName(p->hstrSourceName,
                         lenSource+1,
                         argument[i]+lenContainer);
      } /* endif */
   } /* endfor */
   pi->somSelf = somSelf;
   pi->pDragInfo = pdrgInfo;
   pi->arguments = arguments;
   pi->argument = argument;
   _beginthread(startThread, NULL, 4096, (PVOID)pi);
   DrgDeleteDraginfoStrHandles(pdrgInfo);
   DrgFreeDraginfo(pdrgInfo);
   return 0;

A problem with the above is that dynamic memory management is necessary since the amount of memory needed for the strings cannot be determined at compile time. This can lead to...

Memory leaks?

The normal way to allocate dynamic memory when needed, is to malloc() the storage. If, for some reason, you forget to free a pointer given by malloc(), the storage will be deallocated when the process terminates; therein lies the problem with malloc and WPS programming. Every byte you allocate with malloc belongs to the same process, the WPS itself. Every pointer you forget to free will make the whole system perform worse and worse.

Fortunately, there is half a salvation, an object-bound malloc-like call. Memory allocated with _wpAllocMem will be deallocated when the object that allocated it, is no longer in use. Deallocate the memory with _wpFreeMem.

Fortunately, there is half a salvation, an object-bound malloc-like call. Memory allocated with _wpAllocMem will be deallocated when the object that allocated it, is no longer in use. Deallocate the memory with _wpFreeMem.

Conclusion

If someone with less than one week's experience can write a WPS class, anyone can. A week before writing this, I knew nothing about WPS programming. Now, after having completed this mini-project, I'm working on a real WPS application, so let's get some WPS apps out there!