home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
OS/2 Professional
/
OS2PRO194.ISO
/
os2
/
info
/
prgramer
/
edmi
/
issue_2
/
edmi2.inf
(
.txt
)
Wrap
OS/2 Help File
|
1993-04-12
|
89KB
|
2,203 lines
ΓòÉΓòÉΓòÉ 1. Title Page ΓòÉΓòÉΓòÉ
Welcome to EDM/2 - The Electronic OS/2 Developers' Magazine!.
Portions Copyright (C)1993 by Steve Luzynski.
Issue #2 - April 1993.
(Press 'Forward' to page.)
ΓòÉΓòÉΓòÉ 2. Copyright Notice and other Legal Stuff ΓòÉΓòÉΓòÉ
EDM/2 Copyright (C)1993 by Steve Luzynski. This publication may be freely
distributed in electronic form provided that all parts are present in their
original unmodified form. A reasonable fee may be charged for the physical act
of distribution; no fee may be charged for the publication itself.
All articles are copyrighted by their authors. No part of any article may be
reproduced without permission from the original author.
Neither this publication nor Steve Luzynski is affiliated with International
Business Machines Corporation.
OS/2 is a registered trademark of International Business Machines Corporation.
Other trademarks are property of their respective owners. Any mention of a
product in this publication does not constitute an endorsement or affiliation
unless specifically stated in the text.
ΓòÉΓòÉΓòÉ 3. From the Editor ΓòÉΓòÉΓòÉ
Hi! and welcome again to EDM/2.
This month was a big one for OS/2 developers. The new PDK CDROM, including all
kinds of great OS/2 tools from IBM, was released at the incredible price of
$15. Look for a new PM mail program from me soon now that I have the TCP/IP
developer's kit...
The other big news this month was the release of Borland C++ for OS/2. My copy
came in on the 11th - about a week after I ordered it. I'm very impressed with
this first offering from Borland...
Borland C++ for OS/2
In fact, I'm impressed enough that I'm going to talk about it with the rest of
my space. Borland C++ came in Borland's new style packaging - a slick blue &
white box. Included are seven disks and 8 manuals.
I installed all 28 megs (including all sample code) and sat down to read the
manuals while I waited. Apparently, Borland supplied a DOS based decompressor
program - my guess is that the install time will drop dramatically after they
rewrite their compression program for OS/2.
After the install completed, I made all the changes listed in the README file.
I would suggest that anyone installing it do the same - otherwise a few things
won't work quite right. I would also suggest deleting the IBM Toolkit and its
associated CONFIG.SYS lines before installing to keep things from getting
confused. Besides, BC comes with everything the Toolkit includes EXCEPT the SOM
reference (.INF) file. Copy it out if you need it.
Most of the sample code was ported directly from the toolkit. I compiled one to
make sure everything worked (it did) and started on converting one of my EMX
programs to BC.
Converting from EMX to BC
The first thing I noticed when I compiled my program under BC was a lot of
invalid conversions. EMX let me get away without explicitly casting some type
conversions; BC demanded casts for most of these. I also found that NULL and 0L
are not the same thing under BC; this required determining what it was I really
meant when I had used NULL under EMX.
After fixing these two problems (which were really the only ones I had), I
immediately noticed how much faster Borland is at compiling - especially when
using precompiled headers. It is also possible to continue editing while BC is
compiling - something I could never satisfactorily do when using Emacs as my
development environment.
I would have liked to have shown before and after code; unfortunately, in my
excitement I didn't save the old EMX version. Rest assured that it isn't really
all that difficult - I suspect many of my problems were caused by using an OS/2
1.3 book as a reference, something I have since rectified.
Etc.
You may have noticed that this issue is a little smaller than the last one.
This came about for a number of reasons:
1. March brought about Spring Break for many college students (and teachers.)
These people are among the contributors to this magazine, and many of them
decided that lying on a warm Florida beach would be more fun than sitting
in front of a computer writing articles.
2. At least for me, the release of Borland C++ for OS/2 meant that I was
spending a lot more time programming since it suddenly became a lot easier.
3. Since I too had spring break I was late in sending out guidelines and the
deadline to the authors. As much as I hate to admit it, this is the biggest
reason. I promise next time I'll be earlier.
But have no fear, next month we'll be right back up to speed!
As always, article ideas, criticism, and compliments are welcome at
sal8@po.cwru.edu.
Steve Luzynski, Editor.
ΓòÉΓòÉΓòÉ 4. Corrections and Clarifications ΓòÉΓòÉΓòÉ
Corrections
and
Clarifications
ΓòÉΓòÉΓòÉ 4.1. From Raja Thiagarajan ΓòÉΓòÉΓòÉ
Dear Editor:
First of all, thanks for the nice job you did converting my article from raw
ASCII to INF. You did an excellent job of covering my complete ignorance of
VIEW.
As long as I'm here, I've got a couple of clarifications I'd like to make to my
"Unofficial Guide to Palette Manager" article.
The first is a warning: TRIDENT VIDEO CARDS *DO* ALSO SUFFER FROM THE FATAL
BUG. That is, if you're running the Service Pack and the Trident driver that
was released shortly afterwards, changing the palette and then moving an icon
will LOCK YOUR MACHINE, exactly as with ET4000s. Also as with ET4000s, the
problem goes away if you use the 2.1 beta. Thanks to James Justice for testing
and verifying this.
Finally, I'd also like to say that I've discovered one more video platform that
supports Palette Manager: If you install the 2.1 beta on IBM's new ThinkPad
700C, you can use a 640x480x256-color mode that supports Palette Manager.
Unfortunately, it suffers from the Universal, GpiBitBlt, and Scaling bugs that
I documented in my article. But it sure is nice to do pretty paletted graphics
while lying on your stomach!
Raja Thiagarajan / sthiagar@bronze.ucs.indiana.edu / 3-23-93
ΓòÉΓòÉΓòÉ 4.2. From the Editor ΓòÉΓòÉΓòÉ
Last month, in the last-minute rush to get my first issue out on time, I ended
up forgetting to credit the authors for their articles on the title pages.
Here's the complete list (and my apologies):
o Larry Salomon - Questions and Answers
o Steve Lacy - Advanced GPI
o Raja Thaigarajan - Unofficial Guide to Palette Manager
o Gavin Baker - Introduction to PM -- Part I
o David Charlap - The Making of Minesweeper
Again, my apologies to all who were needlessly confused.
Steve Luzynski - Editor
ΓòÉΓòÉΓòÉ 5. This Month's Features ΓòÉΓòÉΓòÉ
o OS/2 Presentation Manager Drivers
o Road Map for the WorkPlace Shell
o Beginning Client/Server Programming:Named Pipes
ΓòÉΓòÉΓòÉ 5.1. OS/2 Presentation Drivers in a Nutshell ΓòÉΓòÉΓòÉ
OS/2 Presentation Drivers
in a
Nutshell
Dave Raymer
(dave@suite.com)
ΓòÉΓòÉΓòÉ 5.1.1. Introduction ΓòÉΓòÉΓòÉ
Historically, the general population has held the belief that computers are
inherently difficult to use. One of the outgrowths of this popularly held
belief has been the Graphical User Interface, or more commonly the GUI. The
designers of OS/2, both on the Microsoft, and IBM side, strove to provide not
only an easy to use interface, but an easy to program interface. This ease is
measured by the robustness of the application programming interfaces (API's)
that OS/2 supports, as well as the high degree of device independence that is
provided by the display and printer subsystems.
The OS/2 display and printer subsystems are built around the OS/2 Graphics
Engine, and a set of executable programs collectively termed "Presentation
Drivers." Presentation Drivers (PD's) are a special form of dynamic link
library (DLL) that provide a predefined interface for the OS/2 Graphics Engine
(PMGRE) to use in rendering. The display driver is one form of a presentation
driver, and the printer/plotter driver is another. The primary difference
between the two is that the display driver must provide support for a pointing
device, but on the whole, the functionality of the two is essentially the same.
In general, Joe and Suzi User interact with OS/2 through the WorkPlace Shell,
which is an application that makes extensive use of two DLLs, PMWIN.DLL and
PMGPI.DLL. These two DLLs are responsible for providing the API interface that
application programmers use. In turn, PMWIN and PMGPI, use the PMGRE in a
manner very similar to the shell's use of PMWIN and PMGPI. The PMGRE in turn
uses the PD to produce the physical output that the user sees and interacts
with, whether this output is on the video display, or a piece of paper. Figure
1 provides an annotated stack model of this simplified view of the OS/2 user
interface.
WorkPlace Shell <--- User interacts with
PMWIN.DLL PMGPI.DLL <--- WorkPlace interacts with
PMGRE.DLL <--- PMWIN/PMGPI interact with
<presentation driver> <--- produces visual output
<physical device> <--- displays visual output
(fig. 1)
ΓòÉΓòÉΓòÉ 5.1.2. A Brief Overview Of The Printer Subsystem ΓòÉΓòÉΓòÉ
The OS/2 Print Subsystem consists of three primary components, the print
manager, the print spooler, and the presentation driver. Under OS/2 version
2.x the print manager is implemented through WorkPlace Shell printer objects.
The WPS printer object provides the user the ability to manage print jobs on a
per printer basis, but is not the focus of this article, so little more will be
said in regards to it.
OS/2 provides two print spoolers, PMPRINT and PMPLOT, which differ in the fact
that PMPLOT supplies a reverse vector clipping engine developed by Steve
Matthews for Micrografx, Incorporated, and reimplemented by Wes Bell, also of
Micrografx, for IBM, enabling HPGL plotters to produce correct output in what
is primarily a raster oriented environment. The purpose of the print spooler
is to build a meta-data file of an appli- cations request for hardcopy output,
allowing the application to continue processing without without the need to
wait for hardcopy output rendering to complete.
The OS/2 print spoolers provide two types of meta-data files, the first is a
result of opening the printer OD_QUEUED with the PM_Q_STD standard option. In
this mode, the spooler will produce an intermediate data file that contains
boundary information as well as the application supplied arguments to the
rendering functions. The second format occurs when the printer is opened
OD_QUEUED with the PM_Q_RAW option. This combination will produce an
intermediate data file that is the raw printer data stream. However, the
overall data path through the printer will remain the same.
When used in conjunction with the spooler, the data is passed through the
presentation driver twice. In the PM_Q_STD mode, the first pass is used to
accumulate bounds and do basic error checking on the appli- ation supplied
arguments, while the second pass, initiated by the spooler, will generate the
hardcopy output. In the PM_Q_RAW format, the meta-data file created contains
raw printer data stream, and will pass through the presentation driver
unmodified on the second pass.
ΓòÉΓòÉΓòÉ 5.1.3. The Presentation Driver ΓòÉΓòÉΓòÉ
The OS/2 presentation driver is built around the concept of the dispatch table.
The dispatch table belongs to the OS/2 graphics engine, and is passed to the
driver at a specified point during the enable processing, so that the
presentaion driver can override the default processing of certain key rendering
routines. The enable processing for a presentation driver is handled through a
procedure that is aliased to or called OS2_PM_DRV_ENABLE. The following snipet
from a presentation driver module definition file's export section aliases a
procedure "Enable" to OS2_PM_DRV_ENABLE, and places it at ordinal 200 in the
driver. Placing the OS2_PM_DRV_ENABLE procedure at ordinal 200 is required.
OS2_PM_DRV_ENABLE = Enable @200
The prototype for the Enable procedure, for the CSet/2(tm) compiler package, is
as follows. ULONG _System Enable(ULONG SubFunc,ULONG Param1,ULONG Param2);
The returned value from the enable procedure varies from subfunction to
subfunction.
The following table lists the subfunctions.
Name Subfunction Parameters Used
---------------------------------------------------------------
Fill_Logical_Device (1) Param2
Fill_Physical_Device (2) Param1
Disable_Physical_Device (4) Param1
Enable_DC_Begin (5) Param1
Disable_DC_Complete (6) Param1
Save_DC_State (7) Param1
Restore_DC_State (8) both
Reset_DC_State (9) both
Enable_DC_Complete (10) both
Disable_DC_Begin (11) both
The handling of each of these subfunctions is covered in some detail in the
OS/2 Toolkit white books ( specifically the I/O Subsystem Presentation Driver
reference), available from IBM, therefore only a brief summary of each will be
provided here.
Fill_Logical_Device
---------------------
This ENABLE subfunction is called when the driver is loaded. Its
purpose is to initialize the logical device block, which includes
setting some bits telling the engine whether the PD needs a physical
device block (PDEV) per DC and to hook the PMGRE entry points that
the PD wishes/needs to support.
Fill_Physical_Device
----------------------
This ENABLE subfunction is called each time a DevOpenDC is requested
by the application. The purpose of this function is to create and
allocate a physical device block, which should contain any information
needed by the PD to perform I/O to the specified device. Typically
these are things such as a Presentation Manager Anchor Block (HAB),
a "private" memory heap, and a copy of the DEVOPENDATA, passed in
to Enable() for this subfunction via Param1.
Disable_Physical_Device
-------------------------
This ENABLE subfunction is called when a physical device block
is to be deleted. The major purpose of this function is to release
any memory allocated in Fill_Physical_Device, and to close the
pathway to the device.
Enable_DC_Begin
-----------------
This ENABLE subfunction is called each time a DC is created. It is
also the first time the PD is presented the DC handle and is the time
to allocate the internal DeviceContext. We are also presented with
the corresponding physical device pointer returned from the
Fill_Physical_Device subfunction.
Disable_DC_Complete
---------------------
This ENABLE subfunction is called when a DC has completed the disable
process. The purpose of this function is to release any memory
allocated for the PD's internal device context, often referred
to as the "cookie."
Save_DC_State
---------------
This Enable subfunction is called when the engine wishes a device
driver to save all DC information. This may be called multiple times
and the driver should push the DDC in LIFO order.
Restore_DC_State
------------------
This ENABLE subfunction is called when the engine wishes the PD to
POP a saved DC, or POP to a specified DC. The following table shows
the action to be taken based on the contents of Param2.
Param2 Action
-------- -------------------------------------------------
= 0 then error.
> 0 it specifies which state to restore, and the
others are lost, (if Number is 2, then the second
DC is restored, the first DC is saved, and all
others are lost.
< 0 it specifies how many states to pop back. If the
value is -1, then pop back to the first state
(fig. 3)
Reset_DC_State
----------------
This Enable subfunction is called to reset a the current DC, returning
it to is initialization state.
Enable_DC_Complete
--------------------
This call informs the PD that the DC has been created by the Engine.
Also it is the first time the PD receives the magic cookie returned
from the Enable_DC_Begin subfunction. At this time, the PD should
open the spooler if the current DC was openned OD_QUEUED. Also any
metafiles or journaling is started here.
Journaling is used primarily by raster devices that produce output
through "banding." Even a low resolution printer, with 80 by 120
dots per inch would require a substantial amount of memory to
represent an entire page of output in memory. By journaling, or
recording the calls into the driver, the driver can break the
physical page into smaller sections, replaying the journal file
for each section. Journaling was very useful in the 1.X releases
of OS/2 to handle segmentation induced limitations. However, in
Version 2.0+, it can be used to prevent a presentation driver from
consuming a large amount of physical memory, which may lead to
thrashing.
Disable_DC_Begin
------------------
This ENABLE subfunction is called before a the graphics engine
begins disable processing. This allows the PD to do any final
clean up of resources prior to the full disable. This is the
point at which the PD ceases journaling, closes the spooler, etc.
ΓòÉΓòÉΓòÉ 5.1.4. Producing Hardcopy Output ΓòÉΓòÉΓòÉ
Once all ENABLE processing has been completed, the presentation driver is ready
to begin producing output. In the handling of the Fill_Logical_Device Enable()
subfunction, the PD hooks out a copy of the the PMGRE dispatch table. The
heart of the OS/2 graphics engine is a dispatch routine that is used to access
presentation drivers and internal PMGRE entry points via vectors. The dispatch
table is an array of 32bit entries, each representing the addresses of entry
points into the graphics engine. Logically, the presentation driver perform
one of four actions on a given entry in the dispatch table.
1. Ignore it.
2. Copy it.
3. Hook it (replace it.)
4. Swap it (save and replace it.)
The following macros are taken from my presentation driver template source
code, and provide implementations of options two through four.
#define COPY(Fun) \
da##Fun = (PFNL)*(pDispatchTable + (NGre##Fun & 0xffL));
#define HOOK(Fun) \
*(pDispatchTable + (NGre##Fun & 0xffL)) = (ULONG)Fun;
#define SWAP(Fun) \
da##Fun = (PFNL)*(pDispatchTable + (NGre##Fun & 0xffL)); \
*(pDispatchTable + (NGre##Fun & 0xffL)) = (ULONG)Fun;
Note that the above macros assume that the presentation driver source code
provides local storage for SWAP'd and COPY'd vectors in local address space.
In my presentation driver template source code, this storage is provided by
variables with the prefix "da" .
The following are functions which a driver does NOT need to hook, but should
save the PMGRE vector. By calling the vector directly, the overhead of the
PMGRE dispatch mechanism is avoided. These entry points are COPY'd .
COPY( Convert ); COPY( SelectClipPath );
COPY( ConvertWithMatrix);
The following entry points should be HOOK'd by the PD and processed completely.
None of these involve PMGRE call-back, ie, your presentation driver must do all
the work.
HOOK( AccumulateBounds ); HOOK( ImageData );
HOOK( Bitblt ); HOOK( LockDevice );
HOOK( CreateLogColorTable ); HOOK( PolyScanline );
HOOK( DeviceCreateBitmap ); HOOK( PolyShortLine );
HOOK( DeviceDeleteBitmap ); HOOK( QueryColorData );
HOOK( DeviceGetAttributes ); HOOK( QueryColorIndex );
HOOK( DeviceQueryFonts ); HOOK( QueryDeviceBitmaps );
HOOK( DeviceSelectBitmap ); HOOK( QueryDeviceCaps );
HOOK( DeviceSetAttributes ); HOOK( QueryHardcopyCaps );
HOOK( DeviceSetDCOrigin ); HOOK( QueryLogColorTable );
HOOK( DeviceSetGlobalAttribute ); HOOK( QueryNearestColor );
HOOK( DrawLinesInPath ); HOOK( QueryRealColors );
HOOK( ErasePS ); HOOK( QueryRGBColor );
HOOK( Escape ); HOOK( RealizeColorTable );
HOOK( GetBitmapBits ); HOOK( RealizeFont );
HOOK( GetBoundsData ); HOOK( ResetBounds );
HOOK( GetCodePage ); HOOK( SetBitmapBits );
HOOK( GetCurrentPosition ); HOOK( SetCodePage );
HOOK( GetDCOrigin ); HOOK( SetLineOrigin );
HOOK( GetLineOrigin ); HOOK( SetPel );
HOOK( GetPairKerningTable ); HOOK( SetStyleRatio );
HOOK( GetPel ); HOOK( UnlockDevice );
HOOK( GetStyleRatio ); HOOK( UnrealizeColorTable );
The following entry PMGRE entry points should also be supported by the
presentation driver, but may also be safely passed back to the PMGRE supplied
entry points if the processing involves complex clipping, non-device fonts,
etc. These entry points may be safely SWAP'd by a printer presentation driver.
SWAP( Arc ); SWAP( FullArcBoundary );
SWAP( BeginArea ); SWAP( FullArcInterior );
SWAP( BoxBoth ); SWAP( NotifyClipChange );
SWAP( BoxBoundary ); SWAP( NotifyTransformChange );
SWAP( BoxInterior ); SWAP( PartialArc );
SWAP( CharString ); SWAP( PolyLine );
SWAP( CharStringPos ); SWAP( PolyMarker );
SWAP( DeviceQueryFontAttributes ); SWAP( QueryCharPositions );
SWAP( EndArea ); SWAP( QueryTextBox );
SWAP( FillPath ); SWAP( QueryWidthTable );
SWAP( FullArcBoth ); SWAP( SetArcParameters );
SWAP( SetCurrentPosition );
The internals of each of the PD supplied entry points is dependent on the
physical device type (printer or display, raster or vector), the algorithms and
data structures chosen, and of course the developer. It is therefore not within
the scope of this article to discuss them further. However, each of the
external entry points to the presentation driver, and for that matter, the
PMGRE have a common parameter, that is worth discussing. The last parameter to
all entry points is a 32 bit unsigned value which contains the entry point id
in the low 16 bits and a set of flags in the high order 16 bits. These bits
should be checked at the beginning of each entry point procedure, as they
contain additional control information governing exactly what the PD should do.
For example, on the first pass through the driver, the COM_DRAW bit will be
clear, while the COM_BOUND bit will be set. In this case, the PD need only
accumulate bounds for the operation, and need not produce any physical output.
Other bits of interest are used to indicate whether or not the driver is
currently within a path or area bracket.
ΓòÉΓòÉΓòÉ 5.1.5. A Few Final Notes ΓòÉΓòÉΓòÉ
For a display PD, there are several additional entry points, related to the
support of mouse and text cursors. Under V2.0+ of OS/2, the display driver
also has the additional overhead of the Virtual Device Driver necessary to
support the multiple virtual DOS machines (MVDMs) and WINOS2. Overall, display
drivers are more difficult to write than printer drivers due to performance
considerations. If you are interested in creating a display Presentation
Driver, I would highly recommend contacting IBM through the Developers
Assistance Program (the local IBM branch office should be able to help.)
OS/2 Presentation Drivers are not overly complex, neither are they simple. To
successfully create a PD requires careful thought and design as well as a
strong knowledge of computer graphics in general. The use of modular and
structured programming techniques, along with object oriented concepts (one
need not use an object language to write object oriented code) will make the
development cycle far less frustating, and much more rewarding. Remember that
a little extra effort in the design phase can save a great deal of recoding in
the testing phase.
ΓòÉΓòÉΓòÉ 5.2. Road Map for the WorkPlace Shell ΓòÉΓòÉΓòÉ
Road Map
to the
Work Place Shell
David Campbell
(campbell@campbell.saic.com)
ΓòÉΓòÉΓòÉ 5.2.1. The Work Place Shell ΓòÉΓòÉΓòÉ
The Work Place Shell (WPS) is the new user interface introduced with OS/2 2.0.
This article will begin a series of articles dedicated to explaining the
operation and use of the WPS. In preparing this initial article, I was
overwhelmed by the amount of material I would have liked to include. There are
so many aspects of the WPS to discuss. For example, Installation and
Maintenance, Programming new WPS Classes, and future enhancements.
Since the amount of material is so large, I will divide the topic into 2 or 3
articles. The first article will discuss Installation and Maintenance. The
second article will discuss programming issues facing developers who wish to
write classes for the WPS. The third article will discuss future enhancements
to the WPS. With the first article I will attempt to cover the following
topics:
o Evolution of the User Interface
o Configuration and Maintenance of the WPS
o Suggestions for Implementing the WPS
ΓòÉΓòÉΓòÉ 5.2.2. Evolution of the User Interface ΓòÉΓòÉΓòÉ
To better understand the WPS, it is necessary to be aware of the evolution of
the user interface. The user interface has a very simple purpose, and that is
to translate human initiated actions into computer operations and communicate
these operations to the operating system. Traditionally their have been two
different types of user interfaces. These include:
1. Character User Interface (CUI)
2. Graphical User Interface (GUI)
Early user interfaces were CUI. That is they could only display the characters
defined in the ASCII set. Examples of this type of interface are the command
line interfaces provided with DOS 3.3 and early implementations of UNIX and
VMS. This was limiting, but it was the only choice primarily because of 2
hardware constraints. Early CPUs did not have the processing power to manage a
GUI. Also, the video controllers and monitors were unable to display the high
resolution necessary to implement a GUI.
The CUI evolved into the GUI. The GUI provided vastly superior capabilities.
It was now possible to display on the screen different fonts, images, and other
graphical data. This type of interface required more CPU processing power, but
the power of CPUs was increasing and the cost of CPUs was decreasing, so it was
an acceptable penalty. With most current GUIs, emphasis is still on the
'application'. That is to say, the user interacts with applications. These
applications in turn interact with the data that the user requests.
The philosophy of this type of system is somewhat backward. The user should
interface directly with the data, and the application should be evoked
implicitly to act upon the data. This approach to user-data interaction lead to
the development of object oriented user interfaces. The WPS is IBM's attempt at
an object oriented user interface (OOUI). With the graphical interface, it
became possible to represent 'objects' using icons. IBM has stated that that
the current WPS is the template of future OOUIs. These future OOUIs will be
grafted onto AIX and future operating systems. Therefore, the importance of
becoming proficient at using this type of interface is obvious.
To summarize:
o To implement an OOUI effectively, the video subsystem must be operating in
graphical mode. This is necessary to display detailed images which represent
objects.
o An OOUI places emphasis on the data or object, whereas current CUIs and GUIs
place emphasis on the application.
ΓòÉΓòÉΓòÉ 5.2.3. Configuration and Maintenance of the WPS ΓòÉΓòÉΓòÉ
The user interface used by OS/2 is loaded by the base operating system at
system startup. OS/2 can be configured to load any type of user interface. By
default, the OS/2 installation procedure specifies the WPS as the user
interface. In the config.sys file, there is a line which reads:
PROTSHELL=c:\os2\pmshell.exe
This line specifies the protected mode user interface. If you want to load
another user interface, this is where you would place the new execution file.
Interestingly, another environment variable affects the WPS. This environment
variable is 'RUNWORKPLACE'. This environment variable is also used to specify
the shell.
The WPS is composed of objects. An object is an instance of a class. Each
object is represented by an icon. Each class has its own methods, data, and
class icon. The context menu of each class is different. The settings
notebook of each class is different. Interacting with objects has several
advantages. The operating system and user interface only have to be familiar
with the classes. Each external device is represented as an object. For
example, the keyboard, the mouse, the storage devices, and the printers. Each
file and directory on storage devices are also represented by objects.
Finally, each process and print job is represented by an object. In this way,
the operating system has a common interface to everything it interacts with,
because everything it interacts with is an object. The WPS is written using
the System Object Model (SOM). SOM is written to help developers create
classes more quickly, and from different languages. SOM forms a class
hierarchy which is shown below.
SOMObject
Γö£ΓöÇ SOMClass
Γö£ΓöÇ SOMClassMgr
ΓööΓöÇ WPObject
Γö£ΓöÇ WPAbstract
Γöé Γö£ΓöÇ WPPrinter
Γöé Γö£ΓöÇ WPProgram
Γöé ΓööΓöÇ WPShadow
Γö£ΓöÇ WPFileSystem
Γöé Γö£ΓöÇ WPDataFile
Γöé Γöé ΓööΓöÇ WPProgramFile
Γöé ΓööΓöÇ WPFolder
Γöé Γö£ΓöÇ WPDesktop
Γöé Γö£ΓöÇ WPDrives
Γöé ΓööΓöÇ WPStartup
ΓööΓöÇ WPTransient
Γö£ΓöÇ WPJob
ΓööΓöÇ WPPort
Note: Please note that the above class hierarchy is not complete. I have only
included the most significant classes. Also note the class
WPProgramFile. This class is actually derived from WPDataFile NOT
WPFileSystem as most of the IBM documentation states.
Due to the length of this article I will only discuss the classes which begin
with WPObject. WPObject is the parent class of the 3 base classes:WPAbstract,
WPFileSystem, and WPTransient. These 3 classes represent the foundation
classes. Each base class is differentiated from the other base classes in the
way in which the class data is maintained. Classes derived from WPAbstract
classes store their instance data in the os2.ini file. Classes derived from
WPFileSystem classes store their instance data as a file or directory on
permanent storage. Classes derived from WPTransient classes do not store their
instance data, therefore all WPTransient instance data is not maintained if the
computer is restarted. This differentiation in storage types classifies
WPAbstract and WPFileSystem as persistent, and WPTransient as being
non-persistent.
Each of these classes has some very common implementations. For example, the
typical user creates a folder which contains programs and files. This
implementation utilizes 3 WPS classes, WPProgram, WPDataFile and WPFolder. The
folder used is an instance of WPFolder. An instance of WPFolder represents a
directory on a diskette or hardrive. A folder is used as a container for other
objects. Each file in the folder is an instance of WPDataFile. An instance of
WPDataFile represents a physical file which exists either on a diskette or a
hardrive. Each of the programs within the folder is an instance of WPProgram.
Notice this is not the same class as WPProgramFile. Notice also that you do
NOT want to copy the execution file into the folder. An instance of WPProgram
is a reference to an execution file. This reference is used to specify the
name of the execution file to execute, any parameters, the startup directory,
and other information. An instance of this class typically uses the ICON
resource within the execution file.
To compare and contrast these 3 classes, we enumerate the settings notebook of
each class. Notice the differences in the settings notebook for each class.
An instance of WPProgram has 5 pages in its settings notebook.
1. Program
2. Session
3. Association
4. Window
5. General
An instance of WPDataFile has 4 pages in its settings notebook.
1. Type
2. Menu
3. File
4. General
An instance of WPFolder has 8 pages in its settings notebook.
1. View
2. Include
3. Sort
4. Background
5. Menu
6. File
7. Window
8. General
Maintaining the WPS is critical to satisfactory use of the user interface. Due
to the power and flexibility of the WPS, maintenance can be a somewhat
difficult task. Fortunately, their are ways to effectively maintain the WPS to
provide outstanding performance and usability. The number 1 thing is to keep
your os2.ini and os2sys.ini files backed up. This can be done using 'RUN'
commands in the config.sys file. You can use a 'RUN=C:\OS2\XCOPY OS2.INI
C:\TEMP\OS2.INI' this will copy the os2.ini file before the WPS starts. Once
the WPS starts, it opens the os2.ini file. The os2.ini file remains open until
the system is shutdown. Therefore, it is impossible to make copies of the
os2.ini file while the WPS is executing.
Always properly shutdown the system. The WPS maintains several open files.
Unexpectedly turning the computer off can have disastrous effects on the system
configuration files.
The next item of maintenance is concerned with maintaining the active class
list, file types, file associations, and object handles. I have written
several utilities in REXX to perform these functions. Information on these
utilities is presented in the next section.
ΓòÉΓòÉΓòÉ 5.2.4. Suggestions for Implementing the WPS ΓòÉΓòÉΓòÉ
Learning to effectively use the WPS can be difficult. Upon initial
introduction, the casual user tends to configure the WPS similar to the
Microsoft Windows environment. For example, a software developer starts an
editor to edit a source file. The user then saves the file and exits the
editor. He then evokes the compiler to convert the source code to an
executable file. He then runs the execution file.
Here is an alternative. Create a folder on your desktop titled source. Within
this folder create several data files representing your source code. For each
of the data files, set the TYPE to either C code or REXX or whatever. Then
create a program object (WPProgram) for the compiler. On the associations page
of the program object, select C code or REXX. Now when you select the open
menu of the data file, you should see an editor and a compiler. Now, to open
the file to be edited, select the editor from the open menu. To open the file
to be compiled, select the compiler from the open menu. In this way, you are
interacting more with the data than with the application. For example, the
data file can have 2 actions performed on it, edit and compile. We have
created an open menu which has these options!
I have created a set of REXX utilities for managing some of the aspects of the
WPS. These utilities allow you to add a type, delete a type, enumerate all of
the existing types, add an association, delete an association, enumerate all of
the existing associations, get EA information, and set EA information. Several
other utilities are needed, but I have not had the time to develop them. All
of these utilities can be obtained via anonymous FTP from my machine:
campbell.saic.com 139.121.81.146
Please feel free to contact me if you have questions concerning this article or
the WPS. I believe the WPS to be one of the most significant parts of OS/2
2.x. I also believe that without better understanding and knowledge of the
WPS, OS/2 2.x's future is somewhat in doubt. It would be a shame for such a
wonderful system to go unnoticed. Let's not let that happen!
ΓòÉΓòÉΓòÉ 5.3. Beginning Client/Server Programming:Named Pipes. ΓòÉΓòÉΓòÉ
Beginning Client/Server
Programming:
Named Pipes
Steve Lacy
(sl31@andrew.cmu.edu)
ΓòÉΓòÉΓòÉ 5.3.1. Beginning Client/Server Programming: Named Pipes ΓòÉΓòÉΓòÉ
This article is an introduction to client server applications, and thier
interface in OS/2. Throughout this article, click on "Forward" to read the next
paragraph. I've assumed that you've already browsed through the supplied
source files, "client.c" and "server.c" just so you can see generally whats
going on in this program, even though you might not know what the actual
function calls do, you can look at the names (like DosCreateNPipe) and see just
what they do, in this case, creating a named pipe.
ΓòÉΓòÉΓòÉ 5.3.2. Introduction ΓòÉΓòÉΓòÉ
One of the current hip words used when talking about OS/2 is "client/server"
Well, as a programmer, I know I've wondered myself, "what exactly is a
Client/Server program?" Well, from my experience, its not a black and white
definiton, since some people seem to define one style to be client/server
whereas other poeple wouldn't consider that particular example to be a
client/server application.
One thing to keep in mind is that the design of client/server applications is
usually more work than the programming of them. Designing a multithreaded
program which uses named pipes and semaphores elegantly is quite a task, we'll
be getting into those topics in later articles.
For our little example, we're going to be developing a "reverse this string"
client server application. Basically, we'll have a server that accepts
connections on a named pipe, reads in a string, and spits that string back out
onto the pipe, reversed. Note that in our example we're not using a
multithreaded server. This means that if two clients are trying to connect to
the same pipe at the same time, that only one of them will get through, and
that the other will have to wait for the other to finish, until it can
continue.
One thing is known for sure, that client server applications use the following
OS/2 kernel functions: Named Pipes, Threads/Processes, Semaphores, and Shared
Memory. Here's a brief description of four mechanisms
In this article, I don't attempt to define client/server applications, but I do
show you the basics as to what does define a "typical" client/server program,
if such a beast does exist.
ΓòÉΓòÉΓòÉ 5.3.2.1. Named Pipes ΓòÉΓòÉΓòÉ
A named pipe is a mechanism for controlling inter-process (or inter-thread)
communication under OS/2 2.0. Named pipes share a lot of the characteristics
of UNIX sockets, but in my opinion, their programming interface is a lot more
user friendly, and it doesn't have as much programming overhead as UNIX sockets
do.
In this article, we're going to dulge into the basics of named pipes, and the
OS/2 functions used to program a named pipe application.
ΓòÉΓòÉΓòÉ 5.3.2.2. Threads/Processes ΓòÉΓòÉΓòÉ
A standard OS/2 program, something that you would write with the standard
library of C functions, has what you call a "single thread of execution." A
multithreaded program, as you might think, has multiple threads of execution.
Basically, this means that your program is running in two different places at
the same time. Starting a thread has very little overhead, since all the code
for you program is already in memory. The overhead in starting a thread has
been compared to the overhead for calling a standard C function. Your program,
or some thread of your program, can start up another thread by issuing a
DosCreateThread call.
Processes are basically equivalent to "programs" One program can start up
another program by issuing a DosStartSession call. Remember that there is some
significant overhead in loading and starting another program, even if its
another copy of the program that is currently executing. When processes are
started, they always start with the main() function of your code.
ΓòÉΓòÉΓòÉ 5.3.2.3. Semaphores ΓòÉΓòÉΓòÉ
A semaphore is an inter-process or inter-thread signaling mechanism. There are
three types of semaphores in OS/2, you don't particularly need to know about
them now, so I won't go into the details. The way semaphores work is that you
can either post or wait on an open semaphore. When you're waiting, you stop
waiting until someone else posts. The neat thing is that it can be anyone
whatsoever, in any process, in any thread.
ΓòÉΓòÉΓòÉ 5.3.3. Design of the applications ΓòÉΓòÉΓòÉ
This section deals with the design of our application, and gives an
introduction to the Named pipe OS/2 services that we'll be using in our
application. We have to keep in mind that dealing with named pipes is in fact
a lot like dealing with standard files, that is, files on disk. For example,
the client end of our program uses only the DosOpen , DosWrite , DosRead , and
DosClose system calls, the exact same calls that would be used if we were
writing to a file. We distinguish a named pipe from a file by the name that we
send to the system calls. The name for pipes must be of the form "\pipe\*"
Generally, its a good idea to try to pick pipe names that you don't think other
people will be using too, since this would create problems. If you're really
paranoid, you'll do something like include the process number (which is
essentially unique) in the pipe name, so you know that no one else could
possibly have the same name as you. For our example, that would be just a
little bit of overkill, so we're just going to be using the name
"\pipe\reverse\npipe" I find it a good idea to choose pipe names so that
they're of the form "\pipe\application-name\pipe-name"
ΓòÉΓòÉΓòÉ 5.3.3.1. Pipe Semantics. ΓòÉΓòÉΓòÉ
If we're going to have interprocess communication, we have to design our
application so that the communication takes place properly, that all the data
is transmitted, and that there are "clean" open and close operations. Browsing
through the documentation, the most obvious looking function calls are:
DosCreateNPipe , DosConnectNPipe , and DosDisConnectNPipe. So, we have
functions to create pipes, "connect" to them (whatever that may mean) and to
disconnect from them. Thinking of pipes as files, the create routine creates
the pipe, the connect waits for the client to connect to the pipe that we've
just connected. When the entire process has finished, we disconnect from the
pipe, and we can either end our application, or continue with the connect
cycle.
ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
ΓöéServer Client Pipe status Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
Γöé... ... Nonexistant Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéDosCreateNPipe ... Created Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéDosConnectNPipe ... Blocking for Γöé
Γöé connections Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
Γöé... DosOpen Opened Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéDosRead DosWrite Open Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéDosWrite DosRead Open Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
Γöé... DosClose Closing Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
Γöé... ... Closed Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéDosDisConnectNPipe ... Dosconnected (sameΓöé
Γöé as created state) Γöé
ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
ΓòÉΓòÉΓòÉ 5.3.3.2. Server implementation ΓòÉΓòÉΓòÉ
So, thats basically an outline of what our programs should do, but we still
need a few little modifications. The main one being that we have to make sure
that the DosDisconnectNPipe happens after the DosClose happens on the client
side. The simples way to synchronize this it to add andother DosRead on the
server side. What will happen is that the DosRead will fail with an End Of
File (EOF) error, then we know that the pipe has defineately closed, and we can
then do our DosDisConnectNPipe. The way that we know that DosRead has returned
an end of file error is when it returns zero in the ulBytes field, which
usually contains the number of bytes that we've read from the pipe. So,
generally the code for our server looks like this:
DosCreateNPipe(...);
while (1) {
DosConnectNPipe(...);
/* Read the input data */
DosRead(...);
/* This is where we figure out what the output will be */
DosWrite(...);
/* Now we're reading for an end of file. */
DosRead(...);
/* If we're not at the end of the file that is, number of bytes read
isn't zero, then something has happened */
if (ulBytes!=0) error();
rc=DosDisConnectNPipe(...);
}
Other than the parameters for the functions, thats the program. The reason
that we have a while(1) in our program is that after one client has finished,
we want the server to reset and be able to accept more connections from other
clients.
ΓòÉΓòÉΓòÉ 5.3.3.3. Client Implementation. ΓòÉΓòÉΓòÉ
The client, as mentioned before, reads and writes to the created pipe --
teating it as a file, not as a pipe. So, we open the file, write to it, read
from it, and close it. Here's a brief version of the code:
for (i=1;i<argc;i++) {
DosWaitNPipe(...);
DosOpen(...);
DosWrite(...);
DosRead(...);
DosClose(...);
}
The only strange bit at this point should be the DosWaitNPipe call. This is
OS/2 solution to a simple problem: What to do if someone else is already using
the pipe? Well, you have to wait until the pipe frees up, that is, wait until
the server has issued a DosCreateNPipe call.
ΓòÉΓòÉΓòÉ 5.3.4. Conclusion ΓòÉΓòÉΓòÉ
Well, from this you should be able to write a simple client/server program,
basically by using the code supplied here along with the reference information
in the next session. The OS/2 calls, although they may have long names, large
numbers of arguments, or something else that you might find initially
discouraging, they're actually quite easy to use. Just keep plugging, and have
fun! Next time, we'll make the server actually do something, and we'll
introduce semaphores.
ΓòÉΓòÉΓòÉ 5.3.5. Reference Section ΓòÉΓòÉΓòÉ
This is the reference section for this article. You should note that pipe
handles and file handles are interchangable, so the file handlre returned by
DosOpen can be passed to DosWaitNPipe, which requires a pipe handle as its
argument.
ΓòÉΓòÉΓòÉ 5.3.5.1. DosOpen ΓòÉΓòÉΓòÉ
PSZ pszFileName;
PHFILE ppshfFileHandle;
PULONG pActionTaken;
ULONG ulFileSize,ulFileAttribute,ulOpenFlag,ulOpenMode;
PEAOP2 pEABuf;
APIRET rc;
rc=DosOpen(pszFileName,
ppshfFileHandle,
ulFileSize,
ulFileAttribute,
ulOpenFlag,
ulOpenMode);
pszFileName is the name of the file that we're opening.
ppshfFileHandle is the place where the opened file's file handle ps placed.
pActionTaken is the plce where the action taken value is placed. It is one if
the following:
FILE_EXISTED
FILE_CREATED
FILE_TRUNCATED
ulFileSize is the initial size of the file, if you're creating one.
ulFileAttribute is one or more of the following:
FILE_ARCHIVED
FILE_DIRECTORY
FILE_SYSTEM
FILE_HIDDEN
FILE_READONLY
FILE_NORMAL
ulOpenFlag specifies an open action. It is one of the following:
OPEN_ACTION_FAIL_IF_NEW
OPEN_ACTION_CREATE_IF_NEW
OPEN_ACTION_FAIL_IF_EXISTS
OPEN_ACTION_OPEN_IF_EXISTS
OPEN_ACTION_REPLACE_IF_EXISTS
ulOpenMode specifies the mode for opening the file. It is one of the
following:
OPEN_FLAGS_DASD (direct open flag)
OPEN_FLAGS_WRITE_THROUGH (if set, accesses don't go through cache)
OPEN_FLAGS_FAIL_ON_ERROR
OPEN_FLAGS_NO_CACHE
OPEN_FLAGS_NO_LOCALITY (don't know info. about locality)
OPEN_FLAGS_SEQUENTIAL (mostly sequential access file)
OPEN_FLAGS_RANDOM (mostly random access file)
OPEN_FLAGS_RANDOMSEQUENTIAL (random with some locality)
OPEN_FLAGS_NOINHERIT (children don't inherit handle access)
OPEN_SHARE_DENYREADWRITE
OPEN_SHARE_DENYWRITE
OPEN_SHARE_DENYREAD
OPEN_SHARE_DENYNONE
OPEN_ACCESS_READONLY
OPEN_ACCESS_WRITEONLY
OPEN_ACCESS_READWRITE
pEABuf Is a pointer to Extended Attribute information.
In most cases, you should specify OPEN_ACTION_OPEN_IF_EXISTS for ulOpenFlag
and OPEN_SHARE_DENYNONE | OPEN_ACCESS_READWRITE for the ulOpenMode.
ΓòÉΓòÉΓòÉ 5.3.5.2. DosClose ΓòÉΓòÉΓòÉ
HFILE FileHandle;
APIRET rc;
rc=DosClose(FileHandle);
Where FileHandle is the handle for the file that you want to close.
ΓòÉΓòÉΓòÉ 5.3.5.3. DosRead ΓòÉΓòÉΓòÉ
HFILE FileHandle;
PVOID pBufferArea;
ULONG ulBufferLength;
PULONG pBytesRead;
APIRET rc;
rc=DosRead(FileHandle,pBufferArea,ulBufferLength,pBytesRead);
Where FileHandle is the handle of the open file.
pBufferArea is the place where the data is to be read into.
ulBufferLength is the size of pBufferArea.
pBytesRead is the number of bytes read from the file. (on return)
ΓòÉΓòÉΓòÉ 5.3.5.4. DosWrite ΓòÉΓòÉΓòÉ
HFILE FileHandle;
PVOID pBufferArea;
ULONG ulBufferLength;
PULONG pBytesRead;
APIRET rc;
rc=DosWrite(FileHandle,pBufferArea,ulBufferLength,pBytesRead);
Where FileHandle is the handle of the open file.
pBufferArea is the place where the data is to be read from.
ulBufferLength is the size of pBufferArea.
pBytesRead is the number of bytes written to the file. (on return)
ΓòÉΓòÉΓòÉ 5.3.5.5. DosCreateNPipe ΓòÉΓòÉΓòÉ
PSZ pszFileName;
PHPIPE pphpipePipeHandle;
ULONG ulOpenMode,ulPipeMode,ulOutBufSize,ulInBufSize,ulTimeOut;
APIRET rc;
rc=DosCreateNPipe(pszFileName,
pphpipePipeHandle,
ulOpenMode,
ulPipeMode,
ulOutBufSize,
ulInBufSize,
ulTimeOut);
Where pszFileName is the name of the pipe to be created.
ulOpenMode is one or more of the following:
NP_WRITEBEHIND
NP_NOWRITEBEHIND
NP_INHERIT
NP_NOINHERIT
NP_ACCESS_INBOUND
NP_ACCESS_OUTBOUND
NP_ACCESS_DUPLEX
ulPipeMode is one or more of the following:
NP_WAIT
NP_NOWAIT
NP_TYPE_BYTE
NP_TYPE_MESSAGE
NP_READMODE_BYTE
NP_READMODE_MESSAGE
You should also bitwise-or this with the number of instances of the pipe that
you would like. In our case, this will always be one.
ulOutBufSize is the size of the outgoing buffer.
ulInBufSize is the size of the incoming buffer.
ulTimeOut is the default timeout value, in microseconds, that reads and writes
will use. Setting this to -1 gives indefinite timeout. In other words, reads
and writes will wait forever.
ΓòÉΓòÉΓòÉ 5.3.5.6. DosConnectNPipe ΓòÉΓòÉΓòÉ
HPIPE PipeHandle;
APIRET rc;
rc=DosConnectNPipe(PipeHandle);
Where PipeHandle is the pipe handle that we want to begin accepting connections
on.
ΓòÉΓòÉΓòÉ 5.3.5.7. DosDisConnectNPipe ΓòÉΓòÉΓòÉ
HPIPE PipeHandle;
APIRET rc;
rc=DosDisConnectNPipe(PipeHandle);
Where PipeHandle is the pipe handle that we want to stop accepting connections
on.
ΓòÉΓòÉΓòÉ 5.3.5.8. DosWaitNPipe ΓòÉΓòÉΓòÉ
HPIPE PipeHandle;
APIRET rc;
rc=DosWaitNPipe(PipeHandle);
Where PipeHandle is the pipe handle that we want to wait for an available
connection on.
ΓòÉΓòÉΓòÉ 6. Columns ΓòÉΓòÉΓòÉ
o Questions and Answers
o Introduction to PM
ΓòÉΓòÉΓòÉ 6.1. Questions and Answers ΓòÉΓòÉΓòÉ
Questions
and
Answers
Larry Salomon
(os2man@panix.com)
ΓòÉΓòÉΓòÉ 6.1.1. Questions and Answers ΓòÉΓòÉΓòÉ
Welcome to this month's "Questions and Answers"! Each month, I collect various
questions sent to me via email and try to answer each directly; the ones that I
feel contribute the most to developers, whether in terms of information or as a
nifty trick to tuck into your cap, get published in this column.
To submit a question, send mail to my email address - os2man@panix.com - and be
sure to grant permission to publish your question (those that forget will not
be considered for publication).
Unfortunately, no questions were submitted this month so we will discuss some
errors in the "Programming Reference" documentation. The following topics will
be covered:
o Cascading Menus
o Dropping on a Printer
o Message Box Help
ΓòÉΓòÉΓòÉ 6.1.1.1. Cascading Menus ΓòÉΓòÉΓòÉ
OS/2 2.0 added a new feature to menus, which is used extensively throughout the
Workplace Shell. These are called "conditional cascading menus" (I will refer
to them as CCM's), which look similar to "pull-rights", but have the following
differences in appearance and behavior:
o CCM's have a pushbutton instead of simply a right arrow, which looks like the
right arrow on a scrollbar.
o CCM's have a default selection. If you only click on the submenu name, the
default action is taken.
Unfortunately, there is no documentation discussing how to code this in your PM
applications, with the exception of the mentioning of the menu style.
Therefore, let us discuss this new style and introduce two new menu messages.
MS_CONDITIONALCASCADE
Setting this style indicates that the SUBMENU is to be a CCM. Unfortunately,
using this in the resource file does not work. Instead, you must explicitly set
it in your code like below:
HWND hwndMenu;
USHORT usSubmenu;
MENUITEM miItem;
WinSendMsg(hwndMenu,
MM_QUERYITEM,
MPFROM2SHORT(usSubmenu,TRUE),
MPFROMP(&miItem));
ulStyle=WinQueryWindowULong(miItem.hwndSubMenu,QWL_STYLE);
ulStyle|=MS_CONDITIONALCASCADE;
WinSetWindowULong(miItem.hwndSubMenu,QWL_STYLE,ulStyle);
hwndMenu was loaded with WinLoadMenu (specifying HWND_OBJECT as the "frame
window" in preparation for calling WinPopupMenu. usSubmenu is the resource id
of the SUBMENU to convert to a CCM.
MM_SETDEFAULTITEMID and MM_QUERYDEFAULTITEMID
These two messages are used to set and query the default item in a CCM. They
should be sent to the SUBMENU window handle (miItem.hwndSubMenu), but I'm not
sure if the former must be sent to it (the latter must). They have the
following form:
MM_SETDEFAULTITEMID
mpParm1
sDefault (SHORT)
Specifies the default menu item.
bSearchSubmenus (BOOL)
Specifies whether or not submenus should be searched or not.
TRUE Search submenus
FALSE Do not search submenus
mpParm2 (BIT32)
Reserved
NULL Reserved value.
Returns
bSuccess (BOOL)
Success indicator
TRUE Successful completion
FALSE Error occurred
Notes
Setting the default item id does not set the menu item's attribute to
MIA_CHECKED. This responsibility is left to the programmer.
MM_QUERYDEFAULTITEMID
mpParm1 (BIT32)
Reserved
NULL Reserved value.
mpParm2 (BIT32)
Reserved
NULL Reserved value.
Returns
sDefault (SHORT)
Specifies the default item.
0 There is no default item associated with the submenu.
Other Menu item id of the default item.
Encapsulation
All of this can be encapsulized into a single procedure. The code for this is
shown below:
BOOL setCascadeDefault(HWND hwndMenu,USHORT usSubmenu,USHORT usDefault)
//-------------------------------------------------------------------------
// This function sets the default menuitem for the specified CCM and checks
// the menuitem.
//
// Input: hwndMenu - specifies the menu window handle.
// usSubmenu - specifies the id of the cascade menu.
// usDefault - specifies the id of the default menuitem.
// Returns: TRUE if successful, FALSE otherwise.
//-------------------------------------------------------------------------
{
MENUITEM miItem;
ULONG ulStyle;
WinSendMsg(hwndMenu,
MM_QUERYITEM,
MPFROM2SHORT(usSubmenu,TRUE),
MPFROMP(&miItem));
ulStyle=WinQueryWindowULong(miItem.hwndSubMenu,QWL_STYLE);
ulStyle|=MS_CONDITIONALCASCADE;
WinSetWindowULong(miItem.hwndSubMenu,QWL_STYLE,ulStyle);
WinSendMsg(miItem.hwndSubMenu,
MM_SETDEFAULTITEMID,
MPFROM2SHORT(usDefault,FALSE),
0L);
WinSendMsg(miItem.hwndSubMenu,
MM_SETITEMATTR,
MPFROM2SHORT(usDefault,FALSE),
MPFROM2SHORT(MIA_CHECKED,MIA_CHECKED));
return TRUE;
}
ΓòÉΓòÉΓòÉ 6.1.1.2. Dropping on a Printer ΓòÉΓòÉΓòÉ
In the "Direct Manipulation" chapter of Volume 4 of the "Redbooks", it states
that for the rendering mechanism DRM_PRINT the printer sends the source of the
drag operation a DM_PRINTOBJECT message to print the object(s). That's all
well and good, until you try to use the information in the "Programmer's
Reference" to process this message. Well, direct manipulation is a real drag
(pun intended) as it is, but even more so when the documentation is incorrect
about a fundamental message used by the system.
The documentation states that mpParm1 will point to the DRAGINFO structure
corresponding to the drag operation in progress. Bzzz! Thanks for playing, and
Bob will tell you what your consolation prize is. One can glean what the true
value of this parameter is by looking at the name assigned in the documentation
- pDragItem. You guessed it; it points to the DRAGITEM structure corresponding
to an item that was dropped on the printer object.
While this only makes sense, this poses an interesting design decision. If your
application has a container and the user drags 10 items to the printer, you'll
get 10 different DM_PRINTOBJECT messages. Since we all know that printing
should be done in a separate thread, do we start 10 different threads to handle
each message? What if 50 objects were dropped? 100?
This can easily get out of hand, and the system can easily run out of resources
(or it did when I originally implemented it this way for one of my
applications), so an alternative method of accomplishing this will need to be
developed; this is left to the programmer, since the solutions are various and
are specific to an application.
A second, more serious, problem is that of the second parameter, passed in
mpParm2. It points to a PRINTDEST structure, which is defined in <os2def.h> as
such:
typedef struct _PRINTDEST {
ULONG cb;
LONG lType;
PSZ pszToken;
LONG lCount;
PDEVOPENDATA pdopData;
ULONG fl;
PSZ pszPrinter;
} PRINTDEST, *PPRINTDEST;
This should save you a lot of time, since the DEVOPENSTRUC structure is already
initialized and is disguised as a DEVOPENDATA structure. However, when you try
to use the pdosData field in your call to DevOpenDC, you get a
PMERR_INV_DRIVER_DATA error. This is particularly funny, since the printer
specified this as the data to use, yet won't accept it if you use it.
The only solution is to query the default driver data yourself using
DevPostDeviceModes. But wait! You don't have the device name for the printer!
Well, making the assumption that your machine doesn't have more than one
printer attached to the queue you dropped the object on, you can use the
SplQueryQueue function to determine the missing information. Sparing you the
boring details, I'll cut-to-the-quick and will simply show you the procedure
below:
BOOL initPrinter(HAB habAnchor,PPRINTDEST ppdPrinter,PDEVOPENSTRUCT pdosPrinter)
//-------------------------------------------------------------------------
// This function will query the default driver data for the printer
// specified in ppdPrinter. It is the caller's responsibility to free
// the data using free().
//
// Input: habAnchor - anchor block of the calling thread.
// ppdPrinter - pointer to the PRINTDEST structure passed via
// the DM_PRINTOBJECT message.
// Output: pdosPrinter - points to the variable which received the new
// DEVOPENSTRUC structure.
// Returns: TRUE if successful, FALSE otherwise.
//-------------------------------------------------------------------------
{
ULONG ulNeeded;
PPRQINFO3 ppiQueue;
CHAR achDriver[64];
CHAR achDevice[64];
PCHAR pchPos;
*pdosPrinter=*((PDEVOPENSTRUC)(ppdPrinter->pdopData));
SplQueryQueue(NULL,
pdosPrinter->pszLogAddress,
3,
NULL,
0,
&ulNeeded);
ppiQueue=malloc(ulNeeded);
if (ppiQueue==NULL) {
return FALSE;
} /* endif */
SplQueryQueue(NULL,
pdosPrinter->pszLogAddress,
3,
ppiQueue,
ulNeeded,
&ulNeeded);
strcpy(achDriver,ppiQueue->pszDriverName);
free(ppiQueue);
pchPos=strchr(achDriver,'.');
if (pchPos!=NULL) {
*pchPos=0;
strcpy(achDevice,pchPos+1);
} else {
achDevice[0]=0;
} /* endif */
ulNeeded=DevPostDeviceModes(habAnchor,
NULL,
achDriver,
achDevice,
ppdPrinter->pszPrinter,
DPDM_QUERYJOBPROP);
pdosPrinter->pdriv=malloc(ulNeeded);
if (pdosPrinter->pdriv==NULL) {
return FALSE;
} /* endif */
DevPostDeviceModes(habAnchor,
pdosPrinter->pdriv,
achDriver,
achDevice,
ppdPrinter->pszPrinter,
DPDM_QUERYJOBPROP);
if ((ppdPrinter->fl & PD_JOB_PROPERTY)!=0) {
DevPostDeviceModes(habAnchor,
pdosPrinter->pdriv,
achDriver,
achDevice,
NULL,
DPDM_POSTJOBPROP);
} /* endif */
return TRUE;
}
ΓòÉΓòÉΓòÉ 6.1.1.3. Message Box Help ΓòÉΓòÉΓòÉ
Message boxes are an easy way to communicate something to the user from your PM
application. However, one can only say so much in a message box, so PM allows
you to add online help for each one. To correspond a particular message with a
help panel, the panel resource id is specified in the 5th parameter and MB_HELP
must be specified in the flags.
So how do you process the help requests? The documentation states that a help
hook must be used, and that (because the Help Manager also uses a help hook) it
should be installed before creating the help instance so that you can receive
the help messages. Unfortunately, doing this won't work because the
documentation is incorrect. If you look up the entry for WinSetHook, you'll
read that this installs the hook at the head of the hook chain, so (doing a few
abstract calculations in your head) you can see that setting your help hook
before creating the help instance results in the Help Manager's hook being
positioned before yours. Calling WinSetHook after WinAssociateHelpInstance
will fix this problem and your help hook will start seeing the messages. Don't
forget to return FALSE if the message isn't processed, or the Help Manager's
hook won't see them!
As if this wasn't enough, the documentation on the help hook is incorrect also.
It states that the second parameter (sMode) can have one of the following four
values:
o HFM_MENU
o HFM_MB
o HFM_WINDOW
o HFM_APPLICATION
but none of these constants are defined in the toolkit! Instead, sMode can
have one of the following three values:
o HLPM_FRAME
o HLPM_WINDOW
o HLPM_MENU
HLPM_WINDOW and HLPM_MENU seem to correctly behave according to the
documentation for HFM_WINDOW and HFM_MENU, respectively. There is no
corresponding constant for HFM_MB though; instead, message box help requests
have the mode HLPM_WINDOW with the topic number specifying the help panel id
used in the WinMessageBox call.
ΓòÉΓòÉΓòÉ 6.2. Introduction to PM ΓòÉΓòÉΓòÉ
Introduction to
PM Programming
Part II
Gavin Baker
(demogrb@lust.latrobe.edu.au)
ΓòÉΓòÉΓòÉ 6.2.1. Introduction ΓòÉΓòÉΓòÉ
Introduction
Welcome to the second installment of our exciting series on PM programming!
(Well, I thought I'd better start on a high note ...)
In this article, I present a simple program which takes advantage of OS/2
threads. It uses one thread to handle interacting with the user, and another
thread to do all the work. It simply displays the current time in the middle
of the window. It is obviously a trivial prorgam, but nonetheless serves to
illustrate one possible use of threads.
Basically you can have one program (process) doing more than one thing
(threads) at once. A little like multi-multitasking... Anyway, in terms of the
source code, a thread is just a function in your program which gets called in a
special way which allows it to go off and run at the same time as the rest of
the program. You can imagine how complicated things could get having to
co-ordinate things, so OS/2 also provides IPC (Inter-Process Communication)
functions to help you.
The program uses other facets of OS/2 not discussed yet in this series
(resources, and the Graphics Programming Interface [GPI]) so we will not spend
too much time on them since the main focus is on threads. I have included the
RC file which defines the dialog box and the menu, but I will leave off
explaining resources to the next article.
ΓòÉΓòÉΓòÉ 6.2.2. Scope ΓòÉΓòÉΓòÉ
Scope
I am assuming that you are a competent C programmer, and have a working
knowledge of OS/2 from a user's perspective. The sample code here was produced
with Borland C++ for OS/2, but should work with most other compilers.
ΓòÉΓòÉΓòÉ 6.2.3. Trademarks etc. ΓòÉΓòÉΓòÉ
Trademarks etc.
Please note that any trademarks referred to in this article remain the property
of their respective companies.
#include <std_disclaimer.h>
ΓòÉΓòÉΓòÉ 6.2.4. Processes & Threads ΓòÉΓòÉΓòÉ
Processes & Threads
A process in OS/2 terms is just an application or program. Because OS/2 is
multi-tasking, you can run multiple processes (in their own address spaces) at
the same time. A thread is an execution unit within a process. It becomes
clearer in this diagram:
Editor's note: Due to time constraints, this diagram was not available in time
for publication. It will be made available at a later date.
The red shows OS/2 itself. The green represents our program, and the yellow
boxes are the threads within it. You would typically have one thread to handle
the user interface (the main window procedure), one to do all the work, and
perhaps one for printing. Threads can have different priorities, and there are
some very powerful functions to enable threads to communicate with each other,
to synchronise and co-ordinate events.
Threads within a process share the address space, which means the variables and
functions within the program have the same scope between two threads. For
example, consider these two threads:
void Thread1() void Thread2()
{ {
while (1) while (1)
i++; i--;
} }
Get the feeling we may not be getting anywhere...? These two threads are
playing a "tug-of-war" with the poor old (overused) variable i. The first
thread will be forever incrementing i, and the second thread will forever be
undoing all that hard work. The variable i will stay around the same value, but
won't be exactly zero due to the dynamic prioritising of threads that OS/2
performs - if a Thread1 gets a tiny bit more CPU time, i will go up a little.
Threads allow your programs to be much more efficient, and responsive to the
user. Here I will show you how.
ΓòÉΓòÉΓòÉ 6.2.5. The Code ΓòÉΓòÉΓòÉ
The Code
Right - now we'll go straight to the code. The general idea is to have the
main program handle the user interactions, and during initialization we start
the worker thread which carries on its merry way despite what the main thread
may be doing.
The way we acheive this is by creating the main window itself, and then have
the second Worker thread create a dummy window (called an OBJECT WINDOW) which
doesn't actually appear on screen, but gives us a window handle and a message
queue to work with. This is not the only way to do this, but it is probably
the simplest. The main window sends custom messages to the second dummy window
on thread 2 to do all the work.
Now here we specify which portions of the include files we need, and also
include a few others.
#define INCL_WIN
#define INCL_GPI
#define INCL_WINDIALOGS
#include <os2.h>
#include <process.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
#include <dos.h>
#include "step02.h"
Now we need to pass messages between the two threads, so we define our own by
starting at WM_USER and going up. This ensures we don't accidentally use a
system-defined message.
#define WM_BEGIN_PAINT WM_USER+1
#define WM_END_PAINT WM_USER+2
#define WM_ACK WM_USER+3
Using global variables is generally considered a no-no, unless it can't be
avoided. It makes things easier to implement and more self-contained if you
keep globals to a bare minimum. We then supply prototypes for our functions.
HWND hwndMain,
hwndWorker;
MRESULT EXPENTRY MainWndProc (HWND, ULONG, MPARAM, MPARAM);
VOID WorkerThread ();
MRESULT EXPENTRY WorkWndProc (HWND, ULONG, MPARAM, MPARAM);
MRESULT EXPENTRY DlgProc(HWND, ULONG, MPARAM, MPARAM);
VOID WorkPaint (HWND, HPS);
The main function is not very big - all it does is set up some variables, the
flags for the window, initialize things for PM, then register and create our
window. After we exit our main message loop (by getting a WM_QUIT) we clean up
and exit.
int main (void)
{
HAB hab;
HMQ hmq;
HWND hwndFrame;
QMSG qmsg;
ULONG flFrameFlags = FCF_TITLEBAR | FCF_SYSMENU |
FCF_SIZEBORDER | FCF_MINMAX |
FCF_SHELLPOSITION | FCF_TASKLIST |
FCF_MENU ;
randomize();
hab = WinInitialize (0);
hmq = WinCreateMsgQueue (hab, 0);
WinRegisterClass (hab, "STEP2", MainWndProc, CS_SIZEREDRAW, 0);
hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
&flFrameFlags, "STEP2", NULL,
0, NULLHANDLE, ID_MAIN, &hwndMain);
while (WinGetMsg (hab, &qmsg, 0, 0, 0))
WinDispatchMsg (hab, &qmsg);
WinDestroyWindow (hwndFrame);
WinDestroyMsgQueue (hmq);
WinTerminate (hab);
return 0;
}
Now here is the Window Procedure for the main window itself. The special bit
to notice here is the call to _beginthread when we get created (WM_CREATE).
This is where the second thread gets started. Note we just pass it the name of
the function, and that function will start executing from there all by itself.
It can operate more or less like any other function, with a few considerations.
MRESULT EXPENTRY MainWndProc (HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
FILEDLG fild;
switch (msg)
{
case WM_CREATE:
if (_beginthread (WorkerThread, 8192, NULL) == -1)
{
WinMessageBox (HWND_DESKTOP, hwnd,
"Creation of second thread failed!", "Step 2",
0, MB_OK | MB_CUACRITICAL);
return 0;
}
return 0;
Here is the first user message we get. This message will be sent by the second
thread's window procedure to the main one to say it is all setup and ready to
go. We respond by telling it to go ahead and start painting the window.
case WM_ACK:
WinPostMsg(hwndWorker, WM_BEGIN_PAINT, 0, 0);
return 0;
When the main window gets resized, we stop the second thread from painting, and
then restart it, causing it to update itself with the new size of the window.
Note that we use WinSendMsg first, then WinPostMsg. We send the message first,
which calls the target window procedure directly and will not continue until it
returns (thus ensuring the message gets processed) and then we Post the message
to restart in the second thread's message queue so it can keep going.
case WM_SIZE:
WinSendMsg(hwndWorker, WM_END_PAINT, 0, 0);
WinPostMsg(hwndWorker, WM_BEGIN_PAINT, 0, 0);
return 0;
We get this message when the user drops a font or colour onto the window. We
Invalidate the entire window and force a repaint so we can display with the new
font/colours.
case WM_PRESPARAMCHANGED:
WinInvalidateRect(hwndMain,NULL,TRUE);
return 0;
If the main window needs to be painted, we simply make sure that the second
thread gets on with it. Note again the different use of sending and posting
the message.
case WM_PAINT:
WinSendMsg(hwndWorker, WM_END_PAINT, 0, 0);
WinPostMsg(hwndWorker, WM_BEGIN_PAINT, 0, 0);
return 0;
In order to simplify things, PM does some of the painting for us if we like -
returning true from this message makes PM erase the entire window with the
default colour.
case WM_ERASEBACKGROUND:
return (MRESULT) TRUE;
Now, whenever a user selects something from the menu, we get a WM_COMMAND
message. We can then use the SHORT1FROMMP macro (which means extract a short
value from the high word of a message parameter) to get the ID of the menu item
selected. The first menu selection we process is the most important one - the
About box. We call WinDlgBox which does a lot of work for us. It loads the
dialog from the resource (check out STEP02.RC), runs the dialog procedure we
specify (which works just like a window procedure) and will not return until
the dialog is closed. This is called a modal dialog - it will not allow the
user to select anything else until they finish with the dialog. Contrast this
with a modeless dialog, which can be used at the same time as any other
windows. These are often used for floating toolboxes, etc. We pass WinDlgBox
the parent window (which will be the desktop), the owner window (our main
window), a pointer to the dialog procedure which handles the messages, a handle
to the module where the resource is located (by specifying NULLHANDLE OS/2
looks in the EXE file), and any extra info we want to pass the dialog
procedure.
case WM_COMMAND:
switch (SHORT1FROMMP(mp1))
{
case ID_ABOUT:
WinDlgBox(HWND_DESKTOP,hwnd,(PFNWP)DlgProc,NULLHANDLE,
DLG_ABOUT,NULL);
return 0;
Now we will use one of OS/2's standard dialogs - the File Open dialog. All we
do is set up a structure with the appropriate options, and call WinFileDlg.
Once it returns we can examine the FILEDLG struct for the file the user
selected. This example only displays the dialog - it does nothing with what
the user selected.
case ID_FILEOPEN:
memset(&fild, 0, sizeof(FILEDLG));
fild.cbSize=sizeof(FILEDLG);
fild.fl=FDS_OPEN_DIALOG | FDS_CENTER ;
fild.pszIDrive="C:";
WinFileDlg(HWND_DESKTOP,hwnd,&fild);
return 0;
If the user selects Exit from the File menu, we just send ourselves a WM_CLOSE
message, which by default will shut down our application.
case ID_FILEEXIT:
WinPostMsg(hwnd, WM_CLOSE, 0, 0);
return 0;
Any other messages we don't need to worry about, so let PM handle them by
passing them on to the default PM window procedure.
default:
return WinDefWindowProc(hwnd,msg,mp1,mp2);
}
Notice how we first destroy the second thread so it can clean up, before we
close ourselves.
case WM_DESTROY:
WinSendMsg(hwndWorker, WM_DESTROY, mp1, mp2);
return 0;
}
return WinDefWindowProc (hwnd, msg, mp1, mp2);
}
Now we get to the interesting bit - our Worker thread. It looks like a normal
function, it just gets called differently. Although this example does not
cover it, you must keep in mind that having multiple threads in the one program
requires some forethought. You can't have two threads trying to write to the
one data file, for example. We will explore this problem and how to solve it
(using Semaphores) in a later article.
VOID WorkerThread ()
{
HAB hab;
HMQ hmq;
HWND hwndObject,
hwndParent;
QMSG qmsg;
This should look familiar - it looks very much like the main procedure. The
only difference is that we specify HWND_OBJECT when we create the window. We
need our own message queue so we can talk to the main window. Notice how we
ACKnowledge the main window once we get created, and go into our message loop.
All the work is actually done in the second thread's window procedure. One
special thing to note is the call to _endthread at the end. This lets the C
runtime library clean up after us, and shuts down the thread properly.
hab = WinInitialize (0);
hmq = WinCreateMsgQueue(hab, 0);
WinRegisterClass(hab, "STEP2_B", WorkWndProc, 0, 0);
hwndWorker = WinCreateWindow( HWND_OBJECT, "STEP2_B", "",
0, 0, 0, 0, 0, HWND_OBJECT, HWND_BOTTOM, 0, NULL, NULL );
WinSendMsg( hwndMain, WM_ACK, 0, 0 );
while( WinGetMsg ( hab, &qmsg, 0, 0, 0 ))
WinDispatchMsg ( hab, &qmsg );
WinPostMsg( hwndMain, WM_QUIT, 0, 0 );
WinDestroyWindow( hwndWorker );
WinDestroyMsgQueue( hmq );
WinTerminate (hab);
_endthread ();
}
This is the dialog procedure for the About box. It is just the same as a
window procedure except that for the messages we don't process, we call
WinDefDlgProc instead of WinDefWindowProc because certain things have to be
handled differently. If the user presses a button in the dialog, we get a
WM_COMMAND much the same as if they selected a menu item. We know that the OK
button is the only button that will do anything so we don't bother checking and
just close the dialog by calling WinDismissDlg. We pass it the handle of the
dialog, and a BOOLean value which will be returned to the calling function
(back at WinDlgBox) so we can tell if the user pressed OK or Cancel.
MRESULT EXPENTRY DlgProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
switch (msg)
{
case WM_COMMAND:
WinDismissDlg(hwnd,TRUE);
return 0;
default:
return WinDefDlgProc(hwnd,msg,mp1,mp2);
}
}
This is the window procedure for the "dummy" OBJECT window which the second
thread creates. We have to set up a few things when we get CREATEd. Firstly,
we ask PM for a timer. We give it a handle to the anchor block (which we get
from our handle), the handle itself, an ID for the timer (you can have more
than one), and the delay in milliseconds between timer "ticks". We ask it to
send us a WM_TIMER once a second, so we can update our clock. The next thing
we do is get ourselves a Presentation Space (PS), which is like a handle which
is used when we want to draw or paint in the window. We then say that we want
the background to be cleared when we draw things. Otherwise the time we
display would overwrite itself and soon become garbled. I will not go into to
much detail on the drawing side of things, as the GPI (Graphics Programming
Interface) itself could fill a book (and has).
MRESULT EXPENTRY WorkWndProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
static BOOL Paint=FALSE;
static HPS hps;
SIZEL sizel;
switch (msg)
{
case WM_CREATE:
if (!WinStartTimer(WinQueryAnchorBlock(hwnd), hwnd, 1, 1000))
WinMessageBox(HWND_DESKTOP,hwnd,"Could not start timer!",
"Error",0,MB_CUACRITICAL | MB_OK);
hps = WinGetPS(hwndMain);
GpiSetBackMix(hps, BM_OVERPAINT);
return 0;
WM_BEGIN_PAINT is our first user message sent to us from the main window. All
we do is set a flag saying it is OK to keep painting. If we get WM_END_PAINT
then we stop painting for the moment (even while we are still getting WM_TIMER
messages).
case WM_BEGIN_PAINT:
Paint = TRUE;
return 0;
case WM_END_PAINT:
Paint = FALSE;
return 0;
Every second, we will get a WM_TIMER message, and all we do is check if it is
Ok to paint, and then do so.
case WM_TIMER:
if (Paint)
WorkPaint(hwndMain, hps);
return 0;
If we get closed, make sure we clean up by stopping our timer and releasing the
PS we got to draw with. Cleanup is always very important, and could
potentially cause some nasty bugs which may not be obvious. Always consult the
PM Reference manual for functions you may use which require resources to be
released or destroyed.
case WM_DESTROY:
WinStopTimer(WinQueryAnchorBlock(hwnd), hwnd, 0);
WinReleasePS(hps);
return 0;
}
return WinDefWindowProc (hwnd, msg, mp1, mp2);
}
This function does the painting for us. We get the size of the main window (a
RECTangLe), then calculate its width and height.
VOID WorkPaint (HWND hwnd, HPS hps)
{
ULONG x, y, cx, cy;
RECTL rect;
POINTL ptl;
char s[42];
struct time t;
WinQueryWindowRect(hwnd, &rect);
cx = rect.xRight - rect.xLeft;
cy = rect.yTop - rect.yBottom;
We check what the current time is, and compose our string.
gettime(&t);
sprintf(s,"Current Time: %2.2d:%2.2d%colon.%2.2d",t.ti_hour,t.ti_min,t.ti_sec);
We want the time to be shown roughly in the middle of the screen, so we figure
out where that is. We then randomly pick a colour and display the string at
the specified point.
ptl.x=(cx/3); ptl.y=(cy/2);
GpiSetColor(hps, rand() % 16);
GpiCharStringAt(hps, &ptl, strlen(s), s);
}
ΓòÉΓòÉΓòÉ 6.2.6. Prologue ΓòÉΓòÉΓòÉ
Prologue
Well, that's it. It's not a particularly exciting program, and it could
probably be more efficient, but it does provide a general skeleton for a
multi-threaded PM application. Study the structure of the program, in blocks.
The main function which sets up and creates the window, the main window
procedure which handles all the messages and delegates work through user
messages to the worker thread. Then the worker thread which sets up the object
window, and its corresponding window procedure which does all the work.
Try a few things. First, drop a different font on the time. Then try a colour
(this will only last until it updates itself, since it changes colour itself).
Then bring up the About box and move it so you can see the time still updating
itself.
Something we have not discussed is thread states. Basically, a thread can
either be running (the currently executing thread of which there can only ever
be one), ready to run, or blocked (waiting for something to happen). This
makes for very efficient programming. Imagine a terminal program. One thread
can be handling the user interface, one doing general work, and another
monitoring the serial port for input. It would call DosRead from COM1 for
example. If there is nothing to read, it doesn't have to sit in a loop and
poll the port. OS/2 will block the thread and drop its priority until DosRead
returns with something, so it won't get any CPU time until it needs it.
The possibilities for threads are endless - background recalculating,
background printing, etc. are just some. See how threads could improve your
programs.
ΓòÉΓòÉΓòÉ 6.2.7. What Next? ΓòÉΓòÉΓòÉ
What Next?
There is a lot of ground to cover in this area, and we have only scratched the
surface. But fear not - help is at hand! (You'll just have to wait until next
month...)
I welcome any feedback on this article (netmail preferred) - any comments or
suggestions you may have, questions on this article, or things you would like
to see in a future article. I hope you have learned something!
ΓòÉΓòÉΓòÉ 6.2.8. Bibliography ΓòÉΓòÉΓòÉ
Bibliography
The following references were used in the preparation of this article:
Γûá Borland C++ for OS/2 Documentation
Γûá OS/2 Version 2.0 - The Redbooks
Γûá OS/2 Version 2.0 Technical Library
ΓòÉΓòÉΓòÉ 7. Future Attractions ΓòÉΓòÉΓòÉ
Coming up in the future, we have:
o Introduction to PM Part 3
o Writing Installable File Systems
o Getting Started with IPF
o And much more!
ΓòÉΓòÉΓòÉ 8. Contributors to this issue ΓòÉΓòÉΓòÉ
o Gavin R Baker
o David Campbell
o Steve Lacy
o Steve Luzynski
o Dave Raymer
o Larry Salomon
ΓòÉΓòÉΓòÉ 8.1. Gavin R Baker ΓòÉΓòÉΓòÉ
Gavin R Baker
Gavin R Baker is the man behind ThinkSoft, a consulting firm based in
Melbourne Australia which specialises in developing custom software. He has
experience in Assembler, Pascal, C, C++, (and a lot of other languages), and
has worked with Unix, DOS, Windows, OS/2, VMS and Pick operating systems. He is
an active member of Team OS/2. When he isn't programming, he is also a
musician, an actor, and wastes lots of time reading Net News. He can be
contacted thusly:
net: demogrb@lust.latrobe.edu.au
cis: 100026,270
bix: gbaker
ΓòÉΓòÉΓòÉ 8.2. David Campbell ΓòÉΓòÉΓòÉ
David has a degree in Mechanical Engineering. Currently he is working as a
network designer and software developer. He is involved in everything from
wide area token ring networks to developing an object oriented eMail system
which works with IBM's TCP/IP for OS/2.
David W. Campbell
campbell@campbell.saic.com
Work : (615) 481-2131
Home : (615) 693-1479
ΓòÉΓòÉΓòÉ 8.3. Steve Lacy ΓòÉΓòÉΓòÉ
Steve Lacy is a third year computer science student at Carnegie Mellon
University, and is currently overoccupied with school, and his job as an
undergraduate research programmer for the Digital Mapping Lab at CMU. You can
reach Steve via e-mail at sl31@andrew.cmu.edu. He would be glad to hear any and
all of your comments about the previous article. Steve also spends his free
time collecting CD's, and unfortunately spends far too much money in the
process. Remember, diversity is knowledge, and personal gratification
overrides the need for food in many cases.....
ΓòÉΓòÉΓòÉ 8.4. Steve Luzynski ΓòÉΓòÉΓòÉ
Steve Luzynski is the editor and creator of this magazine. He is currently a
Computer Engineering student at Case Western Reserve University in Cleveland,
OH, where he spends a lot of time being cold. Steve has yet to release any
programs for OS/2 as a direct result of: 1) editing this magazine; and 2)
having to waste time going to class when there are programs to write. Steve can
by reached via e-mail at 'sal8@po.cwru.edu' or on Compuserve at 72677,2140.
The old fashioned kind of mail can currently find Steve at:
Steve Luzynski
11904 Carlton Road, Apt. 430D
Cleveland, OH 44106
ΓòÉΓòÉΓòÉ 8.5. Dave Raymer ΓòÉΓòÉΓòÉ
Dave lives in Fort Worth, Texas with his wife, two sons, and five dogs. His
current employer is Suite Software, a small privately held company which
produces an object oriented, multi-platform distributed operating system. He
has worked extensively in Windows/DOS, and OS/2; both at the device and
application levels. Additionally he has worked in X-Windows/UNIX and NeXTSTEP,
as well as in MVS and VMS.
Dave can be reached in the following ways. Please do not hesitate to contact
him for any reason. He is available for Q&A issues (preferably through
e-mail.)
e-mail:
Internet -- dave@suite.com
Prodigy -- shph80a
voice/direct:
Work -- (214)980-9900
Home -- 6528 Levitt Drive
Watauga, Texas, 76148
(817)656-3813
ΓòÉΓòÉΓòÉ 8.6. Larry Salomon ΓòÉΓòÉΓòÉ
Larry Salomon wrote his first Presentation Manager application for OS/2 version
1.1 in 1989. Since that time, he has written numerous VIO and PM applications,
including the Scramble applet included with OS/2 and the I-Brow/Magnify/Screen
Capture trio included with the IBM Professional Developers Kit CD-ROM currently
being distributed by IBM. Currently, he works for International Masters
Publishers in Stamford, Connecticut and resides in Bellerose, New York with his
wife Lisa.