home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
OS/2 Shareware BBS: 32 Periodic
/
32-Periodic.zip
/
edmi2-3.zip
/
EDMI2-3.INF
(
.txt
)
< prev
next >
Wrap
OS/2 Help File
|
1994-03-06
|
250KB
|
3,376 lines
ΓòÉΓòÉΓòÉ 1. March 1994's Title Page ΓòÉΓòÉΓòÉ
Welcome to EDM/2 - The Electronic OS/2 Developer's Magazine!
Portions copyright (c) by Larry Salomon Jr.
Volume 2, issue 3
Copyright Notice and Other Stuff
The editor of this electronic magazine is Larry Salomon, Jr.
Portions of EDM/2 are copyrighted by the editors. 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 the editors are 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.
Administrivia
First of all, I would like to say Happy Anniversary!!! to EDM/2. Yes, it has
indeed been one year since Steve Luzynski published the first issue. We have
come a long way since then, but we can go a lot further, as long as we always
have your continued support. Our success has not been only due to the hard work
of the many authors, but to the readers who have given us all feedback on the
good and bad points of the magazine. A hearty thanks to you all(!) and many
more years for EDM/2 to come!
Prizes, Prizes, Who has the Prizes?
I finally received the copies of Vis-Pro/REXX Bronze Edition from HockWare,
Inc. (thanks again), which were mailed out. Since Gavin Baker never contacted
me after my mail to him bounced, I decided to award the 3rd Place prize to the
next person down the list who wasn't me and didn't already receive a prize.
Thus, the winner is...
(drum roll please)
Raja Thiagaraian, with The Unofficial Guide to the Palette Manager, published
in volume 1, issue 1. I have sent him email about this and he has accepted the
prize. Many congratulations to you, Raja!
New York City C++ SIG
This went very well; it was nice to meet a few of the readers, even if they
didn't figure out that it was me who plugged the magazine in the announcements
(who did you think it was, anyway? :) . I think the plug picked up a few more
subscribers and/or ftp-ers.
My next "stop" will hopefully be at the IBM Technical Interchange in San
Fransisco, CA at the end of March. In case you can be there, too, a bunch of
people (including me) are planning a food fight (just kidding) at a local
restaurant or something like that. Let me know if you want to join us and have
some fun.
Official Announcement
I posted this on comp.os.os2.programmer.misc, but it should go here also. This
month sees the first installment of our new column - Book Review (yes, the
title is lame, but we can't think of anything better). The author is Carsten
Whimster (that is indeed his real name. Ask him for a complete history that
goes back to the Vikings, if you're interested :) and he has a pretty ambitious
job - review a new development or very technical book each month; of course,
the books will be OS/2 related. I think you will like this one.
Guess the Graphic
Since I only received one email guess about last month's graphic for the
communications category (what do I have to do to get a decent response rate?
"If you order now, we'll send you this free thingymajig that slices, dices,
pares, and even writes Workplace Shell objects!" :), the answers were a socket,
named pipe (named Larry, no less), and a token ring. Ba-doom tssh!
Workplace Shell
Last month I said that I would try to have an introductory WPS article for this
issue. Unfortunately, immediately following the issue's release, things got
really hairy at work, so I never could finish my class development.
The good news is that someone else decided to write such an article, which is
in this issue. Enjoy, and thanks to BjФrn for his submission.
One More Time
And speaking of BjФrn, in his article submission which he sent through the mail
was an .INF file. I usually look at these to get an idea of how much rework
was necessary. "Hmm...He did the 'duplicated introduction' thing that I had so
much trouble with. But let me try Alt+F anyways." Much to my surprise, it did
not display the introduction twice!
The net result is that, if you put no text after the heading for the article,
the compiler issues a warning message, but when you display the article
heading, it automatically jumps to the next heading with text. I quickly
adopted this trick in this issue.
ΓòÉΓòÉΓòÉ 2. Features for March 1994 ΓòÉΓòÉΓòÉ
The following articles constitute this issue's features:
o Making Noise with MMPM/2 - Part 2
o Porting STEP02 to ICLUI
o Workplace Shell Development 101
ΓòÉΓòÉΓòÉ 2.1. Making Noise with MMPM/2 - Part 2 ΓòÉΓòÉΓòÉ
ΓòÉΓòÉΓòÉ 2.1.1. Introduction ΓòÉΓòÉΓòÉ
Written by Marc van Woerkom
Introduction
This is the second article on MMPM/2 programming. It was originally planned to
be the final one, but two reasons forced me to extend this series:
1. Semir Patel's fine article in volume 2, issue 1 of EDM/2 already touched
most topics I intended to present here, blasting my concept. (Watch out
for my upcoming DOOM-style game, featuring a certain editor :-)
2. There is so much to cover about MMPM/2 that two parts are simply not
enough.
ΓòÉΓòÉΓòÉ 2.1.2. Limitations of Using REXX ΓòÉΓòÉΓòÉ
Limitations of Using REXX
When I delved further into MMPM/2 programming it became clear to me that the
features accessible from the REXX Media Control Interface (MCI) are very
powerful but that there is still more to see, much like the part of an iceberg
lying under water. After all, let's face the music (pun intended); MMPM/2 is
written in C meaning all of the APIs will be accessible from C. The only APIs
provided for REXX are:
o mciSendString (mciRxSendString)
o mciGetErrorString (mciRxGetErrorString)
o mciGetDeviceID (mciRxGetDeviceID)
Compare this with the near 200 MMPM/2 API calls accessible through C/C++.
To be fair, it should be possible to close the gaps since VX-REXX 2.0 from
Watcom seems to have multimedia support. However, one must stick to C/C++ if
one wants...
o to get complex feedback (be it a whole structure of information, or
synchronization messages)
o to use the basic Multimedia I/O subsystem (MMIO API)
o to control streaming and synchronization through the SPI subsystem
o to use the PM extensions provided by MMPM/2 (i.e. new graphical controls and
secondary window support)
This doesn't even include esoteric business like driver development.
ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇ
After all of this bashing on poor REXX, let's close this section with the
biggest advantage of REXX:
o It's pretty easy to use and provides a great introduction! :-)
Please read the first part of this series for more information on this topic.
ΓòÉΓòÉΓòÉ 2.1.3. Preliminary Thoughts ΓòÉΓòÉΓòÉ
Preliminary Thoughts
Before you start MMPM/2 programming, you need a C or C++ compiler and MMPM/2
bindings. I recommend using either:
o one of the IBM compilers (C Set++, Firststep) together with the Developer's
Toolkit and the MMPM/2 Toolkit (which has been included in the Developer's
Toolkit since the 2.1 release)
o or, the EMX port of the GNU GCC C/C++/Objective C compiler (revisions 0.8h
and 2.5.8 or later) together with the MM4EMX package (revision 1.0).
Although this should suffice to recompile the examples given, and you'll get a
lot of information from here, you still should acquire some of the literature
mentioned at the end of the first part of this series and at the end of this
part.
Another great source of information are the computer networks. Often you can
contact someone on the Internet news groups or CompuServe fora who can help you
or share thoughts with you.
For those with Usenet access I recommend comp.os.os2.multimedia, or (if you
somehow feel an urge to polish your German a little bit :-) maus.os2.prog.
So much for the basic preparations. (Hmm...did I already tell you to install
MMPM/2? :-)
ΓòÉΓòÉΓòÉ 2.1.4. Multimedia Programming Using EMX ΓòÉΓòÉΓòÉ
Multimedia Programming Using EMX
To state it shortly - it's no problem to do any MMPM/2 programming with EMX.
As an EMX user you have the choice between using the OS/2 and MMPM/2 headers
and libraries from the IBM Toolkits or using those of the EMX and MM4EMX
packages. Both have their strengths and weaknesses. However, this article
series should feature nothing which is specific to one of the above
combinations, or even the EMX system.
The MM4EMX Package
The Multimedia for EMX package (MM4EMX) is a freeware package for the EMX
development environment. It contains all necessary 32-bit header files and
import libraries in the BSD .A and Intel OMF .LIB formats together with source
code.
The samples presented and explained in this part of the article series are the
ones from MM4EMX (release 1.0). As a side note, please contact the author if
you should note incompatibilities.
ΓòÉΓòÉΓòÉ 2.1.5. The Textual Media Control Interface ΓòÉΓòÉΓòÉ
The Textual Media Control Interface
The easiest way to use the MCI is via its textual interface, using the
mciSendString() API call. Below is an example of this.
#
# bach.mci -- play the Bach MIDI file
#
open Sequencer alias midi2 shareable wait
info midi2 product wait
set midi2 time format mmtime wait
load midi2 c:\mmos2\sounds\bach.mid wait
status midi2 length wait
play midi2 wait
close midi2
It opens the Sequencer (MIDI) multimedia device, giving it the alias "midi2".
The product info is queried and mmtime is chosen as timeformat. Then a MIDI
file from Bach is loaded into the context of the device, its length is queried
and it gets played. Finally the device is closed.
The following example will take a file of MCI command strings and sends them
line by line for execution to the MDM via mciSendString(), thus interpreting
the file.
//////////////////////////////////////////
// mci -- an interpreter for MCI scripts
//
// using emx 0.8h, mm4emx 1.0
//
// based on a C program by
// John J. McDonough
//
// Marc E.E. van Woerkom, 2/94
//
#include <iostream.h>
#include <fstream.h>
#include <os2.h>
One has to define INCL_OS2MM to include the proper definitions and
declarations, i.e. the 32-bit version of the MMPM/2 API with naming conventions
conforming to OS/2.
A defined INCL_MCIOS2 will include the MCI support.
#define INCL_OS2MM
#define INCL_MCIOS2
#include <os2me.h>
//
// this sample demonstrates the following MMPM/2 API's:
//
// mciSendString() send a MCI command string, receive an answer
// mciGetErrorString() look up the error string for the error code
//
int main(int argc, char* argv[])
{
if ( argc != 2 ) {
cerr << "usage: mci <filename>\n";
return 1;
}
ifstream infile(argv[1]);
if (!infile) {
cerr << "error: can't open input file " << argv[1] << '\n';
return 2;
}
int line = 0;
const bsize = 128;
char buff[bsize];
while (!infile.eof()) {
char c = infile.peek(); // peek one char forward
if (infile.good()) {
infile.get(buff, bsize, '\n');
cout.width(3);
cout << ++line << ": [" << buff << "]\n";
if (c != '#') {
Now we finally got a non-comment line into the variable buff.
The next thing to do is to provide a return buffer for mciSendString(), which
has to be empty.
const rsize = 128;
char rbuff[rsize];
for (int i=0; i<rsize; i++) rbuff[i] = 0;
ULONG rc = mciSendString(buff, // buffer with MCI string
rbuff, // return buffer
rsize, // rbuff size
0, // no callback window handle
0); // no user parameter
if (rc == MCIERR_SUCCESS) {
if (rbuff[0])
cout << " -> " << rbuff << "\n\n";
}
else {
The return code wasn't MCIERR_SUCCESS, so something strange happened. Because
an error code is not too enlightening, we use mciGetErrorString() to get a nice
string.
ULONG rc2 = mciGetErrorString(rc, // error code
rbuff, // return buffer
rsize); // rbuff size
In case the rc is out of range we give up and display the number.
if (rc2 == MCIERR_SUCCESS)
cerr << " -> MCI error: " << rbuff << "\n\n";
else
cerr << " -> error #" << rc << " has occured!\n\n";
}
}
}
infile.get(c); // eat the \r after the \n
}
return 0;
}
All of this is pretty straight forward. Now we have a tool to try out MCI
command strings quick and easy. Below are some other MCI scripts:
#
# boing.mci -- play boing.wav
#
open waveaudio alias wave shareable wait
load wave c:\mmos2\sounds\boing.wav wait
play wave wait
close wave wait
This example shows how the audio CD player applet recognizes CDs, it checks for
the 8 byte long ID:
#
# playcd.mci -- play an audio CD
#
open cdaudio01 alias cdda shareable wait
status cdda media present wait
status cdda mode wait
set cdda time format tmsf wait
status cdda volume wait
status cdda number of tracks wait
status cdda length wait
status cdda type track 1 wait
# check unique ID (8 bytes)
info cdda ID wait
# check unique UPC (bcd number)
info cdda UPC wait
seek cdda to start wait
# this provokes an error, for there is no window to notify
play cdda notify
play cdda wait
close cdda wait
ΓòÉΓòÉΓòÉ 2.1.6. The Procedural Media Control Interface ΓòÉΓòÉΓòÉ
The Procedural Media Control Interface
While the ease of the textual MCI is unbeatable, it has some weaknesses in
comparison to the procedural MCI.
o It has to be translated (parsed) internally which needs time.
o Anything that goes beyond a text string can't be returned as a result.
The following example uses the procedural interface to query an audio CD for
it's table of contents.
////////////////////////////////
// cdtoc - audio CD toc sample
//
// using emx 0.8h, mm4emx 1.0
//
//
// Marc E.E. van Woerkom, 2/94
//
#include <os2.h>
#define INCL_OS2MM
#define INCL_MCIOS2
#include <os2me.h>
#include <iostream.h>
#include <iomanip.h>
//
// mci_err: translate the MCI return code into an error string
//
void mci_err(ULONG rc)
{
const rsize = 128;
char rbuff[rsize];
ULONG rc2 = mciGetErrorString(rc, // error code
rbuff, // return buffer
rsize); // rbuff size
if (rc2 == MCIERR_SUCCESS)
cerr << "MCI error: " << rbuff << "\n\n";
else
cerr << "error #" << rc << " has occured!\n\n";
}
This function prints out a time given in MMTIME format. It employs the helper
macros ULONG_LWLB, ULONG_LWHB and ULONG_HWLB to get the information out of the
ULONG.
//
// print_mmtime: print time given in MMTIME as hh.mm.ss
//
void print_mmtime(ULONG mmtime)
{
ULONG hms = HMSFROMMM(mmtime);
// hms packing is: |--|ss|mm|hh|
int hour = int(ULONG_LWLB(hms));
int min = int(ULONG_LWHB(hms));
int sec = int(ULONG_HWLB(hms));
if (hour)
cout << setw(4) << setfill('0')
<< hour << '.'
<< setfill('0');
else
cout << setfill(' '); // I believe this shouldn't be neccessary
cout << setw(2) << min << '.';
cout << setw(2) << setfill('0')
<< sec
<< setfill(' '); // this neither
}
//
// main
//
int main()
{
cout << "cdtoc -- Audio CD Table of Contents\n\n";
// open the audio CD device
Each MCI command has its specific parameter structure. All unused fields
should be set to 0.
The device to work with (CDaudio) is identified via its name.
MCI_OPEN_PARMS mop;
mop.hwndCallback = 0;
mop.usDeviceID = 0;
mop.pszDeviceType = MCI_DEVTYPE_CD_AUDIO_NAME;
mop.pszElementName = 0;
The first parameter of mciSendCommand() is the ID of the device. In this
special case it's not necessary - the ID is returned to a field of the
parameter structure.
Next is a message specifying the command, MCI_OPEN in this case.
The third parameter contains some so-called message flags. Here they tell the
MDM to wait until the open action is completed and to open it in shared mode.
Then a pointer to the parameter structure is given.
The last parameter is usually 0.
ULONG rc = mciSendCommand(0,
MCI_OPEN, // open message
MCI_WAIT | MCI_OPEN_SHAREABLE, // message flags
&mop, // parameters
0);
if (rc != MCIERR_SUCCESS) {
mci_err(rc);
return 1;
}
Now we issue a GETTOC command. It's not accessible from the textual MCI,
because it returns a bunch of information that doesn't fit into a simple line
of text. (Let's hope there is no CD with more than 99 tracks.)
// ask for the table of contents
const MAXTOCRECS = 99;
MCI_TOC_REC mtr[MAXTOCRECS];
MCI_TOC_PARMS mtp;
mtp.hwndCallback = 0;
mtp.pBuf = mtr;
mtp.ulBufSize = sizeof(mtr);
Note that this time (like in most cases) the device to work with is specified
via the ID obtained from MCI_OPEN
rc = mciSendCommand(mop.usDeviceID, // device ID
MCI_GETTOC, // get toc message
MCI_WAIT, // message flags
&mtp, // parameters
0);
if (rc != MCIERR_SUCCESS) mci_err(rc);
// close the device
MCI_CLOSE doesn't have any special parameters, so the parameter structure is of
the type MCI_GENERIC_PARMS:
MCI_GENERIC_PARMS mgp;
mgp.hwndCallback = 0;
ULONG rc2 = mciSendCommand(mop.usDeviceID, MCI_CLOSE, MCI_WAIT, &mgp, 0);
if (rc2 != MCIERR_SUCCESS) mci_err(rc2);
// now show the TOC, if been successful
if (rc == MCIERR_SUCCESS) {
OK, MCI_GETTOC was successful, so print out the obtained toc entries:
int i = 0;
while (mtr[i].TrackNum) {
cout << "Track" << setw(3);
cout << int(mtr[i].TrackNum)
<< ": Length ";
print_mmtime(mtr[i].ulEndAddr - mtr[i].ulStartAddr);
cout << " [";
print_mmtime(mtr[i].ulStartAddr);
cout << " to ";
print_mmtime(mtr[i].ulEndAddr);
cout << "] Control " << int(mtr[i].Control)
<< ", Country " << mtr[i].usCountry
<< ", Owner " << mtr[i].ulOwner
<< ", #" << mtr[i].ulSerialNum << "\n";
i++;
}
}
// that's all folks!
return 0;
}
Now let's try CDTOC.EXE on a certain audio CD with "Gorgeous Gals",
"Transsylvanian Parties" etc. :-)
cdtoc -- Audio CD Table of Contents
Track 1: Length 4.32 [ 0.02 to 4.34] Control 1, Country 0, Owner 0, #0
Track 2: Length 2.46 [ 4.34 to 7.21] Control 1, Country 0, Owner 0, #0
Track 3: Length 2.45 [ 7.21 to 10.06] Control 1, Country 0, Owner 0, #0
Track 4: Length 3.19 [10.06 to 13.26] Control 1, Country 0, Owner 0, #0
Track 5: Length 3.24 [13.26 to 16.50] Control 1, Country 0, Owner 0, #0
Track 6: Length 2.12 [16.50 to 19.02] Control 1, Country 0, Owner 0, #0
Track 7: Length 3.04 [19.02 to 22.07] Control 1, Country 0, Owner 0, #0
Track 8: Length 1.48 [22.07 to 23.55] Control 1, Country 0, Owner 0, #0
Track 9: Length 2.31 [23.55 to 26.27] Control 1, Country 0, Owner 0, #0
Track 10: Length 2.46 [26.27 to 29.14] Control 1, Country 0, Owner 0, #0
Track 11: Length 8.19 [29.14 to 37.33] Control 1, Country 0, Owner 0, #0
Track 12: Length 2.54 [37.33 to 40.28] Control 1, Country 0, Owner 0, #0
Track 13: Length 3.04 [40.28 to 43.32] Control 1, Country 0, Owner 0, #0
Track 14: Length 1.31 [43.32 to 45.04] Control 1, Country 0, Owner 0, #0
Yup, this is the table of contents of the Rocky Horror Picture Show soundtrack!
I chose this CD because track 11 has 3 subtracks (2.46, 3.34, 1.53). However
they don't show up here, so I have to guess further what the Control field is
good for ("track control field").
ΓòÉΓòÉΓòÉ 2.1.7. Querying MMPM/2 System Values ΓòÉΓòÉΓòÉ
Querying MMPM/2 System Values
Remember PM's WinQuerySysValue() API call? MMPM/2 has a counterpart named
mciQuerySysValue(). But in contrast to PM there are only less than a dozen
MMPM/2 system values defined, which the following example displays.
///////////////////////////////////////////
// mmpmvals - MMPM/2 system values sample
//
// using emx 0.8h, mm4emx 1.0
//
//
// Marc E.E. van Woerkom, 2/94
//
#include <os2.h>
#define INCL_OS2MM
#define INCL_MCIOS2
#include <os2me.h>
#include <iostream.h>
//
// main
//
int main()
{
cout << "mmpmvals -- MMPM/2 System Values\n\n";
BOOL ClosedCaption;
mciQuerySysValue(MSV_CLOSEDCAPTION, &ClosedCaption);
cout << "MSV_CLOSEDCAPTION : " << int(ClosedCaption) << "\n";
ULONG MasterVolume;
mciQuerySysValue(MSV_MASTERVOLUME, &MasterVolume);
cout << "MSV_MASTERVOLUME : " << int(MasterVolume) << "\n";
ULONG Headphones;
mciQuerySysValue(MSV_HEADPHONES, &Headphones);
cout << "MSV_HEADPHONES : " << int(Headphones) << "\n";
ULONG Speakers;
mciQuerySysValue(MSV_SPEAKERS, &Speakers);
cout << "MSV_SPEAKERS : " << int(Speakers) << "\n";
CHAR WorkPath[CCHMAXPATH];
mciQuerySysValue(MSV_WORKPATH, WorkPath);
cout << "MSV_WORKPATH : " << WorkPath << "\n";
ULONG SysqOsValue;
mciQuerySysValue(MSV_SYSQOSVALUE, &SysqOsValue);
cout << "MSV_SYSQOSVALUE : " << int(SysqOsValue) << "\n";
ULONG SysqOsErrorFlag;
mciQuerySysValue(MSV_SYSQOSERRORFLAG, &SysqOsErrorFlag);
cout << "MSV_SYSQOSERRORFLAG: " << int(SysqOsErrorFlag) << "\n";
// that's all folks!
return 0;
}
Running MMPMVALS.EXE on my system yields:
mmpmvals -- MMPM/2 System Values
MSV_CLOSEDCAPTION : 1
MSV_MASTERVOLUME : 100
MSV_HEADPHONES : 1
MSV_SPEAKERS : 1
MSV_WORKPATH : C:\MMOS2
MSV_SYSQOSVALUE : 65537
MSV_SYSQOSERRORFLAG: 2
ΓòÉΓòÉΓòÉ 2.1.8. A First Rendezvous with the MMIO Subsystem ΓòÉΓòÉΓòÉ
A First Rendezvous with the MMIO Subsystem
The MMIO subsystem is responsible for I/O on multimedia files. It comes with
several I/O procedures installed, which can handle specific kinds of data. The
example below will show the installed MMIO procedures.
//////////////////////////////////////////
// mmiofmts - MMPM/2 mmio formats sample
//
// using emx 0.8h, mm4emx 1.0
//
//
// Marc E.E. van Woerkom, 2/94
//
#include <os2.h>
#define INCL_OS2MM
#include <os2me.h>
#include <iostream.h>
#include <iomanip.h>
Wrap MMFORMATINFO into a C++ class.
//
// mmformatinfo
//
class mmformatinfo {
MMFORMATINFO mmfi;
public:
mmformatinfo(FOURCC IOProc=0);
MMFORMATINFO* get_addr() { return &mmfi; }
LONG get_NameLength() { return mmfi.lNameLength; }
PSZ get_DefaultFormatExt() { return mmfi.szDefaultFormatExt; }
FOURCC get_IOProc() { return mmfi.fccIOProc; }
};
This constructor clears the MMFORMATINFO structure and initializes the
fccIOProc field with the proper four character code specific to the MMIO
procedure and the format it handles. Use 0 as default argument.
mmformatinfo::mmformatinfo(FOURCC IOProc=0)
{
char* p = (char*) &mmfi;
for (int i=0; i<sizeof(mmfi); i++) p[i] = 0;
mmfi.fccIOProc = IOProc;
}
//
// mmio_err: translate MMIO error code into a string
//
void mmio_err(ULONG rc)
{
cerr << "MMIO error: ";
char* s;
switch (rc) {
case MMIO_SUCCESS:
s = "SUCCESS (huh?)";
break;
case MMIOERR_UNBUFFERED:
s = "UNBUFFERD";
break;
case MMIOERR_INVALID_HANDLE:
s = "INVALID HANDLE";
break;
case MMIOERR_INVALID_PARAMETER:
s = "INVALID PARAMETER";
break;
case MMIOERR_READ_ONLY_FILE:
s = "READ ONLY FILE";
break;
case MMIOERR_WRITE_ONLY_FILE:
s = "WRITE ONLY FILE";
break;
case MMIOERR_WRITE_FAILED:
s = "WRITE FAILED";
break;
case MMIOERR_READ_FAILED:
s = "READ FAILED";
break;
case MMIOERR_SEEK_FAILED:
s = "SEEK FAILED";
break;
case MMIOERR_NO_FLUSH_NEEDED:
s = "NO FLUSH NEEDED";
break;
case MMIOERR_OUTOFMEMORY:
s = "OUT OF MEMORY";
break;
case MMIOERR_CANNOTEXPAND:
s = "CANNOT EXPAND";
break;
case MMIOERR_FREE_FAILED:
s = "FREE FAILED";
break;
case MMIOERR_CHUNKNOTFOUND:
s = "CHUNK NOT FOUND";
break;
case MMIO_ERROR:
s = "ERROR";
break;
case MMIO_WARNING:
s = "WARNING";
break;
case MMIO_CF_FAILURE:
s = "CF FAILURE";
break;
default:
cerr << rc;
s = " (hmm...)";
}
cerr << s << "\n";
}
//
// main procedure
//
//
// WARNING: The MMPM/2 stops working on my system
// if I use a string as argument for the FOURCC mask
// which is none of the registered ones! (e.g. wuff)
//
// Looks like a MMPM/2 bug to me.
// (A major confusion of mmio.dll?)
//
int main(int argc, char* argv[])
{
cout << "mmiofmts -- MMPM/2 MMIO Formats\n\n";
// parse args
FOURCC mask = 0;
if (argc>1) {
The mmioStringToFOURCC() API call translates a string into a four character
code.
mask = mmioStringToFOURCC(argv[1], MMIO_TOUPPER);
A FOURCC is a 32-bit variable, containing 4 characters.
cout << "mask in use is [";
char* p = (char*) &mask;
for (int i=0; i<4; i++) cout << p[i];
cout << "]\n\n";
}
// query # of IOProcedures
mmformatinfo mmfi_spec(mask);
ULONG NumFormats = 0;
mmioQueryFormatCount() returns the number of installed MMIO procedures.
ULONG rc = mmioQueryFormatCount(mmfi_spec.get_addr(),
&NumFormats,
0,
0);
if (rc != MMIO_SUCCESS) {
mmio_err(rc);
return 1;
}
cout << "formats supported: " << NumFormats;
if (!NumFormats) return 0;
// get formats
Get all format information via mmioGetFormats().
mmformatinfo* mmfip = new mmformatinfo[NumFormats];
ULONG FormatsRead = 0;
rc = mmioGetFormats(mmfi_spec.get_addr(),
NumFormats,
mmfip,
&FormatsRead,
0,
0);
if (rc != MMIO_SUCCESS) {
mmio_err(rc);
return 2;
}
cout << " (" << FormatsRead << " formats read)\n\n";
// print information
cout << "no. 4-cc name leng extn\n\n";
for (int i=0; i<NumFormats; i++) {
cout.setf(ios::right, ios::adjustfield);
cout << setw(2) << i+1 << ": [";
FOURCC IOProc = mmfip[i].get_IOProc();
char* p = (char*) &IOProc;
for (int j=0; j<4; j++) cout << p[j];
cout << "] ";
LONG NameLength = mmfip[i].get_NameLength();
if (NameLength) {
PSZ name = new CHAR[NameLength+1];
LONG BytesRead = 0;
Extract the name of the format via mmioGetFormatName().
rc = mmioGetFormatName(mmfip[i].get_addr(),
name,
&BytesRead,
0,
0);
name[NameLength] = 0;
if (rc != MMIO_SUCCESS) {
mmio_err(rc);
cout << " ";
}
else {
cout.setf(ios::left, ios::adjustfield);
cout << setw(40) << name << " ("
<< setw(2) << BytesRead;
}
delete[] name;
}
else
cout << "-" << setw(43) << "( 0";
cout.setf(ios::left, ios::adjustfield);
cout << ") ."
<< setw(3) << mmfip[i].get_DefaultFormatExt() << "\n";
}
delete[] mmfip;
// that's all folks!
return 0;
}
So let's run mmiofmts.exe to display the currently installed MMIO procedures:
mmiofmts -- MMPM/2 MMIO Formats
formats supported: 15 (15 formats read)
no. 4-cc name leng extn
1: [RDIB] RIFF DIB Image (14) .RDI
2: [AVCI] IBM AVC Still Video Image (25) ._IM
3: [MMOT] IBM MMotion Still Video Image (29) .VID
4: [AVCA] IBM AVC ADPCM Digital Audio (27) ._AU
5: [VOC ] Creative Labs Voice File (24) .VOC
6: [WI30] MS Windows DIB Image (20) .DIB
7: [MIDI] Midi File Format I/O Procedure (30) .MID
8: [OS13] IBM OS/2 1.3 PM Bitmap Image (28) .BMP
9: [OS20] IBM OS/2 2.0 BMP (16) .BMP
10: [AVI ] AVI IO Procedure (16) .AVI
11: [WAVE] RIFF WAVE Digital Audio (23) .WAV
12: [CF ] - ( 0) .
13: [BND ] - ( 0) .BND
14: [MEM ] - ( 0) .MEM
15: [DOS ] - ( 0) .DOS
Save this utility for the next part of this article series when we go to
install our own MMIO procedure!
ΓòÉΓòÉΓòÉ 2.1.9. Memory Playlists ΓòÉΓòÉΓòÉ
Memory Playlists
Using an MCI script, playing several soundfiles is not smooth, for the
waveaudio device must load each soundfile (element) into its context, delaying
the play. A possible work-around is to load the elements into memory before
playing starts. This can be achieved using the MMIO waveaudio I/O procedure
and the MMPM/2 waveaudio playlist processor.
Loading the waveaudio file via the MMIO subsystem into memory has the advantage
that one can extract the necessary data without knowing much about the maybe
complicated internal structure of such a file. The .WAV format is a special
kind of the more general RIFF (Resource Interchange File Format) multimedia
format. For example, the M1.WAV file is 26504 bytes long, but contains only
26460 bytes of pure sound data. Using MMIO we don't have to worry where it is
located in the file.
The usual encoding scheme for sound data is pulse code modulation (PCM). This
means at a fixed rate per second (the sampling frequency) the amplitude of the
signal of each channel is converted by an audio to digital converter into a
number. The M1.WAV file was sampled in mono with 11kHz and 8 bit resolution,
so it contains 26460 bytes/(11000 bytes/s) = 2.4s worth of audio data.
Note: once loaded into memory, one can easily work with this data in ways such
as applying a compression scheme to it, mixing in a second waveaudio file (take
the mean of both amplitude values) if you want to play more than one sound at
once, adding effects such as echoing (rescale the amplitudes and mix this data
with a short delay over the original data), etc.
A playlist is an array of playlist instructions, which is processed by the
playlist processor. Among those instructions is not only the command to play a
waveaudio file residing in memory, but also flow control instructions which
allow jumps, loops and subroutines.
Look at this example, which employs a playlist to play a certain rhythm :-)
///////////////////////////////////////
// rhythm - waveaudio playlist sample
//
// using emx 0.8h, mm4emx 1.0
//
//
// Marc E.E. van Woerkom, 2/94
//
#include <os2.h>
#define INCL_OS2MM
#include <os2me.h>
#include <iostream.h>
#include <iomanip.h>
// prototypes
void mci_err(ULONG);
void mmio_err(ULONG);
The MMAUDIOHEADER structure will get the header information of the waveaudio
file from the MMIO.
//
// mmaudioheader
//
class mmaudioheader {
MMAUDIOHEADER mmah;
public:
mmaudioheader();
MMAUDIOHEADER* get_addr() { return &mmah; }
LONG get_size() { return sizeof(mmah); }
};
mmaudioheader::mmaudioheader()
{
char* p = (char*) &mmah;
for (int i=0; i<sizeof(mmah); i++) p[i] = 0;
}
The constructor of the mem_wav class will load a waveaudio file (given by its
filename) into memory via the MMIO subsystem.
//
// mem_wav: a waveaudio file loaded into memory
//
class mem_wav {
HMMIO hmmio;
PSZ bptr;
ULONG bsize;
ULONG SamplesPerSec;
USHORT BitsPerSample;
public:
mem_wav(char*);
~mem_wav();
PSZ get_bptr() { return bptr; }
ULONG get_bsize() { return bsize; }
ULONG get_SamplesPerSec() { return SamplesPerSec; }
USHORT get_BitsPerSample() { return BitsPerSample; }
};
mem_wav::mem_wav(char* name)
{
The MMIO subsystem looks at the .WAV extension and calls the proper I/O
procedure to open it, delivering a handle as the result.
// open the file
hmmio = mmioOpen(name, 0, MMIO_READ);
Now get the header information of the waveaudio file.
// get header
mmaudioheader mmah;
ULONG BytesRead = 0;
ULONG rc = mmioGetHeader(hmmio, mmah.get_addr(), mmah.get_size(),
&BytesRead, 0, 0);
if (rc != MMIO_SUCCESS) mmio_err(rc);
The header contains the length in bytes (needed to allocate the buffer memory),
the sampling frequency and the sampling resolution (these settings are needed
for a proper reproduction).
// get some infos about the waveaudio file
SamplesPerSec = mmah.get_addr()->mmXWAVHeader.WAVEHeader.ulSamplesPerSec;
BitsPerSample = mmah.get_addr()->mmXWAVHeader.WAVEHeader.usBitsPerSample;
bsize = mmah.get_addr()->mmXWAVHeader.XWAVHeaderInfo.ulAudioLengthInBytes;
bptr = new CHAR[bsize];
The buffer is allocated, now read all information into it.
// read file
rc = mmioRead(hmmio, bptr, bsize);
if (rc == MMIO_ERROR) mmio_err(rc);
cout << "[file " << name
<< ": read" << setw(7) << rc
<< " bytes of" << setw(7) << bsize << "]\n";
Finally close the file. That's all! (And we didn't need to know anything
about RIFF chunks, etc.)
// close file
rc = mmioClose(hmmio, 0);
if (rc != MMIO_SUCCESS) mmio_err(rc);
}
The destructor of the class gets rid of the allocated memory resources.
mem_wav::~mem_wav()
{
delete[] bptr;
}
Now comes the playlist. A playlist entry is composed of four ULONG variables.
The first represents the instruction and the others are its possible arguments.
//
// ple: a playlist entry
//
struct ple {
ULONG operation;
ULONG operand1;
ULONG operand2;
ULONG operand3;
};
This class represents a playlist. It should be stated that it is tailored for
this special example. (For it reads exactly 7 different samples and the
playlist is hardcoded into the setup() member function. A general class of
this kind should be able to deal with a variable amount of samples and should
read the playlist from a data file or a resource block.)
//
// playlist: a waveaudio playlist
//
class playlist {
ple* pl;
int size, used;
mem_wav& m1, m2, m3, m4, m5, s1, p1;
void setup();
public:
playlist(mem_wav&, mem_wav&, mem_wav&, mem_wav&,
mem_wav&, mem_wav&, mem_wav&, int);
~playlist();
ple* get_addr() { return pl; }
int add(ULONG =0, ULONG =0, ULONG =0, ULONG =0);
int add_branch(ULONG);
int add_call(ULONG);
int add_data(mem_wav&);
int add_exit();
int add_return();
};
Allocate the playlist entries.
playlist::playlist(mem_wav& M1, mem_wav& M2, mem_wav& M3,
mem_wav& M4, mem_wav& M5, mem_wav& S1, mem_wav& P1,
int Size)
: m1(M1), m2(M2), m3(M3), m4(M4), m5(M5), s1(S1), p1(P1),
size(Size)
{
if (size < 1) cerr << "error: wrong playlist size!\n";
pl = new ple[size];
used = 0;
setup();
}
playlist::~playlist()
{
delete[] pl;
}
This member function will fill a playlist entry with the proper values. Note
the default arguments. And it returns the number of the current entry which
will come in handy when employed in setup().
int playlist::add(ULONG op=0, ULONG opd1=0, ULONG opd2=0, ULONG opd3=0)
{
if (used >= size) {
cerr << "error: playlist is too small!\n";
return -1;
}
pl[used].operation = op;
pl[used].operand1 = opd1;
pl[used].operand2 = opd2;
pl[used].operand3 = opd3;
return used++;
}
A branch operation (jump to a specific entry).
int playlist::add_branch(ULONG addr)
{
return add(BRANCH_OPERATION, 0, addr);
}
A call operation (call a playlist subroutine).
int playlist::add_call(ULONG addr)
{
return add(CALL_OPERATION, 0, addr);
}
A data operation (play a waveaudio file from a buffer).
int playlist::add_data(mem_wav& mw)
{
return add(DATA_OPERATION, ULONG(mw.get_bptr()), mw.get_bsize());
}
An exit operation (end the playlist processing).
int playlist::add_exit()
{
return add(EXIT_OPERATION);
}
A return operation (return from a playlist subroutine).
int playlist::add_return()
{
return add(RETURN_OPERATION);
}
This is a hardwired playlist. Note that is ordered in a way that only one
forward reference (70) is needed.
void playlist::setup()
{
Jump to the 70th playlist entry.
add_branch(70);
This is one of several subroutines. It plays the buffer containing the audio
data of M1.WAV thrice.
ULONG Intro = add_data(m1);
add_data(m1);
add_data(m1);
add_return();
ULONG A10a = add_data(m2);
add_data(s1);
add_data(p1);
add_data(m3);
add_data(m4);
add_data(m5);
add_data(m4);
add_data(m5);
add_data(m4);
add_data(m5);
add_return();
ULONG A10 = add_data(m2);
add_data(s1);
add_data(p1);
add_data(m5);
add_data(m4);
add_data(m5);
add_data(m4);
add_data(m5);
add_data(m4);
add_data(m5);
add_return();
ULONG B10 = add_data(m2);
add_data(s1);
add_data(p1);
add_data(m1);
add_data(m1);
add_data(m1);
add_data(m1);
add_data(m1);
add_data(m1);
add_data(m1);
add_return();
ULONG C10 = add_data(m2);
add_data(s1);
add_data(p1);
add_data(m2);
add_data(m2);
add_data(m2);
add_data(m2);
add_data(m2);
add_data(m2);
add_data(m2);
add_return();
ULONG A6 = add_data(m2);
add_data(s1);
add_data(p1);
add_data(m5);
add_data(m4);
add_data(m5);
add_return();
ULONG B6 = add_data(m2);
add_data(s1);
add_data(p1);
add_data(m1);
add_data(m1);
add_data(m1);
add_return();
ULONG C6 = add_data(m2);
add_data(s1);
add_data(p1);
add_data(m2);
add_data(m2);
add_data(m2);
add_return();
Well, I didn't count until here. I simply printed out the return code of the
next call in a prior version of this source and noted it.
// #70
add_call(Intro);
add_call(A10a);
add_call(B10);
add_call(A10);
add_call(B10);
add_call(A10);
add_call(C10);
add_call(A6);
add_call(B6);
add_call(A6);
add_call(C10);
add_call(A10);
add_call(C10);
add_call(A10);
add_call(A10);
add_call(A10);
add_call(C10);
add_call(A6);
add_call(C6);
add_call(A6);
add_call(B10);
add_data(s1);
add_exit();
}
This class represents the waveaudio device together with an associated
playlist. The characteristics of the mem_wav given to the constructor are used
for the processing of the whole playlist.
//
// waveaudio: a waveaudio device
//
class waveaudio {
MCI_OPEN_PARMS mop;
public:
waveaudio(playlist&, mem_wav&);
~waveaudio();
void play();
};
waveaudio::waveaudio(playlist& pl, mem_wav& mw)
{
Open the waveaudio device via an MCI command message in a way that it will use
a playlist as data.
// open device
mop.hwndCallback = 0;
mop.usDeviceID = 0;
mop.pszDeviceType = MCI_DEVTYPE_WAVEFORM_AUDIO_NAME;
mop.pszElementName = PSZ(pl.get_addr());
ULONG rc = mciSendCommand(0,
MCI_OPEN, // open message
MCI_WAIT | MCI_OPEN_SHAREABLE | // message flags
MCI_OPEN_PLAYLIST,
&mop, // parameters
0);
if (rc != MCIERR_SUCCESS) mci_err(rc);
If these values aren't set via MCI_SET, the waveaudio data will be played with
improper speed or even be garbled.
// set device parameters
MCI_WAVE_SET_PARMS wsp;
wsp.hwndCallback = 0;
wsp.ulSamplesPerSec = mw.get_SamplesPerSec();
wsp.usBitsPerSample = mw.get_BitsPerSample();
rc = mciSendCommand(mop.usDeviceID,
MCI_SET,
MCI_WAIT |
MCI_WAVE_SET_SAMPLESPERSEC |
MCI_WAVE_SET_BITSPERSAMPLE,
&wsp,
0);
if (rc != MCIERR_SUCCESS) mci_err(rc);
}
Close the waveaudio device.
waveaudio::~waveaudio()
{
// close device
MCI_GENERIC_PARMS mgp;
mgp.hwndCallback = 0;
ULONG rc = mciSendCommand(mop.usDeviceID,
MCI_CLOSE,
MCI_WAIT,
&mgp,
0);
if (rc != MCIERR_SUCCESS) mci_err(rc);
}
Set the waveaudio device on 'play'.
void waveaudio::play()
{
// play the playlist
MCI_PLAY_PARMS mpp;
mpp.hwndCallback = 0;
ULONG rc = mciSendCommand(mop.usDeviceID,
MCI_PLAY,
MCI_WAIT,
&mpp,
0);
if (rc != MCIERR_SUCCESS) mci_err(rc);
}
Routines to print pretty error messages.
//
// mci_err: translate the MCI return code into an error string
//
void mci_err(ULONG rc)
{
const rsize = 128;
char rbuff[rsize];
ULONG rc2 = mciGetErrorString(rc, // error code
rbuff, // return buffer
rsize); // rbuff size
if (rc2 == MCIERR_SUCCESS)
cerr << "MCI error: " << rbuff << "\n\n";
else
cerr << "error #" << rc << " has occured!\n\n";
}
//
// mmio_err: translate MMIO error code into a string
//
void mmio_err(ULONG rc)
{
cerr << "MMIO error: ";
char* s;
switch (rc) {
case MMIO_SUCCESS:
s = "SUCCESS (huh?)";
break;
case MMIOERR_UNBUFFERED:
s = "UNBUFFERD";
break;
case MMIOERR_INVALID_HANDLE:
s = "INVALID HANDLE";
break;
case MMIOERR_INVALID_PARAMETER:
s = "INVALID PARAMETER";
break;
case MMIOERR_READ_ONLY_FILE:
s = "READ ONLY FILE";
break;
case MMIOERR_WRITE_ONLY_FILE:
s = "WRITE ONLY FILE";
break;
case MMIOERR_WRITE_FAILED:
s = "WRITE FAILED";
break;
case MMIOERR_READ_FAILED:
s = "READ FAILED";
break;
case MMIOERR_SEEK_FAILED:
s = "SEEK FAILED";
break;
case MMIOERR_NO_FLUSH_NEEDED:
s = "NO FLUSH NEEDED";
break;
case MMIOERR_OUTOFMEMORY:
s = "OUT OF MEMORY";
break;
case MMIOERR_CANNOTEXPAND:
s = "CANNOT EXPAND";
break;
case MMIOERR_FREE_FAILED:
s = "FREE FAILED";
break;
case MMIOERR_CHUNKNOTFOUND:
s = "CHUNK NOT FOUND";
break;
case MMIO_ERROR:
s = "ERROR";
break;
case MMIO_WARNING:
s = "WARNING";
break;
case MMIO_CF_FAILURE:
s = "CF FAILURE";
break;
default:
cerr << rc;
s = " (hmm...)";
}
cerr << s << "\n";
}
Main function. Perhaps you should start reading this example from here.
//
// main
//
int main()
{
cout << "rhythm -- a Rhythm Generator\n\n";
// load waveaudio files into memory
cout << "loading waveaudio files into memory ...\n\n";
mem_wav m1("m1.wav");
mem_wav m2("m2.wav");
mem_wav m3("m3.wav");
mem_wav m4("m4.wav");
mem_wav m5("m5.wav");
mem_wav s1("s1.wav");
mem_wav p1("p1.wav");
// set up playlist
cout << "\nsetting up playlist ...\n\n";
playlist pl(m1, m2, m3, m4, m5, s1, p1, 100);
// play playlist
cout << "and ... go! (you should pump up the volume :-)\n\n";
waveaudio wav(pl, m1);
wav.play();
// that's all folks!
cout << "... done. yeah!\n";
return 0;
}
Try it! :-)
Note that this example is not too far away from the .MOD file playing
mechanism. The hardest thing for an extension in this direction is probably
getting the proper .MOD file definition. Since there was an unconfirmed report
on Usenet that IBM may deliver a .MOD MMIO procedure in the next MMPM/2
release, I personally won't put time into something like that.
ΓòÉΓòÉΓòÉ 2.1.10. What's Next? ΓòÉΓòÉΓòÉ
What's Next?
At least two important topics are still on my list:
o Expect to see more on the MMIO subsystem. Procedures for handling compressed
waveaudiofiles are under development.
o The PM extensions (graphical buttons, circular sliders and the secondary
windows support) cry for some nice examples.
ΓòÉΓòÉΓòÉ 2.1.11. More Literature on MMPM/2 ΓòÉΓòÉΓòÉ
More Literature on MMPM/2
IBM Doc. S53G-2166:
OS/2 Online Book Collection CD-ROM.
This CD-ROM contains 144 different OS/2 manuals in *.boo format and
readers for OS/2 and DOS. (Highly recommended!)
ΓòÉΓòÉΓòÉ 2.2. Porting STEP02 to ICLUI ΓòÉΓòÉΓòÉ
ΓòÉΓòÉΓòÉ 2.2.1. Introduction ΓòÉΓòÉΓòÉ
Written by Kenton W. Shaver
Introduction
This article describes the translation of Gavin Baker's STEP02.C to the IBM's
C++ libraries for PM (ICLUI). Hopefully it will assist the programmer who is
now learning these libraries or is attempting such ports of their own.
The result - CLOCK.EXE - requires the C-Set++ DLLs to run, as it is linked
dynamically. A slightly modified copy of the original program that will
compile under C-Set++ is also included so that the reader can conduct their own
performance analysis and browsing sessions with the original program and the
resulting port, if desired.
Scope
I'm assuming that you have read Gavin Baker's article in volume 1, issue 2,
that you basically understand Presentation Manager programming, and that you
have perused the recent articles here appearing about ICLUI [by Gordon
Zeglinski, in volume 2, issue 1 - editor] detailing the IEvent, IWindow, and
the IHandler clases, etc. Some important issues will be mentioned again for
easier retention.
ΓòÉΓòÉΓòÉ 2.2.2. Overall Structure ΓòÉΓòÉΓòÉ
Overall Structure
In the ported program, a main window is created which in turn creates a client
window, a menu window, and an object window for its own use; with the exception
of the object window, this is what the original program does also. The object
window launches a thread in charge of keeping time and updating the main
window's client window at one second intervals. The messages exchanged between
the frame window and its object window don't figure for much, though,
especially since no easy method to thread the message processing loop of the
class myObjectWindow is provided; this device is left in place for
illustration.
The two other classes introduced here modify IFrameHandler so that it processes
WM_SYSCOMMAND events and the creation of another IHandler class capable of
processing WM_QUERYTRACKINFO events.
The program itself does basically the same thing as STEP02.C did, with the
exceptions that it uses DosSleep() instead of WinTimer() to provide the timer
support and it subclasses the frame window so that it cannot be resized below
certain dimensions.
ΓòÉΓòÉΓòÉ 2.2.3. Subclassing ΓòÉΓòÉΓòÉ
Subclassing
What do we mean when we say that our program uses subclassing? Do we mean
subclass in the C++ sense, or in the PM sense? In the C++ sense, we might be
indicating that we have used inheritance in our program, while in the PM sense,
we are saying that we have replaced an existing window procedure, thus
modifying the behavior of the object in question.
Our program does the latter, as illustrated by the following snippet:
Boolean mainWindow::trackAction(queryTrackEvent &qte)
{
IWindow::defaultProcedure(qte);
grabMinTrackSize(qte).x *= 3;
grabMinTrackSize(qte).y *=5;
return true; // don't call defaultProcedure again!! thanks
}
A member function of an IHandler derived class returns true when no further
processing of an event is needed. This subclassing method returns true each
time it is called because it has already called IWindow::defaultProcedure(),
IBM's C++ equivalent of WinDefWindowProc().
Because of this method of subclassing, WinSubclassWindow() isn't needed -
that's all there is to it.
ΓòÉΓòÉΓòÉ 2.2.4. Window Procedures in ICLUI ΓòÉΓòÉΓòÉ
Window Procedures in ICLUI
When looking at the definition of the myObjectWindow class, we see that it only
needs a dispatchHandlerEvent() procedure to handle events. This member can make
no assumptions about what type of events it will be getting.
In contrast, command() can assume that it will only be sent WM_COMMAND events,
systemCommand() can be sure it will only get WM_SYSCOMMAND events, and
trackAction() will only be called on to handle WM_QUERYTRACKINFO events. These
methods don't need to look at the event ID, but in the case of
myObjectWindow::dispatchHandlerEvent(), we have to actually look at the event
ID and use a switch block.
ΓòÉΓòÉΓòÉ 2.2.5. The About Box ΓòÉΓòÉΓòÉ
The About Box Figure 1) CLOCK.CPP about box
Modifying the original about box presents little trouble. First, STEP02.RES is
loaded into the Dialog Editor. Be sure to specify MSGDEFINES.H as included in
your resource file. We will rename STEP02.RC to CLOCK.RC to distinguish the old
from the new.
Now, since we are not going to have any children of aboutBoxClass - the class
we'll represent the about box with in our program, it would certainly be okay
to inherit from IHandler and then define a new dispatchHandlerEvent() method to
process the few messages that an about box is concerned with; since all of them
will be WM_COMMAND messages anyway, we'll inherit handler functionality from
the ICommandHandler class.
In either case, IHandler::handleEventsFor() initiates event processing.
ΓòÉΓòÉΓòÉ 2.2.6. General Porting Cautions ΓòÉΓòÉΓòÉ
General Porting Cautions
If you undertake a port of your own from C to C++ and the UI libraries, it
might save time to note several items.
o C++ has keywords that C doesn't, such as class. Be sure you leave the
#include <os2.h> statement outside of extern "C" blocks that you create and
that you have the \TOOLKT21\CPLUS\OS2H path specified in the environment
variable INCLUDE. If you use the C includes in your program, you might
encounter compile-time errors caused by leaving these additional reserved
words in.
o Be sure to include IHandler or an IHandler descendant along with such
IWindow-derived classes as IObjectWindow and IFrameWindow, whether it be by
ancestry or by membership.
o If you register both IFrameHandler and ICommandHandler they will both call
your command() method, and WM_COMMAND event processing will happen twice in
instances where a false is returned indicating that the event wasn't
processed. For example, if you just mark the occurrence of "Open file..."
menu selections with squawk(), each selection of this menu item will be noted
twice if both ICommandHandler and IFrameHandler are active.
o It is a good idea to copy the #define statements showing the hexadecimal
numbers of the messages that you are using in your program. "grep" or another
such tool will easily extract definitions of the window messages such as
WM_CLOSE from your \TOOLKT21\CPLUS\OS2H directory if need be.
o The definition of the TRACKINFO is copied from the headers because it isn't
included with what PM header information we use [this is included if you
#define INCL_WINTRACKRECT before #include-ing <os2.h>. Copying from the
header files should be avoided whenever possible to minimize the amount of
program maintenance when the definitions change - editor].
o Any programs that use the IThreadMemberFn<> template must be compiled and
linked to handle templates. If you use this template class and tell C-Set++
to ignore template information, your program will compile and run as normal
but it won't create the desired additional threads!
ΓòÉΓòÉΓòÉ 2.3. Workplace Shell Development 101 ΓòÉΓòÉΓòÉ
ΓòÉΓòÉΓòÉ 2.3.1. Introduction ΓòÉΓòÉΓòÉ
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.
ΓòÉΓòÉΓòÉ 2.3.2. What Do We Do Now That We Know What To Do? ΓòÉΓòÉΓòÉ
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.
For 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.
ΓòÉΓòÉΓòÉ 2.3.3. Nifty Additions ΓòÉΓòÉΓòÉ
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.
ΓòÉΓòÉΓòÉ 2.3.4. Implementation ΓòÉΓòÉΓòÉ
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.
Although malloc() works, avoid it whenever possible, and if you for whatever
reason must use malloc, be very careful. The last thing you want to hear your
customers say is that their whole system became sluggish and unstable after
installing your program.
ΓòÉΓòÉΓòÉ 2.3.5. Conclusion ΓòÉΓòÉΓòÉ
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!
ΓòÉΓòÉΓòÉ 3. Columns ΓòÉΓòÉΓòÉ
The following columns can be found in this issue:
o Book Review - Real World Programming for OS/2 2.1
o C++ Corner
o Introduction to PM Programming
o Scratch Patch
ΓòÉΓòÉΓòÉ 3.1. Book Review - Real World Programming for OS/2 2.1 ΓòÉΓòÉΓòÉ
ΓòÉΓòÉΓòÉ 3.1.1. Introduction ΓòÉΓòÉΓòÉ
Written by Carsten Whimster
Introduction
The Book Review column is a new monthly column which will focus on development
oriented books and material. This will mostly consist of programming books,
such as this month's Real World Programming for OS/2 2.1, but could also cover
books like The Design of OS/2. The column will be from a beginning PM
programmer's eyes (because that's what I am), and hopefully that is most useful
to the community in general. As I get on with the column, however, I expect to
get better, and so within a year or so, I will (presumably :) qualify as an
intermediate programmer, or perhaps even somewhat of an expert. Hopefully you,
the readers, will move with me in that respect. When reading this column, try
to pick up whichever book strikes your fancy, and join the group of people
following our introductory PM programming columns. I will try to review books
aimed at beginners to start with, and then move on from there.
The column will largely be demand-oriented (after I get my own books out of the
way early on), so if you have a book you would like reviewed, send me e-mail
and tell me about it, or even better, send me the book (sorry, no returns).
Finally, please send me your comments and thoughts so that I can make this
column as effective as possible. After all, this is our magazine.
This month's selection was chosen because it is the one of my books I like best
:). It was recommended to me on CompuServe, and has a good reputation as a
beginner's PM programming book.
ΓòÉΓòÉΓòÉ 3.1.2. Review ΓòÉΓòÉΓòÉ
Review
Real World Programming for OS/2 2.1 is a fairly hefty book at no less than 868
pages! It is aimed at (it says here, on the back) intermediate to advanced
(users or programmers?), and is meant as a PM programming how-to book. It
comes with a 3.5" diskette with all the source code from the book. It even has
its own installation program, and a very nice one at that. The authors
encourage you to use the source code from the book in your own programs,
although they do insist that enough modifications should have been made to make
the code basically your own work.
After installing the sample code, it is recommended that you set or check the
COMPILER, PATH, INCLUDE, and LIBPATH environment variables. I also found it
necessary to set the LIB environment variable. Makefiles are included for IBM
CSet, Borland C++,Watcom C/C++, Zortech C++, and Microsoft's C compiler. Once
the environment variables are set properly, each of the sample apps can be
recompiled in their own sub-directory by a file called GO.CMD, in case you
should want to try your own modifications to the sample code (which is
recommended by the authors.)
To familiarize you a little with the book, I have included the titles of the
chapters here:
1. Programming for OS/2
2. OS/2 Application Window Fundamentals
3. Window Management
4. 2.1 Common Dialogs
5. Menus
6. Presentation Spaces and Drawing
7. Creating and Manipulating PM Fonts and Text
8. Profile Management
9. Memory Management
10. Dynamic Link Libraries
11. Printing
12. Threads and Semaphores
13. Calling 16-Bit Code
14. Communication Basics
15. Miscellaneous Topics
I haven't read every chapter of course, but I have used material from a
surprising number of them. The authors recommend that you read chapters one
through four, and then go on to whatever chapters interest you, but I only made
it through the first three before curiousity and need took me elsewhere. All
the chapters have an introduction explaining the importance of the material
before launching into the code example. Each chapter has a sample program to
demonstrate the use of the various APIs involved, as well as showing a possible
application for these calls. Code from these sample programs is introduced in
the text of each chapter, and at or near the end of each chapter the full
source code is presented.
Although this book is large, it is not a complete coverage of the API, nor is
it meant as such. It is intended to get the reader started into various
important intermediate areas, and to expose what the authors consider the more
useful function calls. The book is sometimes a little sparse in its coverage
of some of the relevant data structures, but this is usually easily rectified
by cross-referencing with the IBM toolkit reference material.
Due to the vast nature of the complete OS/2 API, there are (and must be) areas
of interest which the authors for whatever reason, have decided to treat
lightly, or skip altogether. Unfortunately, the application I was working on
uses several features of the API which aren't covered well in the book. This
necessitated a lot of foot-work which might have been avoided otherwise. For
example, the TRACKINFO structure used to control frame windows is mentioned
only once, and briefly at that. I also found it difficult to extract the
correct information about pop-up menus from the complicated examples to build
my own fairly simple pop-up, but eventually I did succeed. Frequently, I found
that the sample code had a large number of intricately woven features which
made it quite tricky to pull out just the necessary bits. In spite of having a
good reputation for beginners, it probably is better off targeted at developers
who have used the PM API for a few months already, and who consequently have
the necessary understanding of the way things work to extract the needed parts,
all the needed parts, and nothing but the needed parts from the sample code.
Another small, but noticeable flaw is the index at the back. It has most, if
not all of the actual API names and data structures, but when you don't know
the name of what you are looking for (which presumably will happen frequently,
otherwise use the reference material instead of a programming book,) it can be
a tedious job trying to imagine what the name of a certain call or data
structure might be. Unfortunately this is quite common among books, and
programming books in particular, and is not just a fault with this book.
Despite these minor concerns, the book remains a favorite of mine, and I use it
frequently. The lucid writing, the good organization, and the depth of the
material make it enjoyable most of the time. I recommend this book to anyone
willing to dig a little, and who wants something a little more than an
introductory book, with useful sample code, and a great breadth of coverage.
ΓòÉΓòÉΓòÉ 3.1.3. Summary and Ratings ΓòÉΓòÉΓòÉ
Summary and Ratings
This book is a good buy overall, although it is a little lacking in some areas.
It was recommended to me as a beginning PM programmer's book, but I see it as
being a little too complex for that, unless you have experience in programming
some other GUI. The skeleton program is good, but separating out the code you
need from the more advanced examples can be tedious and difficult. Had the
pace of chapters two and three been more leisurely, I would have picked up a
lot more, and felt a lot more at ease with it. As it was, I found myself
constantly picking it up, putting it down, looking elsewhere for simpler
examples, and then picking it up again to give it another try. If I had a bit
of PM, X, Mac or Windows programming background, this book would have been
perfect.
The example programs in the chapters are very good, though, and as the authors
like to say, much more than just the normal do-nothing programs that many books
have. The INITOR application actually made it into my \BIN directory with my
other utilities, and most of the other programs were interesting at the very
least. Intermediate or experienced PM programmers should love it.
ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
ΓöéBOOK ΓöéAUDIENCE ΓöéMARKΓöéCOMMENTS Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéReal World Programming for OS/2 ΓöéIntermediateΓöéB+ ΓöéLots of good code examples, but Γöé
Γöé2.1, Blain, Delimon, and Γöéto Advanced Γöé Γöésometimes it is too complex for Γöé
ΓöéEnglish, SAMS Publishing. ISBN ΓöéPM C Γöé Γöénovices. Accurate. Well organized.Γöé
Γöé0-672-30300-0. US $40, CAN $50.Γöéprogrammers Γöé ΓöéThe index needs a little beefing Γöé
Γöé Γöé Γöé Γöéup. Good, but not entirely Γöé
Γöé Γöé Γöé Γöécomplete how-to reference. Good Γöé
Γöé Γöé Γöé Γöépurchase. Γöé
ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
This table will contain all books I have reviewed, so that books can be
compared against each other. I will be very careful to rate books fairly
relative to each other. I will rate conservatively until I get a better feel
for what's out there. If I feel a need to adjust ratings, I will adjust all of
them at the same time, and write a note explaining why I felt this necessary,
for example, a new book came out which was so good that there wasn't enough
room at the top of the scale to properly convey this. :)
BOOK: The name of the book, author(s), publishing company, ISBN, and price
(approximate.)
AUDIENCE: This is a description of the audience I think the book targets best.
This is not intended as gospel, just a guideline for people not familiar with
the book.
MARK: My opinion of the success of the book's presentation, and how well it
targets its audience. Technical content, technical accuracy, organization,
readability, and quality of index all weigh heavily here. I may revise,
expand, or add categories in the future. I don't expect to see any book score
less than C, but the scale is there if necessary.
A+ Ground-breaking, all-around outstanding book.
A Excellent book. This is what I want to see happen a lot.
A- Excellent book with minor flaws or omissions.
B+ Very good book with minor flaws or omissions.
B Good book with some flaws or omissions.
B- Good book, but in need of improvement.
C Mediocre book with some potential, but in need of repairing.
D Don't buy this book unless you need it, and nothing else exists.
F Don't buy this book. Period.
COMMENTS: To some extent, this is a summary of the review proper, but also
explains why the book may have gotten a better/worse mark than otherwise
expected. For example, if a book is very popular but didn't fare too well,
this discrepancy would be explained here.
ΓòÉΓòÉΓòÉ 3.1.4. Coming Up ΓòÉΓòÉΓòÉ
Coming Up
Next month I will be looking at Learning to Program OS/2 2.0 Presentation
Manager by Example, Knight. Other books I intend to review are:
o The Art of OS/2 C Programming, Panov and Salomon
o OS/2 Presentation Manager Programming, Petzold - 1994
o OS/2 Presentation Manager GPI, Winn
o The Design of OS/2, 2nd Edititon, Kogan and Deitel - 1994
This list is not set in stone, but they are books I am interested in. I am
considering reviewing OS/2 Unleashed, but it is not strictly speaking a
development book, so I'm going to wait until the list of real development books
has diminished a bit. By the way, does anyone know why the special edition of
OS/2 Unleashed has completely different authors?
If anyone has a book they would like to have reviewed, I will be happy to
oblige, so long as I can afford it, ie. no $500 books. :) Of course, the
request would be fulfilled a lot quicker if accompanied by a book. :)
ΓòÉΓòÉΓòÉ 3.2. C++ Corner ΓòÉΓòÉΓòÉ
ΓòÉΓòÉΓòÉ 3.2.1. Introduction ΓòÉΓòÉΓòÉ
Written by Gordon Zeglinski
Introduction
In this issue, we will get back to encapsulating bits and pieces of the OS/2
API. Instead of just dumping out the final version, I will take an iterative
approach. In this issue, the first version of a queue encapsulation hierarchy,
will be developed. In subsequent issues, a refined version will be presented.
The focus here, will be on the API calls, and encapsulation concepts.
Loose Ends
In the last issue, the makefile distributed to build the Watcom version of POV
2.1 was actually a NMAKE file not a WMAKE file.
I recently added 4 more megs of RAM to the system here. This has greatly
improved the stability of the WorkFrame. I suspect its instability was somehow
related to swapping. Hopefully the OS/2 2.1 CSD will be out soon, and help
improve its stability even more.
ΓòÉΓòÉΓòÉ 3.2.2. Queue Basics ΓòÉΓòÉΓòÉ
Queue Basics
What are Queues?
A queue (as implemented by the OS/2 kernel) is a linked list of elements
allowing inter-process communication. Each record in the list contains a
pointer to the data, the length of the data, and a ULONG parameter. The queue
can be a FIFO (first-in, first-out) or LIFO (last-in, first out) and can have
up to 16 priority levels. The queue is a one way communication method from the
client to the server.
ΓòöΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòù
ΓòæULONG ParameterΓòæ - Application Defined
ΓòƒΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓòó (similar to message IDs in PM)
ΓòæData Pointer Γòæ
ΓòƒΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓòó
ΓòæData Length Γòæ
ΓòÜΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓò¥
Visualization of a queue element
You should have noted that queues use pointers to the application defined data.
This means that when the client and server are in different processes, shared
memory must be used. Using shared memory efficiently will be a refinement
examined in the next revision of this class.
The Queue API
OS/2 provides the following functions to manipulate queues. Our goal is to
encapsulate these functions for both the client and server processes.
o DosCloseQueue()
o DosCreateQueue()
o DosOpenQueue()
o DosPeekQueue()
o DosPurgeQueue()
o DosQueryQueue()
o DosReadQueue()
o DosWriteQueue()
We will examine some of these functions in this column. The reader is referred
to the Control Program Reference and Guide for an explanation of those
functions not covered and for more details on those covered. As with all
kernel functions, a return code of 0 indicates that no error occurred.
DosCreateQueue()
This function is used by the server process to create the queue. Its syntax
follows:
APIRET DosCreateQueue(PHQUEUE phqHandle,
ULONG ulFlags,
PSZ pszName);
phqHandle The variable pointed to by this parameter will contain the
queue handle on return.
ulFlags Used to set the queue type, one of the QUE_FIFO, QUE_LIFO,
or QUE_PRIORITY flags can be or'd with one of
QUE_NOCONVERT_ADDRESS or QUE_CONVERT_ADDRESS.
pszName The name of the queue. It must have the prefix "\QUEUES\" .
DosOpenQueue()
This function is used by the client process to obtain access to the queue. Its
syntax follows:
APIRET DosOpenQueue(PPID ppidOwner,
PHQUEUE phqHandle,
PSZ pszName);
The parameters of this function are the same as for the DosCreateQueue()
function, except for the ppidOwner parameter. This parameter is used to return
the process ID of the queue's creator to the process calling this function,
which can later be used to grant access to the shared memory object.
DosWriteQueue()
This function is used by the client to place data into the queue. Its syntax
follows:
APIRET DosWriteQueue(HQUEUE hqHandle,
ULONG ulParam,
ULONG ulSzBuf,
PVOID pvBuffer,
ULONG ulPriority);
hqHandle The handle of the queue to be written to.
ulParam The parameter in the queue record discussed previously.
ulSzBuf Lenght of the data buffer.
pvBuffer Pointer to the data buffer.
ulPriority If the queue was created as a priority based queue, then
this contains the elements priority. Otherwise, it is
ignored.
DosReadQueue()
This function is used by the server to read data from the queue. Its syntax
follows:
APIRET DosReadQueue(HQUEUE hqHandle,
PREQUESTDATA prdRequest,
PULONG pulSzData,
PPVOID ppvData,
ULONG ulCode,
BOOL32 bNoWait,
PBYTE pbPriority,
HEV hevSemaphore);
hqHandle The handle of the queue to be read from.
prdRequest Pointer to a REQUESTDATA structure.
pulSzData Pointer to the variable which will receive the length of
the data item.
ppvData Pointer to the variable which will receive the address of
the data item.
ulCode A value of 0 is used to read the first element from the
queue. A value returned by DosPeekQueue() can be used to
read the element previously peeked at.
bNoWait If 0, the thread will block until a data element is in the
queue if one is not already present. If 1, the thread
returns immediately.
pbPriority Pointer to the variable which will hold the elements
priority.
hevSemaphore Handle of an event semaphore which will be posted when a
element is placed into the queue and the bNoWait is 1.
The REQUESTDATA structure is defined as follows:
typedef struct _REQUESTDATA
{
PID pid; // PID of the process which placed the element in the queue
ULONG ulData; // The Param parameter of DosWriteQueue
} REQUESTDATA;
Note: the first time this function is called with a bNoWait of 1, the handle
of the event semaphore is stored by the system; this handle must be used in
subsequent reads from the queue. The event semaphore must also be created
using the DC_SEM_SHARED flag.
ΓòÉΓòÉΓòÉ 3.2.3. Encapsulating the Queue API ΓòÉΓòÉΓòÉ
Encapsulating the Queue API
We start the encapsulation process by listing the properties of queues that we
wish to encapsulate:
o Reading of queue elements
o Writing of queue elements
o Creating the queue
o Opening the queue
o Manipulating the shared memory
We also note that the queue has two separate sides, a client and a server.
These two sides have several properties in common which will lead us to this
first approximation to the queue hierarchy:
ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
ΓöéQueueObjΓöé
ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
Γöé Γöé
ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
ΓöéClientQueueObjΓöé ΓöéServerQueueObjΓöé
ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
Queue Hierarchy
We will now look at the design of these three classes.
QueueObj
As the base class for this hierarchy, QueueObj provides the data members that
are common to both the client and the server, as well as various support
functions. These data members include the names of the queue and shared memory
object, the handle of the queue, the address of the shared memory, and the size
of the shared memory. For the first pass, the shared memory will be maintained
by member functions of the hierarchy.
The class definition follows:
class QueueObj
{
public:
enum _Action { None, Create, Open };
_exp_ QueueObj(char *ShMemNm,
unsigned long MemSize,
char *QueueNm,
unsigned long flag=QUE_PRIORITY,
_Action Mem=None,
_Action Queue=None);
virtual _exp_ ~QueueObj();
unsigned long GetError() { return Error; }
void _exp_ SetMemName(char *N);
void _exp_ OpenMem(unsigned long MemSize);
void _exp_ CreateMem(unsigned long MemSize);
void _exp_ SetQueueName(char *N);
void _exp_ OpenQueue();
void _exp_ CreateQueue(unsigned long flag=QUE_PRIORITY);
PID GetOwner() { return pidQueueOwner; }
protected:
typedef unsigned long HQUEUE;
HQUEUE hqueue;
char *ShareMemName;
char *QueueName;
char *MemBase;
unsigned long ShMemSize;
PID pidQueueOwner;
unsigned long Error;
};
This class provides support methods for both the client and server classes to
use. These methods allow both the shared memory and the queue to be named,
created, or opened.
ClientQueueObj
class ClientQueueObj : public QueueObj
{
public:
_exp_ ClientQueueObj(char *ShMemNm,
unsigned long MemSize,
char *QueueNm,
unsigned long flag=QUE_PRIORITY,
int Reserv=0,
_Action Mem=Open,
_Action Queue=Open);
int _exp_ Write(ULONG Req,ULONG Priority=15);
int _exp_ Write(ULONG Req,char *Buff,ULONG BuffSize,ULONG Priority=15);
void * _exp_ RequestMemBlock(unsigned long BlSize);
void SetReserveLen(int Len) { ReservedLen=Len; }
protected:
char *CurrLoc;
int CurrLenght;
int ReservedLen;
};
By default, the constructor for the client object opens the queue and shared
memory. This object is responsible for maintaining the position of the free
space in the shared memory object. The variable ReservedLen is used to reserve
a section of shared memory from the start. The member function Write() uses
the variables CurrLoc and CurrLength as parameters in the call to
DosWriteQueue().
To write to the queue, the RequestMemBlock() member function is used to request
a block of shared memory. This block is then initialized by the application,
and either version of the member function Write() is called. The second form
of the member function requires that the location and length of the shared
memory block be specific as parameters while the first assumes the last request
block of shared memory is the data buffer source.
ServerQueueObj
class ServerQueueObj : public QueueObj
{
public:
_exp_ ServerQueueObj(char *ShMemNm,
unsigned long MemSize,
char *QueueNm,
unsigned long flag=QUE_PRIORITY,
EventSemaphore *RS=NULL,
_Action Mem=Create,
_Action Queue=Create);
PID GetPID() { return ReqDat.pid; }
ULONG UserData() { return ReqDat.ulData; }
void _exp_ PostSem();
void _exp_ ResetSem();
void _exp_ WaitOnSem();
void* _exp_ Read(ULONG ElCode=0, BOOL wait=0);
void* _exp_ Peek(ULONG ElCode=0);
ULONG GetDataLen() { return DataLen; }
BYTE GetPriority() { return Priority; }
protected:
EventSemaphore *ReadSem;
REQUESTDATA ReqDat;
ULONG DataLen;
void *Entry;
BYTE Priority;
};
The constructor for the server object creates both the shared memory and queue.
It contains data members and member functions to manipulate the various
parameters to the DosReadQueue() function, mentioned earlier. In addition to
this, it also has a pointer to an event semaphore object. As this issue
focuses on queues, I will not go into details on the semaphore objects.
This object is a bit easier to use. After creating an instance of it, the read
function is called to remove an element from the queue if one is present.
ΓòÉΓòÉΓòÉ 3.2.4. Using the Objects ΓòÉΓòÉΓòÉ
Using the Objects
We will now test the queue server and client objects by creating two simple
applications. The client application will prompt the user to numerical input.
It will place this numerical input into the queue, at which point the server
will echo the input to the display. Below is the source code to the queue
server with comments added.
/* These definitions are contained in QDefs.h
#define QName "DemoQueue" //Name of the queue
#define SMemName "DemoMem" //Name of the shared memory
#define NemSize 1024*8 //size of the shared memory block
//in bytes
struct QueueMessage{ //format of the queue data packet
int Number;
};
*/
int main(){
int loop;
QueueMessage *Mesg; // pointer to the data packet
//create a shared event semphore
EventSemaphore Sem((char*)NULL,Semaphore::SemCreate,DC_SEM_SHARED);
//create the queue object.
//This also creates the shared memory and the actual queue
ServerQueueObj InQueue(SMemName,NemSize,QName,0,&Sem);
loop=1;
//reset the event semaphore
Sem.Reset();
cout<<"Queue Server Active !"<<endl;
while(loop){
//attempt to read an element from the queue
Mesg=(QueueMessage *) InQueue.Read(0,1);
//read from queue until an element occurs
while(InQueue.GetError() ){
//this semaphore will be posted as soon as an element is put in the queue
Sem.Wait();
Mesg=(QueueMessage *) InQueue.Read(0,1);
}
//terminate is the number was -1, else echo the number
if(Mesg->Number == -1){
loop=0;
cout<<"Terminating"<<endl;
}
else{
cout<<"Number= "<<Mesg->Number<<endl;
}
}
return 0;
}
The queue encapsulation has made using the queues much less tedious. The code
for the client is very similar. Therefore, it is left as an exercise for the
curious to discover how it works.
There are limitations with the current queue implementation. They are:
o Only 1 client may use the shared memory pool at a time.
o Only 1 thread in the client process may use the shared memory pool at a time.
The following files are included with this issue:
MAKE.CMD Rexx .CMD file to make the server and client. Assumes the
compiler is IBM C-Set++.
QCLIENT.CPP Main routine for the client.
QSERVE.CPP Main routine for the server.
QUEUEOBJ.CPP Queue object member function definitions.
QUEUEOBJ.H QueueObj class definition.
QDEFS.H Header used in the client a server source files.
SEMTIMOBJ.CPP Semaphore member functions.
SEMTIMOBJ.H Semaphore object definitions.
QSERVE.EXE Server executable.
QCLIENT.EXE Client executable.
Although this code is compiled using C-Set++, other compilers should be able to
compile the source files.
Note: QSERVE.EXE must be run before QCLIENT.EXE .
ΓòÉΓòÉΓòÉ 3.2.5. Summary ΓòÉΓòÉΓòÉ
Summary
This concludes our first attempt at encapsulating the queue objects. We have
developed queue objects which encapsulate the creation and manipulation of
queues and the shared memory they require. In a future issue, the next version
of the queue objects will be presented. The goal for the next version is to
eliminate the constraints that exist in this version.
ΓòÉΓòÉΓòÉ 3.3. Introduction to PM Programming ΓòÉΓòÉΓòÉ
ΓòÉΓòÉΓòÉ 3.3.1. Introduction ΓòÉΓòÉΓòÉ
Written by Larry Salomon, Jr.
Introduction
The purpose of this column is to provide the readers out there wh9 are not
familiar with PM application development the information necessary to satisfy
their curiousity, educate themselves, and give them an advantage over the
documentation supplied by IBM. Of course, much of this stuff could probably be
found in one of the many books out there, but the problem with books in general
is that they don't answer the questions you have after you read the book the
first time through.
I will gladly entertain feedback from the readers about what was "glossed over"
or what was detailed well, what tangential topics need to be covered and what
superfluous crap should have been removed. This feedback is essential in
guaranteeing that you get what you pay for. :)
It should be said that you must not depend solely on this column to teach you
how to develop PM applications; instead, this should be viewed as a supplement
to your other information storehouses (books, the network conferences, etc.).
Because this column must take a general approach, there will be some topics
that you would like to see discussed that really do not belong here. Specific
questions can be directed to the Scratch Patch, where an attempt to answer them
will be made.
ΓòÉΓòÉΓòÉ 3.3.2. Last Month ΓòÉΓòÉΓòÉ
Last Month
Last month, we finished dissecting the (initial) HELLO program that was
introduced in volume 2, issue 1. This month we will starts to look at a
significantly changed version of HELLO in order to set the direction of this
column for the next few issues. To recap, though, HELLO's purpose was simply
to demonstrate the minimum number of calls required to do anything useful.
Other than that, it didn't do anything useful. :)
Up to this point, we have introduced and continued discussion on two areas of
PM application development - messages and functions. Now we are going to add
two more - dialog box implementation and resource files.
ΓòÉΓòÉΓòÉ 3.3.3. Dialog Boxes ΓòÉΓòÉΓòÉ
Dialog Boxes
In most programs, it is necessary to gather (even frequently) data from the
user. This may be as simple as getting a filename, or it may be as complex as
(gulp) filling out an online tax form. In any case, what is the method by
which input is obtained? CUA defines the mechanism to be a dialog box. What
is a dialog box, you ask? To be precise, it is a window whose sole purpose is
to receive data from the user in order for the application to continue.
Because it is a window, it has the window characteristics discussed when this
column began.
Every one has seen a dialog box or two; whether it is when your word processor
prompts you for a file name to load, or when your spreadsheet program asks you
to select a printer, you cannot escape these facts of (GUI) life. However,
using them is one thing - implementing them is quite another.
There are two components to dialog box implementation - screen layout and code.
We will begin to look at each in this issue, and will continue in more detail
in future issues as we also look at the various controls.
Multilingual
Okay, so you are a Pascal hacker from DOS trying to learn C under OS/2. To
make it worse, you have to also learn PM, according to your boss. But if that
weren't enough, now you can tack on another language to learn, another skill to
master. It is called the resource language, and it is the language in which
dialog boxes (among other things) are designed.
Simply put, a dialog box is a collection of child windows. Each window is
defined separately, with a specific order. At the next level, there are groups
of windows, beginning with the first one having a special style - WS_GROUP -
and ending just prior to the beginning of the next group or with the end of the
dialog box definition. Finally, a dialog definition begins with a DIALOG
keyword, followed by a BEGIN keyword, and ends with an END keyword.
A sample dialog definition follows:
DLGTEMPLATE DLG_GRAPHTYPE LOADONCALL MOVEABLE DISCARDABLE
BEGIN
DIALOG "Create graph", DLG_GRAPHTYPE, 64, 23, 195, 170, NOT FS_DLGBORDER |
WS_VISIBLE, FCF_SYSMENU | FCF_TITLEBAR | FCF_SIZEBORDER
BEGIN
GROUPBOX "Graph type", -1, 10, 105, 170, 60
LTEXT "Field(s) to graph", -1, 10, 90, 100, 8
CONTROL "", DGT_UB_BARGRAPH, 15, 110, 50, 45, WC_BUTTON, BS_USERBUTTON | WS_GROUP | WS_TABSTOP | WS_VISIBLE
CONTROL "", DGT_UB_LINEGRAPH, 70, 110, 50, 45, WC_BUTTON, BS_USERBUTTON | WS_GROUP | WS_TABSTOP | WS_VISIBLE
CONTROL "", DGT_UB_SCATTERGRAPH, 125, 111, 50, 45, WC_BUTTON, BS_USERBUTTON | WS_GROUP | WS_TABSTOP | WS_VISIBLE
LISTBOX DGT_LB_HEADERLIST, 10, 30, 175, 60, LS_HORZSCROLL | LS_EXTENDEDSEL | WS_GROUP
LISTBOX DGT_LB_FULLIST, 10, 30, 175, 60, LS_HORZSCROLL | LS_EXTENDEDSEL | WS_GROUP | NOT WS_VISIBLE
DEFPUSHBUTTON "~Graph", DGT_PB_GRAPH, 10, 10, 40, 13, WS_GROUP
PUSHBUTTON "~Settings...", DGT_PB_SETTINGS, 55, 10, 40, 13
PUSHBUTTON "Cancel", DLG_PB_CANCEL, 100, 10, 40, 13
PUSHBUTTON "Help", DLG_PB_HELP, 145, 10, 40, 13
END
END
As you can see, there are many more details regarding these definitions;
fortunately, you don't have to know them. There is a utility called DLGEDIT
which allows you to design, on the screen, your dialog boxes and save the
result. We won't describe how to use this utility; see the accompanying
documentation for this. What is the result? The human-readable source is
saved with the extension .DLG and the binary form is saved with the extension
.RES. In order to explain more, we need to broaden the scope of our discussion
to...
Resource Files
A resource file - with the extension .RC - contains the definition of the
various resources used by the application and each of these resources can be
loaded or accessed via a Win*() function specific to that resource type. The
resource language also includes C-style preprocessor commands to allow the
inclusion of C header files containing #define statements for various constants
that are needed by both the resource file and the application.
For dialogs, Microsoft decided that the Dialog Box Editor shouldn't have to
deal with parsing the entire resource file, so it added dialog definition files
(.DLG) to the fold. Integrating the two can get unwieldy - at the end of the
.RC file, you indicate the dialog file to include:
RCINCLUDE MYDLGS.DLG
...but at the beginning of the dialog file, you must somehow also include the
#define statements for each dialog, so there is a different statement:
DLGINCLUDE 1 MYDLGS.H
Whoa! Why the number on the DLGINCLUDE statement but not the RCINCLUDE
statement? The answer is that Microsoft thought that different dialogs would
have different sets of constants, so the number is the identifier of the dialog
(meaning you can have different #include files refer to different dialog box
definitions).
If the reasoning seems flawed to you, that is because it is. My personal taste
is to append the .RC file with the contents of the .DLG file. Preference is
the key word here. Since the dialog editor reads the binary file (.RES), it
doesn't matter how we manipulate the human-readable source.
ΓòÉΓòÉΓòÉ 3.3.4. Dialog Code ΓòÉΓòÉΓòÉ
Dialog Code
In addition to a dialog screen definition, there is code to be written. Since a
dialog is much like a "main window", you can imagine that the code is some form
of a window procedure and this is correct. It is called a dialog procedure and
differs from a window procedure in the following ways:
1. The dialog procedure never receives a WM_CREATE message. Instead, it gets a
WM_INITDLG message.
2. The default dialog procedure is not WinDefWindowProc(); it is instead
WinDefDlgProc() and takes the same parameters.
3. A dialog is not explicitly destroyed by the application (usually). Instead,
it is dismissed via the WinDismissDlg() function.
WM_INITDLG
This message occurs when a dialog box is being created.
Parameters
param1
hwndFocus (HWND)
The handle of the window to receive the input focus when
initialization is completed.
param2
pvData (PVOID)
Points to the application data specified as the last parameter on the
call to WinDlgBox() or WinLoadDlg().
Returns
reply
bFocusChanged (BOOL)
Focus change indicator:
FALSE Give the input focus to the window in param1
TRUE The input focus was assigned by the application to another
window so do not give the input focus to any window.
We will examine in detail how dialog procedures are written and used in later
issues.
Dialog boxes come in two flavors, and the type determines the function used to
display the dialog box.
Modal dialog boxes are those that require interaction by the user before the
application or system can continue. An "Open file" dialog is an example of
this.
Modeless dialog boxes are those that do not require input for the application
to continue. A toolpad is an example of this.
For the former, WinDlgBox() is used to display and process the dialog. The
latter uses WinLoadDlg(). Both functions take the same parameters.
ULONG WinDlgBox(HWND hwndParent,
HWND hwndOwner,
PFNWP pfnDlgProc,
HMODULE hmModule,
ULONG ulDlgId,
PVOID pvData);
hwndParent The parent window of the dialog box
hwndOwner The desired owner window of the dialog. This is rarely the
true owner of the dialog (see below). For calls to
WinDlgBox(), the true owner is disabled until the dialog is
dismissed.
pfnDlgProc Specifies the dialog procedure
hmModule Handle to the DLL where the dialog definition resides
ulDlgId ID of the dialog to be displayed
pvData Application data, passed to the dialog procedure via the
WM_INITDLG message
The true owner of a dialog is calculated by the system in the following manner
- each successive parent window of hwndOwner is queried until one is found
whose immediate parent is hwndParent. If found, this becomes the true owner,
otherwise hwndOwner becomes the true owner.
Dismissal versus Destruction
When a dialog is dismissed, it is by default not destroyed until the
application exits. This is for performance reasons; since a large percentage
of dialogs are reused by an application many times, this reduces the time
between the call to WinDlgBox() and the time the dialog is actually displayed.
The ramifications are that if you allocate resources in the dialog procedure,
you cannot wait until the WM_DESTROY message to free them, or else you are
asking for trouble. When do you do it? I have yet to find a good answer to
that question...
ΓòÉΓòÉΓòÉ 3.3.5. Summary ΓòÉΓòÉΓòÉ
Summary
This month, we began to look at dialog boxes and resource files. These are
collectively large topics, so we will continue next month with these. In the
meantime, a sample application has been provided as hello.zip; this is an
enhancement of the previous Hello world application that was discussed in
previous issues.
Further down the road, we will begin looking at each of the individual
controls, what their purpose is, and how they are used (primarily from the
context of a dialog box). Stay tuned!
ΓòÉΓòÉΓòÉ 3.4. Scratch Patch ΓòÉΓòÉΓòÉ
Written by Larry Salomon, Jr.
Welcome to this month's "Scratch Patch"! Each month, I collect various items
that fit into this column sent to me via email. 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 an item, send it via email to my address - os2man@panix.com - and be
sure to grant permission to publish it (those that forget will not be
considered for publication). This month, we have the following:
o Corrections
o Gotcha Notes!
o Questions and Answers
o Snippet(s) of the Month
o Documentation Chop Shop
o Want Ads
ΓòÉΓòÉΓòÉ 3.4.1. Corrections ΓòÉΓòÉΓòÉ
Corrections
Last month, I incorrectly credited Gordon Zeglinski as the author of the
functions in Snippet(s) of the Month. The true author is Richard Papo, the
author of MemSize. He can be reached via email at 72607.3111@compuserve.com .
My apologies for any inconveniences.
ΓòÉΓòÉΓòÉ 3.4.2. Gotcha Notes! ΓòÉΓòÉΓòÉ
Gotcha Notes!
This is a part of this column that will appear from time to time, whenever
someone else (or myself) manages to stumble upon something that is not obvious,
yet doesn't belong anywhere else in the column. Things that end up here are
not documentation errors or vagaries, but just things to be careful of. If you
have something that would be of help to another of your fellow developers,
please send it in.
The first submission this month deals with MDI applications, minimized icons,
and the WM_QUERYTRACKINFO message. As many of you know, this message is sent
to the frame whenever any tracking-related event - most notably moving and
sizing - occurs to allow the frame to set parameters for the event. The point
is that, by subclassing the frame window and intercepting this message, you can
control some very important things such as minimum or maximum size of the
window simply by setting one or more fields in the TRACKINFO structure pointed
to by PVOIDFROMMP(mpParm2).
After investigating a bug reported by one of the users of an application I
wrote, I discovered - much to my chagrin - that if a window is minimized and
the user decides to move the icon, the frame receives this message. If, as was
the case in my application, the frame is subclassed and the ptlMaxTrackSize is
blindly set without any concern about the type of operation in progress, the
icon will disappear when the operation is finished!
The way to avoid this is to check that you are not minimized (by calling
WinQueryWindowULong(hwndWnd,QWL_STYLE) and checking for the WS_MINIMIZED bit)
before proceeding to set any fields in the TRACKINFO structure.
On a related topic, it should be noted that this message is not sent to the
frame when the user maximizes the frame. The message that is sent to the frame
is WM_ADJUSTWINDOWPOS.
ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇ
Eberhard Mattes noted that, for menu items with the style MIS_BITMAP, it isn't
stated that bitmaps of menus are loaded from the .EXE file and not resource
DLL. He presents a solution to this in the Snippet(s) of the Month section of
this column.
ΓòÉΓòÉΓòÉ 3.4.3. Questions and Answers ΓòÉΓòÉΓòÉ
Questions and Answers
You have to love it when it seems that everyone else is writing your column for
you. ;) This answer to a frequently asked question comes from Juergen Hermann
(jh@ccave.ka.sub.org)
The answer to the question "how to bind a default icon to an .EXE file" is that
the system looks for an icon resource with the (magic) number 1, so you have to
use the following in your resource file.
ICON 1 PRELOAD MYAPP.ICO
It was pointed out that this will cause trouble if you define a constant for
the number 1 because it conflicts with DID_OK, which is defined by the Toolkit.
My experience has indicated that the first icon resource found in the
executable is used, but since the first icon resource in my application are
usually the lowest numbered also, this could instead by the criteria. Given
that you can't get much lower than 1 (0?), the behavior I have seen would be
consistent with what is described above.
ΓòÉΓòÉΓòÉ 3.4.4. Snippet(s) of the Month ΓòÉΓòÉΓòÉ
Snippet(s) of the Month
Eberhard Mattes, well-known for his numerous contributions including the EMX
development system, sent us the following code to load a bitmap and set it into
a menu item. The code is adapted from DVIPM.
/* The bitmap of a menu entry having style MIS_BITMAP is loaded from
the main executable. This function loads it from the resource DLL.
Input: hwndFrame Handle of the frame window owning the menu
hMod Module handle of the DLL containing the bitmap
resource
idMenu ID of the menu item (must have style MIS_BITMAP)
idBitmap ID of the bitmap
*/
void load_menu_bitmap (HWND hwndFrame, HMODULE hmod,
USHORT idMenu, USHORT idBitmap)
{
HPS hps;
HWND hwndMenu;
HBITMAP hbm;
hps = WinGetScreenPS (HWND_DESKTOP);
hwndMenu = WinWindowFromID (hwndFrame, FID_MENU);
hbm = GpiLoadBitmap (hps, hmod, idBitmap, 0, 0);
WinSendMsg (hwndMenu, MM_SETITEMHANDLE,
MPFROMSHORT (idMenu), MPFROMLONG (hbm));
WinReleasePS (hps);
}
ΓòÉΓòÉΓòÉ 3.4.5. Documentation Chop Shop ΓòÉΓòÉΓòÉ
Documentation Chop Shop
The documentation of MM_SETITEMHANDLE appears to be wrong. It says that
mpParm1 is the item index, but instead it should be the ID of the menu item
with which it is to be associated. This message seems to search all child
submenus for the menu item.
ΓòÉΓòÉΓòÉ 3.4.6. Want Ads ΓòÉΓòÉΓòÉ
Want Ads
Below are the hot topics as of this issue's writing. Feel free to write on any
of these.
Workplace Shell Programming (hot) - lately, I have noticed two things: 1) lots
of people want to learn how to write new Workplace Shell classes and 2) no one
who knows anything about it is telling the rest of us how to do it. This
month's article is a start, but is not enough! :)
Client/Server (hot) - using either named pipes (with or without a network) or
sockets, client/server programming is all the rage these days. On a related
note, some people have also expressed an interest in learning about interfacing
with the various protocol drivers (e.g. NDIS, IPX/SPX, etc.). Any articles in
this area are most welcome.
Multimedia (warm) - last month we had two articles on this topic. However,
they both dealt with sound, which we all know is not the only alternative media
type. Articles on anything else - MIDI, video, etc. - are needed.
Animation (warm) - a few readers expressed an interest in the various animation
techniques that can be applied to PM applications. The ultimate article, in my
opinion, would be one that develops a sprite library a la the Commodore 64's
(and Amiga's?) built-in routines, since this is probably the hardest component
of any good animation sequence. Call this a "personal request"...
ΓòÉΓòÉΓòÉ 4. How do I get EDM/2? ΓòÉΓòÉΓòÉ
EDM/2 can be obtained in any of the following ways:
On the Internet
o All back issues are available via anonymous FTP from ftp.cdrom.com in the
/pub/os2/2_x/program/newsltr directory.
o The EDM/2 mailing list. Send an empty message to edm2-info@knex.via.mind.org
to receive a file containing (among other things) instructions for
subscribing to EDM/2.
o IBM's external gopher server in Almaden, of which I don't have the address.
On Compuserve
All back issues are available in the OS/2 Developers Forum 2.
IBM Internal
o IBM's internal gopher server in Almaden, of which I don't have the address.
o IBM's REQUEST command on all internal VM systems. Enter the VM command
REQUEST LIST FROM ASSELIN AT RALVM12 and a list of the requestable packages
will be sent to you; in this list are the names of the packages containing
the EDM/2 issues.
ΓòÉΓòÉΓòÉ 5. Contributors to this Issue ΓòÉΓòÉΓòÉ
Are You a Potential Author?
We are always looking for (new) authors. If you have a topic about which you
would like to write, send a brief description of the topic electronically to
any of the editors, whose addresses are listed below, by the 15th of the month
before the month in which your article will appear. This alerts us that you
will be sending an article so that we can plan the issue layout accordingly.
After you have done this, get the latest copy of the Article Submission
Guidelines from ftp.cdrom.com in the /pub/os2/2_x/program/newsltr directory.
(the file is artsub.zip) The completed text of your article should be sent to
us no later than five days prior to the last day of the month; any articles
received after that time may be pushed to the next issue.
The editors can be reached at the following email addresses:
o Larry Salomon - os2man@panix.com (Internet).
The following people contributed to this issue in one form or another (in
alphabetical order):
o BjФrn Fahller
o Larry Salomon, Jr.
o Kenton W. Shaver
o Marc van Woerkom
o Carsten Whimster
o Gordon Zeglinski
o Network distributors
═══ 5.1. BjФrn Fahller ═══
BjФrn Fahller
BjФrn Fahller is about to finish his Master's degree in Computer Science at
LuleЖ University, Sweden. Before the university studies, he worked with ASIC
design for the telecommunications industry, as well as miscellaneous
programming jobs. His major fields of interest are Human-Computer interaction
and object oriented design.
After having worked with a short software project for the University of British
Columbia, in Vancouver, Canada, the summer of '93, he is now trying to get back
to Beautiful B.C. by searching for jobs there.
Since a little over a year ago he has also tried to run the OS/2 ftp site,
ftp.luth.se, but is constantly fighting the disk space problem.
He can be reached on the Internet at bjorn@ludd.luth.se
ΓòÉΓòÉΓòÉ 5.2. Larry Salomon, Jr. ΓòÉΓòÉΓòÉ
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 being distributed by IBM with the Professional Developers Kit
CD-ROM. Currently, he works for International Masters Publishers in Stamford,
Connecticut and resides in Bellerose, New York with his wife Lisa.
Larry can be reached electronically via the Internet at os2man@panix.com.
ΓòÉΓòÉΓòÉ 5.3. Kenton W. Shaver ΓòÉΓòÉΓòÉ
Kenton can be contacted at kenton+@CMU.EDU.
ΓòÉΓòÉΓòÉ 5.4. Marc van Woerkom ΓòÉΓòÉΓòÉ
Marc Ernst Eddy van Woerkom is a physics student at the Rheinisch-Westfaelische
Technische Hochschule Aachen in Germany. Currently, he works there at the
Aachen Center for Solidification in Space, ACCESS, on his Diplomarbeit.
When he isn't wrestling with FORTRAN 77 code for his diploma, he enjoys the
progress made in the last 15 years i.e. programming in C++ on his private OS/2
machine. Next to his computer addiction, he likes to cross the nearby Dutch
border to see a good movie, read science-fiction, play Badminton on occasion
and also watch a lot of cable television.
Marc can be reached at either of the following addresses:
o marc_van-woerkom@ac3.maus.de (home)
o marc@pxcl1.gi.rwth-aachen.de (work)
ΓòÉΓòÉΓòÉ 5.5. Carsten Whimster ΓòÉΓòÉΓòÉ
Carsten Whimster
I am an undergraduate computer science student at the University of Waterloo,
and an OS/2 enthusiast as of OS/2 2.0. I am currently in my third year, taking
mainly operating system, language, compiler, and graphics courses, as much as
possible. :)
This summer I will be working at the University as a tutor in CS241, an
introductory course to compilers.
I am a beginning OS/2 PM programmer with a few projects on the go, and many
more in my head. I try to buy as many books as I can afford on the subject,
but if you work for a publisher, I would certainly not turn down any donations,
and if the books are interesting and development-oriented, I will try to review
them as time permits.
I am a TEAM-OS/2 member, and stay busy trying to keep up with several OS/2
groups on the Internet.
I am also an original member of the Kitchener-Waterloo OS/2 Users' Group, and
have recently volunteered to help out organizing the events.
It's a miracle my girl-friend puts up with my doing all these things :)
You may reach me...
...via email:
bcrwhims@undergrad.math.uwaterloo.ca - Internet
gopher://descartes.math.uwaterloo.ca:70/h0/mathSOC/.csc/.www/.bcrwhimster/homepage.html
- Mosaic homepage
...via snail mail:
Carsten Whimster
319 Erb Street West, 3rd floor
Waterloo, Ontario
Canada
N2L 1W4
ΓòÉΓòÉΓòÉ 5.6. Gordon Zeglinski ΓòÉΓòÉΓòÉ
Gordon Zeglinski
Gordon Zeglinski is a freelance programmer/consultant who received his Master's
degree in Mechanical Engineering with a thesis on C++ sparse matrix objects.
He has been programming in C++ for 6 years and also has a strong background in
FORTRAN. He started developing OS/2 applications with version 2.0 .
His current projects include a client/server communications program that
utilitizes OS/2's features which has entered beta testing. Additionally, he is
involved in the development of a "real-time" automated vehicle based on OS/2
and using C++ in which he does device driver development and designs the
applications that comprise the control logic and user interface.
He can be reached via the Internet at zeglins@cc.umanitoba.ca.
ΓòÉΓòÉΓòÉ 5.7. Network distributors ΓòÉΓòÉΓòÉ
Network Distributors
These people are part of our distribution system to provide EDM/2 on networks
other than the Internet. Their help to provide access to this magazine for
others is voluntary and we appreciate them a lot!
o Paul Hethman (hethman@cs.utk.edu) - Compuserve
o Gess Shankar (gess@knex.via.mind.org) - Internet
o David Singer (singer@almaden.ibm.com) - IBM Internal
o Andre Asselin (ASSELIN AT RALVM12) - IBM Internal
If you would like to become a "network distributor", be sure to contact the
editors so that we can give you the credit you deserve!