═══ 1. Title Page ═══ Welcome to EDM/2 - The Electronic OS/2 Developers' Magazine!. Portions Copyright (C)1993 by Steve Luzynski. Issue #3 - May/June 1993. (Press 'Forward' to page.) ═══ 2. Copyright Notice and other Legal Stuff ═══ EDM/2 Copyright (C)1993 by Steve Luzynski. This publication may be freely distributed in electronic form provided that all parts are present in their original unmodified form. A reasonable fee may be charged for the physical act of distribution; no fee may be charged for the publication itself. All articles are copyrighted by their authors. No part of any article may be reproduced without permission from the original author. Neither this publication nor Steve Luzynski is affiliated with International Business Machines Corporation. OS/2 is a registered trademark of International Business Machines Corporation. Other trademarks are property of their respective owners. Any mention of a product in this publication does not constitute an endorsement or affiliation unless specifically stated in the text. ═══ 3. From the Editor ═══ Well, EDM/2 has finally made it to its third issue! You've probably noticed that this issue says 'May/June' on it - that's because I ran out of time to do separate issues for each month. I'm back on schedule now, though. This month, I happened upon an interesting little piece of software: The Microsoft Upgrade Advisor This is a free advertising tool MS has come up with to hype DOS 6.0 and Windows. Oddly enough, you must have at least Windows 3.0 to run it. No problem - A double-click from the OS/2 drives object, and it started up. After a few screens worth of stuff, it noted that I have OS/2 2.0 on my system. It recommended that I 'upgrade' my 32 bit multitasking OS with DOS 6 + Windows 3.1. Obviously I'd want to drop back down to an 8 bit OS with a cute graphical shell. My point in bringing this up was not to trash MS in any way (although that's sometimes fun too). Instead, it got me thinking about what they're doing. While IBM does have an OS/2 demo disk out, I would never have seen it had I not been poking around on Internet. On the other hand, I have 3 MS Upgrade Advisors. Getting three free high-density disks wasn't bad, of course, but I am a bit distressed to find that IBM isn't throwing their disk out in the quantities MS is. Perhaps if IBM started pushing OS/2 as hard as MS pushes Windows, I might be able to walk into a Babbage's or an Egghead and be able to pick DeScribe up off of the shelf. In other news... Starting with the next issue, I hope to have a new column. The idea is this: Each month, you, the readers, write up reivews of your favorite pieces of OS/2 development software. This can be any native OS/2 program, be it PM or text mode. Originally, I was going to limit it to public domain tools, but I see no reason to exclude commercial tools. After all, for upwards of 400 dollars in most cases, I'd like to hear from someone else who has used a particular program before I buy it. At any rate, Larry Salomon and I will go through your articles and select which ones get in to EDM/2. (Chances are that almost all of them will.) Here's yet another chance to get your name up in lights! Submissions can be in either .IPF format or plain ascii, and can be sent to either os2man@panix.com (Larry) or sal8@po.cwru.edu (myself). I will be getting back on schedule after this month, so for the July issue I'd like your submissions no later than June 25th. Submissions Once again, I'd like to remind you all that I'm always in the market for articles for EDM/2. No topic is too broad or too focused to not be of interest to someone out there. I prefer submissions in .IPF format, but if you don't know how to create an .IPF file, plain ASCII is fine as well. The rules go like this: For .IPF format submissions, your article should start at a heading level of three. This is because the table of contents is at level one and the cover panel (with the EDM/2 logo, article title, and author's name) is at level two. This means that when you are writing your article, you'll need to include dummy :h1 and :h2 panels. Beyond that, do what you want. You can either write an elaborate article with hotlinks to glossary items and animated example windows, or just stick with a straightforward series of panels. For ASCII format submissions, there are a few things that make my life a lot simpler. First, since the .IPF tagging language uses colons to tell the compiler that something is a command, colons should be represented as '&colon.' rather than ':'. For the same reason, ampersands should be '&.' rather than '&'. Next, each new paragraph should start with ':p.'. This indicates to the IPF compiler that it should start a new line. Any place you would like to put special emphasis on a word or phrase, place asterisks (*) around the text. You should also indicate what kind of emphasis you want: bold, italics, underlining, or a combination of the three. All submissions should come with a brief blurb describing the author. See the 'Contributors' section for several examples of what it should look like. If you already have a piece you'd be willing to contribute, but don't want to rework, contact me anyway and I'll arrange to convert your article for you. Please note that: 1) I really hate doing it; and 2) I'll have to have your article early so I have time to do the conversion. For example, converting Andre's article on Installable File Systems in this issue from Word for Windows format into what you see took me almost 3 hours! Anyway... In the future, I hope to include some how-to columns on using the help compiler (.IPF files can compile to either .INF or .HLP files). For now, I'm including in this issue's sample code both my editorial and the 'base' file for this issue. While you won't be able to compile them directly without some reworking, it should give you an idea of how to proceed. Steve Luzynski, Editor. ═══ 4. This Month's Features ═══ o Dos Development Tools Under OS/2 o OS/2 Installable File Systems - Part 1 o Programming the Container Control - Part 1 ═══ 4.1. Dos Development Tools Under OS/2 ═══ DOS Development Tools Under OS/2 Charles R. Oldham (cro@socrates.ed.asu.edu) ═══ 4.1.1. Introduction ═══ In this new age of interoperability, OS/2 has been billed by the marketing types as the Integrating Platform. "Run your Windows, Dos, and OS/2 applications on one machine! Preserve your existing investment in Dos and Windows software--upgrade only when you are ready!" they cry. Well, for a change, the marketing people are on the right track. OS/2 is indeed the Integrating Platform, but there are far more advantages for the developer than there are for the user. For the first time developers have the ability to write software for two different operating systems and three different operating environments: Dos, Windows, and OS/2 PM (four if you count OS/2 text mode programs). And we can do it all from one machine that hosts a multitasking, multithreaded, protected-mode operating system. Unlike systems that support cross-compilers, we are also able to test these applications to a great extent, using the Dos and WinOS /2 subsystems of OS/2 2.x. Developers are faced with a difficult question when a transition to a new operating system is possible: o How can I get the work done that I did before, but still utilize the full potential of the Operating System? Frustration is a real possibility in a situation like this. The developer may discover after a few hours or days of programming that running a Borland IDE in a Dos fullscreen session is still a lot like running Dos! Though we seem to have all of OS/2's power at our fingertips, having to use Dos tools to develop Dos programs limits us somewhat. Dos tools are not multitasking or multithreaded; they do not understand filenames that are not in the 8.3 format; interprocess communication is almost nonexistent, or at best limited due to the fact that Dos understands only the single process. It is reasonable to conclude, therefore, that we would like to minimize our contact with Dos tools, and maximize the number of OS/2 based tools that we would use to get our work done. Unfortunately, some tools, like compilers, in tegrated development environments, and especially debuggers, are irreplaceable. The question then becomes o How can I minimize my contact with Dos while still utilizing my irreplaceable tools? ═══ 4.1.2. DPipeLn ═══ Origins, Usage, and Internal Structure ═══ 4.1.2.1. Origin ═══ The Origin of DPipeLn The utility called DPipeLn was created because when developing for Dos, I tended to find myself using a Dos box all day. This frustrated me because I felt like I wasn't using the abilities of OS/2. Also, though I like Borland's Integrated Development Environments, I find them somewhat cumbersome for large projects, and I wanted to be able to use the more advanced utilities like NMake, Gnu Make, and the other Gnu programming utilities that are rapidly being ported to OS/2. I thought about my requirements and realized that it was probably possible to write an OS/2 utility that would start a Dos session transparently, run the appropriate command, and pipe the results back into the OS/2 session. This I did, and DPipeLn is the result. dpipeln [-v] [-f] [-sdos_settings_filename] [-ttemp_dir_name] dos_command -v: Start Dos session visible -s: Specify file to get Dos Settings from -f: Start Dos session in the foreground -t: Specify location of temporary files Now it is possible to call any Dos command-line based tool from an OS/2 prompt, and to see the results as if it was executed as a native OS/2 command. Dos programs can become part of pipelines (though the program must be at the head of the command line because input is not fed to the Dos program), they can be called from OS/2 based Make utilities, even from IBM's WorkFrame/2! DPipeLn even passes return codes back from a terminated Dos program. This is essential to allow make utilities to operate correctly. ═══ 4.1.2.2. Internals ═══ DPipeLn Internals DPipeLn was developed with the excellent EMX/GCC development system by Eberhard Mattes. It is a standalone program; it contains none of the library calls that require emx.dll or emxlibc.dll. The executable itself and the kit that contains the files needed to build DPipeLn can be found in the Zips/ directory that accompanies this issue of EDM/2. ═══ 4.1.2.3. Program Flow ═══ DPipeLn Program Flow Here is what happens when DPipeLn is executed: 1. Stdout is forced into binary mode. 2. The command line is processed. Several variables that control program operation are set. 3. The named pipe used for transferring the stdout (and stderr when 4Dos is in use) is created. 4. The named pipe used for transferring the Dos program return code is created. 5. The StartData structure, which is used by DosStartSession() OS/2 API function, is populated with the proper data. 6. The auxiliary batch file is created. DosStartSession actually calls the Dos command processor and instructs it to execute this batch file. The contents will be explained when this function is reviewed. 7. DPipeLn executes the DosStartSession call. The Dos program runs asynchronously. 8. A separate thread is initiated that waits for the return code from the Dos program. 9. DPipeLn connects to the Dos stdout pipe and prints out data that is fed by the Dos program through the pipe. 10. We wait for the return code thread to complete. When it is complete, we know the Dos program is complete and we set DPipeLn's return code to be the same as the Dos return code. 11. Malloc'ed memory is freed and the named pipes are disconnected. 12. DPipeLn exits with the return code previously set. ═══ 4.1.2.4. DPipeLn.c ═══ This portion of the article is intended to be an introduction to the relevant portions of the OS/2 API. If you are already familiar with the Named Pipe API, the Session API, and the Thread API, you will probably find it more worth your while to examine DPipeLn's source code directly to learn about the internals. ═══ 4.1.2.5. #defines, #includes, and Global Variables ═══ DPipeLn.c #define INCL_DOSSESMGR /* Session Manager values */ #define INCL_DOS /* DOS Calls (not MS-DOS) */ #define INCL_DOSNMPIPES /* Named Pipe Support */ #define INCL_DOSPROCESS /* Process IDs */ #define INCL_DOSQUEUES /* Queue support */ Since the OS/2 header files are so big, there are #ifdefs throughout them that allow you to tell the compiler only which sections you would like included. DPipeLn needs to use these sections. #define BUFSIZE 80 /* Pipe buffer size */ #define SETSIZE 4096 /* Dos settings maximum size */ Like the comments say, make symbolic constants for the named pipe buffer size, and the size of the Dos settings structure. #define new_string(x) ((char *)malloc((x) + 1)) #define freemem(x) if ((x) != NULL) free((x)) Macros to make string creation and memory release a little easier. #define USING_4DOS When compiling the software, #define this if you are using the 4Dos command processor replacement. The reason for this will be discussed later in the article. #include #include #include #include #include #include the header files that we need. The following are the function prototypes for the program. I'm including them here with a description of what they all do because I won't show and describe the internals of each function--only the interesting ones. PBYTE get_dos_settings(char *file); Read in the Dos settings file. The Dos settings file lets the user describe the settings for the Dos session that the indicated program will be run in. The format of the settings file is documented later in this article. char *get_basename(); Creates a base name for the named pipes and auxiliary batch file out of the PID of the DPipeLn process. The function allocates a string which is freed at the termination of the program. char *make_npipename(char *PQbasename); Takes the passed string and makes an appropriate name for the named pipe through which the Dos session will communicate with the OS/2 session. char *make_batname(char *base, char *tempdir); Makes the auxiliary batch file name. The function returns a fully-qualified path. int build_batchfile(char *Batname, char *Errpname, char *Command); Builds the auxiliary batch file. Returns FALSE if the build failed. void process_cmdline(int argc, char *argv[]); Processes all the command line switches and sets appropriate global variables that control the program flow. void usage(); Prints a usage message, calls cleanup(), then exits. void cleanup(); Frees allocated memory and disconnects named pipes. char *get_exename(char *arg); Strips the path off of argv[0] to make an executable name out of it. Returns a character string containing just the name by which DPipeLn was executed. The string is used when reporting errors. void setup_startdata(); DPipeLn calls the OS/2 API function DosStartSession() to create the Dos session in which the Dos command is run. DosStartSession() takes a struct called STARTDATA. This function puts the proper data in the struct. void transfer_data(HPIPE *piphand); This function reads the data from the named pipe that is connected to the Dos programs stdout (and stderr if 4Dos is the command processor). void read_errpipe(); This function reads the data from the named pipe that is connected to the "echo" command in the auxiliary batch file. It runs in a separate thread and collects the return code from the Dos program. char *make_rcdname(char *base); We need a name for the named pipe that transfers the return code from the Dos session. make_rcdname creates a character string that contains the name. char *get_tempdir(); Scans the environment looking for a suitable temporary directory name. This directory will hold the auxiliary batch file. get_tempdir() returns a character string with the dirname in it. Now, global variables: PSZ PgmTitle = NULL; /* Title of window in which Dos program will execute */ PSZ PgmCmdLine = NULL; /* Command line for Dos program */ PSZ PgmSettingsFile = NULL; /* File containing DOS settings */ PSZ Exename = NULL; /* DPipeLn Executable name */ UCHAR ObjBuf[100]; /* Object buffer. Used by DosStartSession */ PSZ Command; /* The actual DOS command to be executed */ PSZ Errpname; /* Name of the pipe that contains the return code */ HPIPE Piphand, Rcdpipe; /* Named Pipe handles */ int Opt_visible; /* Run DOS session invisibly */ int Opt_settings; /* DOS session has a settings file */ int Opt_foreground; /* Run session in foreground */ char *PQbasename; /* Unique name for the named pipe */ char *Pname; /* Fully qualified path for named pipe */ char *Batname; /* Unique name of the batch file */ ULONG OwningPID; /* PID of dpipeln.exe */ char RCstring[BUFSIZE]; /* String to temporarily hold ret code from DOS program */ char *Tempdir = NULL; /* temporary directory in which to put batch file */ ═══ 4.1.2.5.1. main() ═══ On to the main program. I'll leave out the local variables and only mention them when pertinent. Also, I won't cover each and every statement. Most are documented thoroughly within the code if you are interested. USHORT main(int argc, char *argv[]) { Standard stuff. if (_fsetmode(stdout, "b") == -1) { fprintf(stderr, "Could not set stdout to binary mode.\n"); exit(-1); } Set stdout to binary mode. This is necessary because when stdout is in text mode, EMX converts \n to \r\n on output. This helps when porting applications from Unix to OS/2, but is not desired behavior here. PQbasename = get_basename(); Pname = make_npipename(PQbasename); Errpname = make_rcdname(PQbasename); Batname = make_batname(PQbasename, Tempdir); Now we create unique names for the pipe through which the Dos program will send it's output, the pipe that will transfer the return code from the Dos program, and the auxiliary batch file. All the variables will contain fully qualified paths to the referenced resources. rc = DosCreateNPipe(Pname, &Piphand, NP_ACCESS_DUPLEX, NP_WAIT || NP_TYPE_MESSAGE || NP_READMODE_BYTE || NP_UNLIMITED_INSTANCES, outbuffer, inbuffer, timeout); rc = DosCreateNPipe(Errpname, &Rcdpipe, NP_ACCESS_DUPLEX, NP_WAIT || NP_TYPE_MESSAGE || NP_READMODE_BYTE || NP_UNLIMITED_INSTANCES, outbuffer, inbuffer, timeout); Important part--create the named pipes. The variables refer to the following: Pname, Errpname The names of the pipes. Piphand, Rcdpipe The OS/2 programming reference calls these "Pipe Handles". These were defined in the global variable section, and the API takes addresses to them. The API function then populates them with the proper data so they will refer to the named pipe. NP_ACCESS_DUPLEX These will be duplex (two-way) pipes. For some reason, I was unable to get them to function as one-way pipes. NP_WAIT Open the pipe in blocking mode, i.e. DosRead and DosWrite block if there is no data available. NP_TYPE_MESSAGE Data is written to the pipe as a stream of messages, as opposed to a stream of bytes (NP_TYPE_BYTE). NP_READMODE_BYTE Data is read from the pipe as a stream of bytes. NP_UNLIMITED_INSTANCES There is no limit on the number of instances of this pipe. This parameter doesn't really matter, as we know there will always be no more than one of each of these pipes (since the names are unique). outbuffer, inbuffer, timeout These local variables set the size of the output and input buffers for the pipes, and set the default amount of time DosWaitNPipe will wait for a pipe to become available. setup_startdata(&sd); DosStartSession takes a special parameter--a pointer to what is called the StartData structure. It contains members that define the different characteristics of the new session. ═══ 4.1.2.5.2. setup_startdata() ═══ Here is the definition of the StartData structure, taken from the EMX header files: typedef struct { USHORT Length; USHORT Related; USHORT FgBg; USHORT TraceOpt; PSZ PgmTitle; PSZ PgmName; PBYTE PgmInputs; PBYTE TermQ; PBYTE Environment; USHORT InheritOpt; USHORT SessionType; PSZ IconFile; ULONG PgmHandle; USHORT PgmControl; USHORT InitXPos; USHORT InitYPos; USHORT InitXSize; USHORT InitYSize; USHORT Reserved; PSZ ObjectBuffer; ULONG ObjectBuffLen; } STARTDATA; void setup_startdata(STARTDATA *SD) { setup_startdata() takes one parameter--a pointer to a STARTDATA struct. SD->Length = sizeof(STARTDATA); First we set the length of the structure. Different values enable different properties--specifically a length of 30 or 32 will cause DosStartSession to leave the rest of the struct blank and allow the installation file to fill in appropriate fields. We need all the functionality, so we will initialize SD->Length to the actual size of the struct. SD->Related = SSF_RELATED_INDEPENDENT; We want an independent session. Originally I had this set as SSF_RELATED_CHILD, because it seem to make sense that I would want DPipeLn to spawn a child session to run the Dos program in. Also, child sessions cause a Termination Queue to be created, which, for Dos sessions, contains the Dos program's exit code. Obtaining the exit code is then merely a matter of invoking the necessary functions from the queue API. However, the Toolkit reference says this about SSF_RELATED_CHILD: Once a process has issued DosStartSession specifying a value of 1 for Related, no other process within that session can issue related DosStartSession functions until all the dependent sessions have ended. And I found that that was true. When running IBM's WorkFrame/2 from the 11/93 PDK CD-Rom and running a make that involved a Dos compiler and DPipeLn, I could not start any other tool until the make was finished. WorkFrame would complain with error 452 "ERROR_SMG_NOT_PARENT". Unfortunately, SSF_RELATED_INDEPENDENT type sessions do not pass exit codes back to the parent. To circumvent that, DPipeLn creates a Dos batch file each time it is run. The batch file contains batch commands to run the Dos program, and then send the return code back through the return code pipe to the OS/2 session. This one of two reasons is why DPipeLn works better with 4Dos than the stock command.com--4Dos includes primitives for retrieving the return code from the previously run program. The batch file for command.com is several hundred lines of if statements. The second reason is that command.com makes no provision for redirecting stderr. Thus, you get stderr from the Dos process only if you are running 4Dos. SD->FgBg = Opt_foreground ? SSF_FGBG_FORE : SSF_FGBG_BACK; Opt_foreground is a global variable set while processing the command line. It is true if the user specified on the command line that the Dos session should appear in the foreground. FgBg is the element of STARTDATA that controls that characteristic. SD->TraceOpt = SSF_TRACEOPT_NONE; It is possible to start a session specifically for debugging--if so, TraceOpt is set to SSF_TRACEOPT_TRACE or SSF_TRACEOPT_NOTIFY. SD->PgmTitle = PgmTitle; PgmTitle contains the title of the session window. SD->TermQ = 0; If the Dos session was a child session, we would set TermQ equal to the queue handle. 0 indicates that there is no termination queue. TermQ is ignored for independent sessions anyway. SD->InheritOpt = SSF_INHERTOPT_PARENT; This determines if the new session will inherit the parents environment or not. In this case, we want to. SD->SessionType = SSF_TYPE_WINDOWEDVDM; Though the default is for the session to be invisible, we still want to set its type to a windowed VDM in case the user shows the window. SD->IconFile = 0; There is no icon associated with this session. SD->PgmHandle = 0; Do not use the installation file. I'm not sure what this means or how it works. The CP reference is vague on this parameter. SD->PgmControl = Opt_visible ? SSF_CONTROL_VISIBLE : SSF_CONTROL_INVISIBLE; Start the program as visible, or invisible depending on the command line argument. SD->InitXPos = 30; SD->InitYPos = 40; SD->InitXSize = 200; Here the initial window coordinates and size are set. SD->Reserved = 0; This is a reserved parameter. The CP reference says it must be 0. SD->ObjectBuffer = ObjBuf; DosStartSession calls DosExecProgram to actually start the program. If DosExecPgm fails, the name of the object responsible is placed in this buffer. SD->ObjectBuffLen = 100; This is just the size of the object buffer. SD->Environment = Opt_settings ? get_dos_settings(PgmSettingsFile) : 0; DosStartSession allows Dos sessions to get their settings from a string of null-terminated strings. If the user specified a settings file on the command line, then get_dos_settings() loads that file into memory and Environment is assigned a pointer to the beginning of the set. The settings cannot be any larger than 4096 bytes. This is not documented in the CP Reference, I learned how to do it by examining the code from Norm Ross's StartD program. Thus, DPipeLn and StartD can use the same settings files. SD->PgmName = 0; PgmName is the name of the executable that DosStartSession is running. If this is 0 (or NULL) the Session will invoke the a shell--either the one specified in CONFIG.SYS or the one in the settings file. PgmCmdLine = new_string(strlen(Batname) + strlen("/c ")); strcpy(PgmCmdLine, "/c "); strcat(PgmCmdLine, Batname); SD->PgmInputs = PgmCmdLine; PgmInputs is the last of the STARTDATA elements. It points to the command line for the program being invoked. Since we are actually invoking a batch file that runs the desired executable, we need to run it through the shell, thus the /c. ═══ StartD Reference ═══ StartD is a small OS/2 utility written by Norm P. Ross (npross@undergrad. uwaterloo.ca) that starts a Dos session from an OS/2 command line. It is a lot like the built-in start /dos , except it allows a settings file to be specified. The settings file uses the same strings found in a standard Dos settings box. Here's the settings file I use when spawning Borland C/C++ for Dos. EMS_MEMORY_LIMIT=128 XMS_MEMORY_LIMIT=8192 DPMI_DOS_API=ENABLED DPMI_MEMORY_LIMIT=8 IDLE_SENSITIVITY=50 StartD can be found at ftp-os2.nmsu.edu in /pub/os2/2.x/sysutils/startd.zip. ═══ 4.1.2.5.3. main() continued ═══ Now that the STARTDATA struct is set up, we can create the batch file, and start the new session. The session is started asynchronously, so program flow continues after the call. rc = build_batchfile(Batname, Errpname, Command); rc = DosStartSession(&sd, &sess_id, &s_pid); DosStartSession() will return the new session's ID in sess_id, and the appropriate process ID in s_pid. Because DPipeLn might still be busy transferring stdout from the Dos program when the Dos program ends and sends its return code over the pipe, we need to wait for that return code in a separate thread. Starting new threads in OS/2 is easy--almost easier than under Unix: rc = DosCreateThread(&threadid, read_errpipe, targ, tflags, tstack); DosCreateThread() needs the following read_errpipe The name of the function to execute in the thread. targ The address of a parameter block to pass to the function. This allows any information to be passed into the thread in the form of a pointer to some storage. tflags If true, the thread starts right away. If false, the program must call DosResumeThread() to start the thread. tstack Specifies the amount of stack to give to the thread. The main program sets this at 4096 bytes. DosCreateThread() returns the thread ID in threadid. rc = DosConnectNPipe(Piphand); After creating the pipe, we need to connect to it. transfer_data(&Piphand); And now we transfer data from it. transfer_data() loops until the Dos program closes the pipe (by exiting). The data is gathered out of the pipe by a DosRead() call and printed to the stdout of the OS/2 session. rc = DosWaitThread(&threadid, DCWW_WAIT); This waits for the read_errpipe() thread to complete. When it is complete we know that the Dos program (actually the batch file) has reported the exit code. This is the easy way out--normally in a multithreaded program semaphores are used to insure that global variables are accessed only when the data in them is valid. sscanf(RCstring, "%d", &dosreturn); Put the return code into the local dosreturn. The batch file transmits the code as ASCII text, so the sscanf is necessary to convert it to an integer. DosWaitChild(DCWA_PROCESS, DCWW_WAIT, &wp_results, &the_pid, s_pid); This call may not be absolutely necessary, since the Dos program is started as an independent session. It blocks until the Dos session is complete. cleanup(); cleanup() frees allocated storage and closes the named pipes. return (USHORT)dosreturn; Lastly, return the Dos exit code to the calling process. ═══ 4.1.2.6. DPipeLn Examples ═══ Now that you understand how DPipeLn works, let's look at a couple of ways to use it. ═══ 4.1.2.6.1. Make and DPipeLn ═══ Here is a makefile I use that utilizes DPipeLn. Note that GnuMake supports parallel makes, and since OS/2 never loads code more than once, parallel makes can be a real win even with Dos compilers. SHELL=c:/4os2/4os2.exe # Makefile for the School Improvement Program. OBJS=fileio.obj filewins.obj prutils.obj si_chk0.obj si_chk1.obj si_chk2.obj \ si_chk3.obj si_chk4.obj si_chk5.obj si_chk6.obj si_chk7.obj si_chk8.obj \ si_chk9.obj si_chka.obj si_chkb.obj si_chkc.obj si_chkd.obj si_chke.obj \ si_chkf.obj si_chkg.obj si_chkh.obj si_pg0.obj si_pg1.obj si_pg2.obj \ OBJS2=d:\dosapps\borlandc\lib\c0l.obj CFLAGS=-ml -c -If:\tcxl55\inc DEBUGLINK= /m /v DEBUG=-v %.obj : %.c echos $< ^ >> sources.rsp sip.exe: preclean objects $(OBJS) sip.rsp dpipeln.exe -sbcc.ini tlink.exe $(DEBUGLINK) /c @sip.rsp,sip,sip,tcxlbcl cl objects: $(OBJS) dpipeln.exe -sbcc.ini bcc.exe $(DEBUG) $(CFLAGS) @sources.rsp sip.rsp: makefile echo $(OBJS2) + > sip.rsp echo $(OBJS) >> sip.rsp preclean: copy nul sources.rsp clean: erase /q sip.exe sources.rsp sip.rsp $(OBJS) The makefile builds a list of targets in the file sources.rsp, and then passes that to BCC as a response file. BCC only loads itself once, but you could speed up processing by splitting the object files up into two targets and compiling them in parallel. ═══ 4.1.2.7. WF/2 and DPipeLn ═══ If you have IBM's WorkFrame/2 and have read its README file, you may remember that it says that you can use Dos hosted tools, but you cannot see their output. With DPipeLn, you can! Dos command-line tools become just like OS/2 tools under WF/2. Under Language Profile Management, simply specify x:\path\dpipeln.exe as the compiler executable name. Then, under Compiler Options, type the acual name of the compiler, along with any command-line options, in the options dialog. Here's an example using Turbo Pascal 6.0 for Dos. And in the Compiler Options dialog box: ... ═══ 4.1.3. Conclusion ═══ DPipeLn has been a very useful tool for helping to seamlessly integrate my Dos development utilities with OS/2 hosted utilities such as GnuMake, IBM WF/2, and others. Source and executable is available, as mentioned at the beginning of this article. Comments, questions, and suggestions are encouraged. I may be reached at the address found at the end of this document. Happy Hacking! ═══ 4.2. OS/2 Installable File Systems - Part 1 ═══ OS/2 Installable File Systems Part 1 - Introduction Andre Asselin (assela@rpi.edu) ═══ 4.2.1. Introduction ═══ One of the most useful but obscure features of OS/2 is it's support for installable file systems (IFSs). This API allows you to extend OS/2 to do two things: access files stored on disk in formats other than FAT and HPFS, and access files that are stored on a network file server. For example, under many versions of UNIX, files are stored in the Berkeley fast file system format, which isn't compatible with FAT or HPFS. If you have both UNIX and OS/2 installed on the same computer with partitions setup for each, you could write an IFS to read and write the UNIX files while OS/2 is running and give all the programs running under OS/2 (including DOS and Windows programs) access to those files. The other variety of IFS is called a remote file system or redirector, and allows file sharing over LANs. In this case, when a program calls for a file, the IFS acts as a client and sends the request over a network to a file server. The file server handles the request and sends the data back to the client. Examples of this type of IFS include Novell's Netware requester and the requester part of LAN Server. Sometimes, in addition to just sending the requests to the server, this type of IFS also has to translate between OS/2 file conventions and file conventions that the server understands. For example, the NFS (Network File System) protocol is completely UNIX oriented, so the NFS client (which is an IFS) has to handle UNIX conventions like case sensitive filenames and UNIX file attributes. Up until now, there hasn't been much written on how to develop an IFS. It also doesn't help that the documentation on the IFS API has been available only by special request from IBM. This series is going to explore the IFS API in depth with working examples in order to take the mystery out of writing them. This first article is a brief overview of the IFS concept to give a feel for what's involved in writing one. In the coming months, I'm going to explore the IFS architecture in depth and build up a toolkit that can be used as a skeleton for writing an IFS. ═══ 4.2.2. IFS Structure and How It Fits In ═══ An IFS (which is also known as an FSD for File System Driver) can be thought of as a specialized kind of physical device driver (PDD), although it is structured very differently. Instead of having only two entry points and being based on request packets, it is structured as an ordinary 16-bit DLL with a series of predefined entry points, broken up into four groups, that are exported to the system (see Table 1). The first group consists of the primary entry points that all IFSs are required to export. This group does basic file manipulation, such as opening and closing, and reading and writing files. Most of these entry points correspond pretty closely in name and function to related DosXxx calls. The other three groups of calls provide support for the swapper, file locking, and UNC (Universal Naming Convention). For each of these groups, if your IFS doesn't support that particular feature, you don't have to export any of the functions in that group of entry points. Note that exports should be made on a group-by-group basis; don't export only some of the entry points from a group. Figure 1 (below) shows where an IFS fits into the OS/2 system. The two shaded components are what make up an IFS. The piece that runs in ring 0 (called MY.IFS in the figure) is the main component. It is called by the OS/2 kernel through one of its FS_XXX entry points whenever something needs to be done on a drive that the IFS is managing. The IFS performs the function and returns the requested data or an error code. Typically, it will interact with a PDD to do its I/O. Like any ordinary DLL, an IFS doesn't have a process or threads of its own. Instead, it operates in the context of the caller's process and on the caller's thread (in the case of an IFS, the caller's thread and process are the thread and process of the application making the Doscall). In this sense, an IFS can be thought of as a passive library that's not activated until a program calls it. Since an IFS has to be available to every application in the system, it is loaded into global system memory (using GDT selectors). Any memory it allocates (such as for buffers) should also come from global system memory so that no matter which process its entered from, it'll still have access to the memory. The piece that runs in ring 3 (UMY.DLL in the figure) is a utility library. There is one utility DLL for each IFS; the naming convention is a 'U' followed by the IFS name with an extension of .DLL. It has four entry points; one each for FORMAT, RECOVER, SYS, and CHKDSK. When a user runs any of those programs and it detects that it is being run against a drive being managed by an IFS, it will call the appropriate entry point in the IFS's utility DLL. The DLL is then completely responsible for carrying out the command. ═══ 4.2.3. How a Doscall Gets Processed ═══ A typical Doscall (such as DosOpen, DosRead, or DosWrite) is processed mainly by the IFS with some help from the OS/2 kernel (see Figure 2). When an application issues a Doscall, such as DosOpen (1), a component of the OS/2 kernel called the file system router does some preliminary validation of the parameters. If everything's OK, it decides which IFS the call should be sent to and calls the IFS at the appropriate entry point, which in the case of DosOpen is FS_OPENCREATE (2). The IFS then looks at the parameters that were passed and decides on a course of action to service the call. In some cases, it may not support the function (for example, trying to open a remote drive for direct, sector-by-sector access), in which case it immediately returns an error code. If it does support the function, it might first check its cache to see if it already has the requested data, in which case it can just return it. If it doesn't have the data in its cache, it will probably have to do some I/O to get it. In the this case, it calls one of OS/2's file system helpers, FSH_DOVOLIO (3), which acts as an intermediary between the IFS and the device driver. FSH_DOVOLIO generates a request packet and calls the strategy entry point of the appropriate device driver (4). The device driver performs the I/O (5), and eventually returns to FSH_DOVOLIO, which returns back to the IFS. The IFS can then examine the return code for errors and take appropriate action. If it needs to, the IFS can the device driver multiple times until it has all the requested data. It then returns back to the OS/2 kernel, which in turn returns back to the application. The application can then examine the return code to see if the operation was successful. ═══ 4.2.4. Ring 0 Issues ═══ The type of IFS described above runs completely in ring 0 and can be thought of as a 'traditional' IFS. Unfortunately, it can be difficult to develop because of the restrictions placed on code that runs at ring 0 (PDDs and VDDs suffer from these restrictions as well). There are three main restrictions: No preemptive multitasking - When an IFS or any other ring 0 code is entered, preemptive multitasking is temporarily disabled. The IFS can still yield to other threads (i.e. cooperative multitasking), but it will never be suspended unless it specifically asks for it. This has the benefit of eliminating some synchronization issues, but it also means that periodic yields have to be sprinkled throughout lengthy operations. No Doscalls; only file system helpers and DevHlps - None of the ring 3 API's (such as the Doscalls) are available to ring 0 code (except at initialization, which is special). If you use any functions from the C library, you have to make sure that they don't in turn use any of the Doscalls (i.e. malloc and free don't work at ring 0 because they call DosAllocMem). As a partial replacement, there are a series of file system helper (FSH) calls available for IFS's (see Table 2) as well as the standard DevHlp calls available to all device drivers. These APIs perform common tasks such as allocating memory and handling semaphores, but they are more primitive and thus more difficult to use than the Doscalls. Debugging is a pain - If you thought debugging PM applications was tricky, wait until you try an IFS! First, you won't be able to use your favorite debugger-- you have to use a ring 0 debugger, such as the OS/2 kernel debugger or ASDT32. Neither of these can do source level debugging, so you have to know assembler, and they also have limited support for labels. There is also no such thing as a printf in ring 0, so you have to use a hex dump to look at variables. As if that weren't bad enough, OS/2's crash protection doesn't apply to ring 0 code; if your code has a bug and traps, it will bring down the system with an IPE (Internal Processing Error). This is where you get the blue screen, lots of register information, a suggestion to call IBM, etc. Frequent reboots tend to be the way of life. ═══ 4.2.5. The Split IFS / Control Program Approach ═══ Because of these restrictions, a popular approach to developing an IFS is to split it between ring 0 and ring 3. The ring 0 piece (the IFS proper) contains only stub routines and passes all the calls up to a ring 3 control program. The control program handles the calls and then passes the results back to the ring 0 piece. The advantages of doing it this way are that developing the bulk of the IFS is easier (because it runs at ring 3, you don't have to worry about those restrictions mentioned above), and you have access to all the normal ring 3 APIs. This approach also allows you to layer an IFS on top of an already existing IFS; for example, DCF/2 and Stacker for OS/2 probably take this approach to use FAT or HPFS to store their compressed data files. This way they don't have to worry about totally rewriting the FAT or HPFS code-- all they have to be concerned with is compressing the data on the fly and then using the already existing IFSs to actually store it on the disk. This type of IFS is usually implemented using captive threads. Figure 3 traces a call as it winds its way between an application, the IFS, and its control program. When the control program initializes, it creates one or more threads that call into the IFS (1 and 2). The IFS makes these threads block until some application calls the IFS with work to do (3 and 4). The IFS then sets up a parameter area, blocks the application's thread and unblocks the control program's thread. The IFS returns back to the control program (5 and 6), where it executes the request. The control program then puts the results back in the parameter block and calls the IFS again (7 and 8). The IFS then unblocks the application's thread and has it return with the results (9 and 10) while the control program's thread is again blocked waiting for a new request. ═══ 4.2.6. What's Ahead ═══ Well, that's a high level overview of what's involved in writing an installable file system. In the next issue, I'm going to start developing the framework to implement a split ring 0 / ring 3 IFS. To illustrate how to use it, I'm going to write an IFS that supports file versions (like VMS). While you're waiting for the next issue, you may want to start gathering the tools necessary to write an IFS. The first thing you'll need, of course, is the documentation on the IFS API and the libraries and header files that go along with it. To get it, you need to send a note on CompuServe requesting the IFS documentation to Mike Abrams (72420,216) in the OS2DF1 forum section 11 (Device Driver Development) with your name and address. If you don't have an account on CompuServe, you can send him a note using the Internet; address it to 72420.216@compuserve.com (note the period instead of the comma in the number). You'll also need two C compilers. For the ring 0 piece, you'll need a 16-bit C compiler. Most IFS developers use Microsoft C 6.0, but I'm going to investigate the possibility of using Borland C++ instead. If I can make it work, that's what I'm going to use, but the code should be compatible with both compilers. For the ring 3 piece, you'll need a 32-bit compiler, such as IBM's C Set++ or Borland C++ for OS/2. Finally, you probably won't need it right away, but you should also get a hold of either the kernel debugger or ASDT32, which is available through IBM's EWS program. If this series is interesting to you, please drop me a note telling me a little bit about yourself and your background so that I have a better idea who my audience is (it'll also help my motivation if I know that more than Steve and I are reading this series). While you're at it, if you have any feedback or ideas on what you'd like to see covered in future articles, I'd love to hear it. See you next month. ═══ 4.2.7. Table 1 - FSD Entry Points ═══ ┌──────────────────────────────────────────────────────────────────────────────────┐ │Primary Entry Points │ │ │ │File Management │ │ │ │ FS_OPENCREATE Open or create a file │ │ │ │ FS_CLOSE Close a file │ │ │ │ FS_READ Read data from a file │ │ │ │ FS_WRITE Write data to a file │ │ │ │ FS_CHGFILEPTR Change the current position in a │ │ file │ │ │ │ FS_COMMIT Write all cached data for a file to│ │ disk │ │ │ │ FS_DELETE Delete a file │ │ │ │ FS_NEWSIZE Change the size of a file │ │ │ │ FS_MOVE Move or rename a file │ │ │ │ FS_COPY Copy a file │ │ │ │ FS_FILEATTRIBUTE Set or return the standard (DOS) │ │ attributes │ │ │ │ FS_FILEIO Perform an atomic read/write/lock │ │ operation │ │ │ │ FS_FILEINFO Set or return information on an │ │ open file │ │ │ │ FS_PATHINFO Set or return information on a file│ │ │ │Volume Management │ │ │ │ FS_ATTACH Associate or disassociate a drive │ │ letter with an FSD │ │ │ │ FS_MOUNT Examine a volume to determine if │ │ the FSD knows its format │ │ │ │ FS_FSINFO Get/Set file system information │ │ │ │ FS_FLUSHBUF Write all cached data for a volume │ │ to disk │ │ │ │Directory Management │ │ │ │ FS_CHDIR Change directory │ │ │ │ FS_MKDIR Make a new directory │ │ │ │ FS_RMDIR Remove a directory │ │ │ │Directory Search │ │ │ │ FS_FINDCLOSE End a search in progress │ │ │ │ FS_FINDFIRST Begin a new search │ │ │ │ FS_FINDFROMNAME Restart a search from a particular │ │ point │ │ │ │ FS_FINDNEXT Return next file name │ │ │ │ FS_FINDNOTIFYCLOSE Stop monitoring a directory for │ │ changes │ │ │ │ FS_FINDNOTIFYFIRST Start monitoring a directory file │ │ changes │ │ │ │ FS_FINDNOTIFYNEXT Return the next set of changes │ │ │ │FSD Extended │ │Interface │ │ │ │ FS_FSCTL Extended interface to an FSD │ │ │ │ FS_IOCTL I/O control for devices │ │ │ │Miscellaneous │ │ │ │ FS_NMPIPE Support named pipe operations │ │ │ │ FS_PROCESSNAME Allow an FSD to canonicalize a file│ │ name to its own particular │ │ conventions │ │ │ │ FS_SETSWAP Designate a partition as the swap │ │ partition │ │ │ │ FS_INIT Initialize the FSD │ │ │ │ FS_SHUTDOWN Notify the FSD that the system is │ │ shutting down │ │ │ │ FS_EXIT Notify the FSD that a process is │ │ ending │ │ │ │Swapper Entry Points │ │ │ │ FS_ALLOCATEPAGESPACE Allocate space in the swapper file │ │ │ │ FS_DOPAGEIO Read or write page(s) to the │ │ swapper file │ │ │ │ FS_OPENPAGEFILE Open the swapper file │ │ │ │File Locking Entry │ │Points │ │ │ │ FS_CANCELLOCKREQUEST Cancels a lock request │ │ │ │ FS_FILELOCKS Lock or unlock a region of a file │ │ │ │UNC Entry Point │ │ │ │ FS_VERIFYUNCNAME Determine if FSD provides access to│ │ a specific UNC server │ └──────────────────────────────────────────────────────────────────────────────────┘ ═══ 4.2.8. Table 2 - File System Helpers ═══ ┌───────────────────────────────────────────────────────────────────────┐ │FSH_ADDSHARE Add a name to the sharing set │ │ │ │FSH_CALLDRIVER Call a PDD's extended strategy │ │ entry point │ │ │ │FSH_CANONICALIZE Convert a filename to canonical │ │ form │ │ │ │FSH_CHECKEANAME Verify correctness of an EA name │ │ │ │FSH_CRITERROR Signal a hard error │ │ │ │FSH_DEVIOCTL Send a PDD an IOCTL request │ │ │ │FSH_DOVOLIO Call a PDD to transfer sectors │ │ │ │FSH_FINDCHAR Find first occurrence of a │ │ character in a string │ │ │ │FSH_FINDDUPHVPB Locates equivalent hVPBs │ │ │ │FSH_FORCENOSWAP Forces a segment permanently into │ │ memory │ │ │ │FSH_GETPRIORITY Get current thread's priority │ │ │ │FSH_GETVOLPARM Get data for current volume │ │ │ │FSH_INTERR Signal an Internal Processing Error│ │ │ │FSH_IOBOOST Give the current thread an I/O │ │ boost │ │ │ │FSH_IOSEMCLEAR Clear an I/O event semaphore │ │ │ │FSH_ISCURDIRPREFIX Check if current directory is a │ │ prefix of a file name │ │ │ │FSH_LOADCHAR Load character from a string (NLS) │ │ │ │FSH_NAMEFROMSFN Retrieve a file's name from its │ │ file handle number │ │ │ │FSH_PREVCHAR Move one character backwards in a │ │ string (NLS) │ │ │ │FSH_PROBEBUF Verify a region of memory for │ │ accessibility │ │ │ │FSH_QSYSINFO Retrieve system information │ │ │ │FSH_REGISTERPERFCTRS Register the IFS with PERFVIEW │ │ │ │FSH_REMOVESHARE Remove a name from the sharing set │ │ │ │FSH_SEGALLOC Allocate a segment │ │ │ │FSH_SEGFREE Deallocate a segment │ │ │ │FSH_SEGREALLOC Reallocate a segment │ │ │ │FSH_SEMCLEAR Clear a semaphore │ │ │ │FSH_SEMREQUEST Request a semaphore │ │ │ │FSH_SEMSET Set a semaphore │ │ │ │FSH_SEMSETWAIT Set a semaphore and wait on it │ │ │ │FSH_SEMWAIT Wait on a semaphore │ │ │ │FSH_SETVOLUME Force a volume to be mounted on a │ │ drive │ │ │ │FSH_STORECHAR Store a character in a string (NLS)│ │ │ │FSH_UPPERCASE Uppercase a string (NLS) │ │ │ │FSH_WILDMATCH Match a filename using wildcards │ │ │ │FSH_YIELD Yield the CPU to higher priority │ │ threads │ └───────────────────────────────────────────────────────────────────────┘ ═══ 4.3. Programming the Container Control - Part 1 ═══ Programming the Container Control Larry Salomon, Jr. (os2man@panix.com) ═══ 4.3.1. Introduction ═══ The release of OS/2 2.0 was special in many ways; not the least was the inclusion of four new standard PM controls: the container, the notebook, the slider, and the value set. Without question, the most difficult of these to utilize fully is the container control. It's flexible design allowed for it to be used in a variety of fashions (which is no surprise when you look at its real-world counterpart) and provided a CUA 91 compliant interface without much additional work by the programmer. Unfortunately, the initial work that has to be done is significant and has daunted many developers. This article begins a multi-part series on how to program the container and will describe the container control from a developer's perspective as well as introduce some of the basic programming concepts that can be used in you applications. For additional information, it is highly recommended that you acquire a copy of the Winter and Spring 1993 issues of the OS/2 Developer magazine (produced by IBM Corp. and published by Miller Freeman Inc.). These two issues contain a single article each by the container control developers on how to use it and are both very informative. ═══ 4.3.2. A Bird's-Eye View ═══ In "real-life" a container is something that you see everyday. Most people store foodstuffs in Tupperware containers. On many office desks, pens and pencils are stored in containers. In a filing cabinet, documents are stored in another container, although it is more widely known as a folder. Everywhere you look, there are containers being used; because of this, the CUA designers decided that the ability to store objects in a container was a very good thing; not only was it a familiar concept, but it was also already being used on another computer platform (the MacIntosh) with extremely good results. The container is exactly what you think it is. It allows an application to place "things" inside of it and these things can be viewed in one of five different ways. The term "thing" is used because the container does not restrict the application in the type of items stored. The five different views supported by the container are listed below: View Description Icon Each object is represented as an icon with a descriptive string below it. Name Each object is represented as an icon with a descriptive string to the right. Text Each object is represented solely as a descriptive string. Tree Each object is represented as a hierarchical diagram of icons and/or descriptive strings. Detail Each object is represented as a detailed description of the object. The information displayed is defined by the application. This month, we will only employ the icon view for simplicity. The other views will be covered in subsequent months. Each object is described to the container using a base data type - this can be either the RECORDCORE or the MINIRECORDCORE structure, depending on the CCS_ style used when creating the control. (For the purposes of our discussion, the MINIRECORDCORE structure will be referenced although either could be used.) When objects are allocated, additional memory at the end of the MINIRECORDCORE structure can be requested, for the storage of any object-specific data. Thus, you would have to define a structure to contain both the base data and the additional fields needed: typedef struct _MYCNRREC { MINIRECORDCORE mrcCore; CHAR achText[256]; ULONG ulNumSold; float fGrossIncome; float fNetIncome; float fTotalCost; float fNetProfit; } MYCNRREC, *PMYCNRREC; Since the container, when returning pointers to the object records, always returns a PRECORDCORE type, you will need to typecast this to your application-defined type (here, PMYCNRREC). ═══ 4.3.3. Basic Record Operations ═══ Inserting and Removing Records The container makes a distinction between allocation and insertion of records; likewise, a similar distinction is made between removing a record and freeing the memory consumed by the record. Allocation is accomplished by sending the control a CM_ALLOCRECORD message, while insertion is done by sending a CM_INSERTRECORD message. The CM_ALLOCRECORD requires the number of additional bytes needed in mpParm1 and the number of records to allocate in mpParm2. If more than one record is allocated, a linked list of the records is returned (with the linked being the preccNextRecord field in the MINIRECORDCORE structure); otherwise, a pointer to the record is returned. The container will initialize the entire structure to "nothing" (meaning no icon, no text, etc.). It is your responsibility to "fill-in-the-blanks" so that something meaningful will appear in the control; for the icon view, these fields are the hptrIcon and pszIcon fields of the MINIRECORDCORE structure. You should set them to the handle of the icon and to the pointer to the descriptive text to be displayed, respectively. Using our MYCNRREC structure again as an example: ULONG ulExtra; PMYCNRREC pmcrRecord; ulExtra=sizeof(MYCNRREC)-sizeof(MINIRECORDCORE); pmcrRecord=(PMYCNRREC)PVOIDFROMMR(WinSendMsg(hwndCnr, CM_ALLOCRECORD, MPFROMLONG(ulExtra), MPFROMSHORT(1))); if (pmcrRecord!=NULL) { pmcrRecord->mrcCore.hptrIcon=WinLoadPointer(HWND_DESKTOP, NULLHANDLE, HPTR_PRODUCT); pmcrRecord->mcrCore.pszIcon=pmcrRecord->achText; strcpy(pmcrRecord->achText,"Thing-A-Ma-Jig"); } /* endif */ Once you have allocated, initialized, and are ready to insert your records, you need to describe the placement of the records to the container (via the RECORDINSERT structure) to the container. An item of interest is that the container has two notions of order: z-order and item-order. The former should be familiar because PM windows all have this concept. The latter, however, is useful when you wish to enumerate the records in an order other than the z-order placement without affecting the appearance of the records in the container. Continuing our example above: RECORDINSERT riInsert; riInsert.cb=sizeof(RECORDINSERT); riInsert.pRecordOrder=CMA_FIRST; /* Insert at the head of the list */ riInsert.pRecordParent=NULL; /* No parent record */ riInsert.fInvalidateRecord=TRUE; /* Automatic invalidation of records */ riInsert.zOrder=CMA_TOP; /* Place on top of siblings */ riInsert.cRecordsInsert=1; WinSendMsg(hwndCnr, CM_INSERTRECORD, MPFROMP(pmcrRecord), MPFROMP(&riInsert)); A couple of important things should be noted here: o It is very inefficient to allocate and insert records one at a time. If at all possible, allocate many records and insert them all at once. o You can specify FALSE for the fInvalidateRecord field to cause the container not to invalidate the records once they are inserted. Doing this allows you to reflow - or arrange - the records in the container before displaying them ( using the CM_ARRANGE) message. If you choose to do this, you must send a CM_INVALIDATERECORD message specifying CMA_REPOSITION and CMA_ERASE to redraw the container. To remove a record from the list, you use the CM_REMOVERECORD message. If you choose not to have the container free the memory (using the CMA_FREE flag), you must also send a CM_FREERECORD message to return the memory to the system. A "gotcha" here is that the first parameter of the two messages is a pointer to an array of pointers. Thus, if you are only removing one record, you need to specify a pointer to the record pointer: PMYCNRREC pmcrRecord; /* Query the first record in the container */ pmcrRecord=(PMYCNRREC)PVOIDFROMMR(WinSendMsg(hwndCnr, CM_QUERYRECORD, MPFROMP(NULL), MPFROM2SHORT(CMA_FIRST, CMA_ITEMORDER))); /* Remove it and free the memory */ WinSendMsg(hwndCnr, CM_REMOVERECORD, MPFROMP(&pmcrRecord), MPFROMSHORT(CMA_FREE)); /* We now need to invalidate the container */ WinSendMsg(hwndCnr, CM_INVALIDATERECORD, MPFROMP(NULL), MPFROM2SHORT(0,CMA_REPOSITION|CMA_ERASE)); Arranging the Records Arranging the records is as simple as sending a CM_ARRANGE message. This message takes no parameters. the record pointer: WinSendMsg(hwndCnr, CM_ARRANGE, 0, 0); ═══ 4.3.4. Using the CM_SETCNRINFO Message ═══ To change the value any of a multitude of attributes belonging to the container, you can use the CM_SETCNRINFO message. This controls everything about the container including the current view type, which is probably its most widely used feature. The way this message works is that you define a variable of type CNRINFO, set the appropriate fields, and send the message. In the second parameter, you specify one or more flags which tells the container which fields to examine. Thus, you do not have to initialize the entire structure for one field. the record pointer: CNRINFO ciInfo; /* Change the view to icon view */ ciInfo.flWindowAttr=CV_ICON; WinSendMsg(hwndCnr, CM_SETCNRINFO, MPFROMP(&ciInfo), MPFROMLONG(CMA_FLWINDOWATTR)); This provides a very limited example of the CM_SETCNRINFO message. For more information about the CNRINFO structure see the Technical Reference, available with the Programming Toolkit. ═══ 4.3.5. Basic Notifications ═══ The container will notify its owner via the WM_CONTROL message whenever any of a multitude of events occur. Five of the more basic notifications are listed: Code Meaning CN_ENTER The user pressed ENTER or double clicked the mouse while the container had the focus. CN_HELP The user pressed F1 while the container had the focus. CN_CONTEXTMENU The user pressed the context menu key combination as defined on the System settings. The default for the system is Ctrl+F10. CN_SETFOCUS The container has received the focus. CN_KILLFOCUS The container is losing the focus. Next month we'll briefly describe a few others and their meanings. ═══ 4.3.6. Next Month ═══ Next month, we will look at the tree view as well as look at a few other notifications. Also, we will build our first sample application using the concepts described here and in next month's article. ═══ Example Container Object Record ═══ Used as the object record type throughout this article, the definition is below: typedef struct _MYCNRREC { MINIRECORDCORE mrcCore; CHAR achText[256]; ULONG ulNumSold; float fGrossIncome; float fNetIncome; float fTotalCost; float fNetProfit; } MYCNRREC, *PMYCNRREC; ═══ 5. Columns ═══ o Questions and Answers o Introduction to PM ═══ 5.1. Questions and Answers ═══ Welcome to this month's "Questions and Answers"! Each month, I collect various questions sent to me via email and try to answer each directly; the ones that I feel contribute the most to developers, whether in terms of information or as a nifty trick to tuck into your cap, get published in this column. To submit a question, send mail to my email address - os2man@panix.com - and be sure to grant permission to publish your question (those that forget will not be considered for publication). This month's deluge of questions covered the following topics: o Using Presentation Parameters o Context-specific Dialogs o Adjusting the Priority of a Non-related Process o Hooking the Kernel APIs o Using MMPM/2 to Record and Playback Audio Samples ═══ 5.1.1. Using Presentation Parameters ═══ Les Chan (chan@holo.ps.iit.nrc.ca) writes: How do I create buttons (and other standard classes) with custom fonts? I think the way to do this is with the last parameter in WinCreateWindow, which is PVOID pPresParams. But I can't figure out how to use it. Also, once the window is created, one should be able to change the font with WinSetPresParams. And I can't get this to work consistently (the window is created with pPresParams=NULL). So, what is the correct way to create and manipulate presentation parameters? To answer your second question first, the most common problem with using WinSetPresParam is not passing the correct values for the parameters; this often manifests itself when attempting to change the font of a control. What most developers do not know, when using a string for the value of the presentation parameter, is that you must add one to the length for the `\0' byte at the end. WinSetPresParam(hwndButton, PP_FONTNAMESIZE, strlen("12.Helv"), "12.Helv"); WinSetPresParam(hwndButton, PP_FONTNAMESIZE, strlen("12.Helv")+1, "12.Helv"); Thus, the first example is incorrect and will yield indeterminable results, while the second will work as expected. Using WinSetPresParam will work regardless of the value of pPresParam on the WinCreateWindow call. Your first question is a bit more complex, due to the fact that the online and hardcopy documentation for 1.x and 2.0 are incorrect. Piecing together the information from all sources reaps the solution, however. In the WinCreateWindow call, the last parameter is defined to be of type PVOID when in reality it is of type PPRESPARAMS. This type is a variable length structure which contains one or more presentation parameters to be passed to the window (I won't review the structure definition, since you can do that on your own). By initializing this structure properly, you can set the appropriate characteristics on the call to WinCreateWindow as in the example below: ULONG ulSzFont; BYTE abBuf[sizeof(PRESPARAMS)+256]; PPRESPARAMS pppParm; ulSzFont=strlen("18.Helv")+1; pppParm=(PPRESPARAMS)abBuf; pppParm->cb=sizeof(PARAM)+ulSzFont-1; pppParm->aparam[0].id=PP_FONTNAMESIZE; pppParm->aparam[0].cb=ulSzFont; strcpy(pppParm->aparam[0].ab,"18.Helv"); if (WinCreateWindow(hwndWnd, WC_BUTTON, "Test", WS_VISIBLE|BS_PUSHBUTTON, 100, 100, 200, 100, hwndWnd, HWND_TOP, 1, NULL, pppParm)==NULLHANDLE) { WinAlarm(HWND_DESKTOP,WA_ERROR); return MRFROMSHORT(FALSE); } /* endif */ An interesting note here is that, when testing this, this code did not result in an error, yet no button was displayed when I placed this in the WM_CREATE processing. ═══ 5.1.2. Context-specific Dialogs ═══ A user who wishes to remain anonymous writes: I'm writing a program that is essentially a dialog. I know how to do that. But I want to have a portion be dependant on another part, but don't know how to make that happen. For example, when you click on an radio button in the top half, the bottom half changes so you can change settings for it. If you have any experience using the notebook, you can design your main dialog in a related fashion. For each radio button in the top half, call WinLoadDlg to load an auxilliary dialog specific to the item that radio button represents. Then, when you receive the WM_CONTROL/ BN_CLICKED notification message, call WinShowWindow both to hide the currently displayed dialog and to display the dialog corresponding to the button selected. As a caveat, define the auxilliary dialogs as having no border and not having any frame controls (FCF_ flags). You will also need to call WinSetWindowPos after loading them to set their position to be the lower half of the main dialog. ═══ 5.1.3. Adjusting the Priority of a Non-related Process ═══ Roman Stangl (86505339awiwuw11.wu-wien.ac.at) writes: How can the priority of any running process be adjusted, without these processes being child processes of the program that wants to adjust the priority. The answer is that you can't, at least as far as I am aware. If you have the source to the child processes, you can modify them to accept a command line parameter specifying the desired priority class and value and then have them call DosSetPriority. ═══ 5.1.4. Hooking the Kernel APIs ═══ Roman Stangl (86505339awiwuw11.wu-wien.ac.at) again writes: How can the OS/2 API DosOpen() be hooked to trace write accesses to files, similar to a DOS TSR that hooks into INT21? The only way I know of to accomplish what your second question is asking is by writing your own IFS (installable file system). You might be able to also do this using device monitors, but I cannot be sure. My apologies for the lack of a concrete answer to this. ═══ 5.1.5. Using MMPM/2 to Record and Playback Audio Samples ═══ Marc Van-Woerkom (Marc_Van-Woerkom@ac3.maus.de) writes: I own a Soundblaster card and want to use it under OS/2! It's FM synthesizers generate some nice sounds but I think the best effects are produced by it's digital channel. For this I need a driver/routines that allow me to record and play a sample. A solution already exists (sbos2 package) but that's only an intermediate solution -- with OS/2 2.1 MMPM/2 will become the standard to deal with. So is there a MMPM/2 API which allows me to record/play samples? In the Summer 1992 issue of the OS/2 Developer magazine, there is an article entitled "Listen to the Sounds: The MMPM/2 Audio Subsystem" starting on page 98. In this article, the author discusses how both audio samples and MIDI sequences are used in the MMPM/2 system. Since I personally have no experience using MMPM/2, I will not try to explain the article in order to avoid confusing anyone, but will consider the reference as an affirmative answer to your question. Now that the magazine is published by Miller Freeman, I am unsure if back issues can be ordered. If so, the IBM Mechanicsburg number is G362-0001; if not, you can try contacting the magazine via the Internet at the address os2mag@vnet.ibm.com for the possibility of article reprints. ═══ 5.2. Introduction to PM Programming - Part III ═══ Introduction to PM Programming - Part III Gavin Baker (demogrb@lust.latrobe.edu.au) ═══ 5.2.1. Introduction ═══ Introduction Menus, dialog boxes and icons are all examples of Resources in OS/2. Resources are stored in their own read-only segment within the executable file (or DLL), can be shared between multiple instances of a program, and are normally loaded only when required at runtime rather than at the load time of the application. The most commonly used resource types along with their definition are shown below: ┌────────────────────┬──────────────────────────────┐ │Type │Description │ ├────────────────────┼──────────────────────────────┤ │ACCELTABLE │Keyboard Accelerator table │ ├────────────────────┼──────────────────────────────┤ │BITMAP │Bitmap │ ├────────────────────┼──────────────────────────────┤ │DIALOG │Dialog box template │ ├────────────────────┼──────────────────────────────┤ │FONT │Font │ ├────────────────────┼──────────────────────────────┤ │MENU │Menu definition │ ├────────────────────┼──────────────────────────────┤ │MESSAGETABLE │Application Message Strings │ ├────────────────────┼──────────────────────────────┤ │POINTER │Icon or mouse pointer │ ├────────────────────┼──────────────────────────────┤ │RCDATA* │Custom-defined resource │ ├────────────────────┼──────────────────────────────┤ │STRINGTABLE │Strings │ └────────────────────┴──────────────────────────────┘ Resources are created in two ways (depending on the type of resource). They are either text or binary. The binary resources are POINTER, BITMAP, and FONT. All the other resources are textual. The RCDATA type is special, as it allows the programmer to define a custom structure for a resource. The RC.EXE program is called the Resource Compiler, and it takes the your source file and compiles it into a binary form (.RES). It also binds the .RES file with your executable. We will examine the general syntax of resources and the structure of the RC file, and then examine each type in detail. ═══ 5.2.2. Scope ═══ Scope I am assuming that you are a competent C programmer, and have a working knowledge of OS/2 from a user's perspective. The sample code here was produced with Borland's Resource Workshop for PM and tested with the OS/2 Resource Compiler (RC). ═══ 5.2.3. Syntax and the RC File ═══ Syntax and the RC File Resources are defined in a text file with the .RC extension. There are a number of keywords for the different resource types, and each keyword defines one resource. Resources are referenced by an identifier, which takes the form of a C #define. Since the Resource Compiler (RC.EXE) supports C-like #includes and #defines, it is convenient to have all the symbols defined in one header file for the resources and their IDs. (Obviously they must have unique numbers). Resources are usually named according a certain convention - ID followed by a single letter denoting the resource type, an underscore and then a descriptive name. Here are some examples: #define IDM_FILE 10 /* the ID of the FILE Menu */ #define IDS_ERROR 300 /* the ID of the ERROR String */ #define IDD_OPTIONS 436 /* the ID of the OPTIONS Dialog */ Generally speaking, a resource statement consists of the following: TYPE ──── IDENTIFIER ─┬───────────┬─ RESOURCE DEFINITION ────┤ │ │ │ MEMORY │ └─ OPTIONS ─┘ Due to the differing nature of resources, it is easier to show the syntax for each type's definition rather than to generalize here. The options refer to the Memory Options, which specify how OS/2 treats the memory occupied by the resource. The memory options are as follows: ┌────────────────────┬────────────────────────────────────────────────────────────┐ │Option │Meaning │ ├────────────────────┼────────────────────────────────────────────────────────────┤ │FIXED or │Resource remains at a fixed point in memory │ │MOVEABLE* │Resource can be moved by the system in memory │ ├────────────────────┼────────────────────────────────────────────────────────────┤ │PRELOAD or │Resource is loaded at load-time │ │LOADONCALL* │Resource is loaded when first referenced (run-time) │ ├────────────────────┼────────────────────────────────────────────────────────────┤ │DISCARDABLE │Resource can be discarded if not needed │ └────────────────────┴────────────────────────────────────────────────────────────┘ Note that the default options are marked with an asterisk. These options apply to all of the resource types. ═══ 5.2.4. Resources ═══ Resources In the following sections we will see each type of resource (at least the most common ones) and some examples, along with how to use them in your programs. The syntax diagrams follow those conventions used in the Command Reference, but also note that keywords are underlined. ═══ 5.2.4.1. String Table ═══ String Table Syntax STRINGTABLE ──── IDENTIFIER ─┬───────────┬─ BEGIN ──── │ │ │ MEMORY │ └─ OPTIONS ─┘ ┌─────────────────────────────┐ │ │  │ ─ ─ ──── IDENTIFIER ──── STRING ──┴── ─ ─ ──── END ────┤ Notes String tables are used to keep all of the static text which is used in a program in one place, thus making it easier to manage, and also to maintain versions in multiple languages. Ideally the program source code should be free of static or literal text, and instead use a STRINGTABLE to manage the text. Strings are loaded by using the WinLoadString call, and once loaded, are used exactly the same way as a normal string. Example STRINGTABLE ID_ERRORS FIXED BEGIN IDS_FILENOTFOUND "Sorry - I can't find that file anywhere!" IDS_NOGAMES "Games are not allowed during office hours." IDS_BYEBYE "Are you sure you really truly want to go?" END Output There is no output as such from this resource - you would use it in your program like this: WinLoadString(hab, NULLHANDLE, IDS_BADCMD, MAXERRORLEN, pszError); WinMessageBox(HWND_DESKTOP,HWND_DESKTOP,pszError,"Error!",0, MB_CUACRITICAL | MB_OK); This will load an error message from the executable file and display an error message in a box. ═══ 5.2.4.2. Message Table ═══ Message Table Syntax MESSAGETABLE ──── IDENTIFIER ─┬───────────┬─ BEGIN ──── │ │ │ MEMORY │ └─ OPTIONS ─┘ ┌─────────────────────────────┐ │ │  │ ─ ─ ──── IDENTIFIER ──── STRING ──┴── ─ ─ ──── END ────┤ Notes A Message table works just the same way as a STRINGTABLE. Most applications use a STRINGTABLE, or use the MKMSGF (the Make MeSsaGe File utility, documented in the Toolkit Tools Reference). Example MESSAGETABLE ID_ERRORS BEGIN IDM_PRINTEDOK "The file has been successfully printed." IDM_SYNTAX "A syntax error was found on line %1." END ═══ 5.2.4.3. Menu ═══ Menu Syntax MENU ──── IDENTIFIER ─┬───────────┬─ BEGIN ──── │ │ │ MEMORY │ └─ OPTIONS ─┘ ┌─────────────────────────────────────────────────────────────────────────────────┐ │ │  │ ─ ─ ──── SUBMENU ──── TEXT ──── IDENTIFIER ──── STYLE ──── BEGIN ──── │ │ ┌──────────────────────────────────────────────────────────────────────────┐ │ │ │ │  │ │ ─ ─ ──── MENUITEM ──── TEXT ──── IDENTIFIER ──── STYLE ──── ATTRIBUTES ──┴─ │ │ │ ─ ─ ──── END ───────────────────────────────────────────────────────────────────────┴─── ─ ─ ──── END ────┤ Notes I hope this diagram isn't too confusing - if you examine it, all it means is that a MENU consists of one or more SUBMENUs, which consist of one or more MENUITEMs. You can leave off the style and attributes if you don't want to specify any (see the example). Put a tilde ~ character in to specify that the following letter will be the underlined shortcut key. Note: If you make the ID of the menu the same as your main window that you create with WinCreateStdWindow and use the FCF_MENU style when you create it, PM will automatically load the menu and assign it to the window. Example MENU ID_MAIN BEGIN SUBMENU "~File", IDM_FILE BEGIN MENUITEM "~New", IDM_FILENEW MENUITEM "~Open...", IDM_FILEOPEN MENUITEM "~Save", IDM_FILESAVE MENUITEM "Save ~As...",IDM_FILESAVEAS END SUBMENU "Fon~t", IDM_FONT BEGIN MENUITEM "~Small", IDM_FONTSMALL MENUITEM "~Medium", IDM_FONTMED, MIS_TEXT, MIA_CHECKED MENUITEM "~Large", IDM_FONTLARGE MENUITEM SEPARATOR MENUITEM "~Options...",IDM_FONTOPTIONS END END Output ═══ 5.2.4.4. Dialog ═══ Dialog Syntax The dialog template itself: DLGTEMPLATE ──── IDENTIFIER ─┬───────────┬─ BEGIN ──── │ │ │ MEMORY │ └─ OPTIONS ─┘ ─ ─ ──── DIALOG ──── TITLE, ID, X, Y, WIDTH, HEIGHT ─┬──────────┬─ BEGIN ──── │ │ └─ STYLES ─┘ ┌───────────────────────┐ │ │  │ ─ ─ ──── CONTROL DEFINITION ─┴── ─ ─ ──── END ──── ─ ─ ──── END ────┤ The Control Definition: CONTROL ──── TEXT, ──── ID, ──── X, ──── Y, ──── ─ ─ ──── WIDTH, ──── HEIGHT, ─┬──────────┬─┤ │ │ └─ STYLES ─┘ Notes A dialog template is used to define a dialog, which contains many controls. The control can be a custom control, or one of the following: ■ AUTOCHECKBOX ■ AUTORADIOBUTTON ■ CHECKBOX ■ DEFPUSHBUTTON ■ GROUPBOX ■ PUSHBUTTON ■ RADIOBUTTON ■ ENTRYFIELD to name a few. Consult the Toolkit documentation for a complete description of all the different controls. Once you know how to use one, the others work basically the same. Example This example was produced with Borland's Resource Workshop for PM (part of Borland C++ for OS/2), which makes it easy to visually design dialogs and other resources. The IBM Toolkit also includes a Dialog Editor. As you can see, a reasonably simple dialog can get fairly complex. If you were to write this by hand it would take hours to figure out the X and Y positions, flags, etc. This is not practical for anything but the simplest of dialogs, so a dialog editor (such as the two mentioned above) is invaluable. This may seem complicated, but it is fairly straightforward. Everything except the buttons is defined as a CONTROL (a generic way of specifying a control). The type of control is given in the flags, for example the comment edit field is defined as a WC_ENTRYFIELD, and has its own specific flags (ES_LEFT and the others). There are lots of flags specified (more than if you had done it by hand) but most have reasonable defaults. Three things to note: ■ The order is important - it is the order the user tabs through in. ■ The WS_TABSTOP flag must be specified if you want the user to be able to tab to the control under keyboard control. ■ Groups (WS_GROUP) are mainly used to group together automatic radio buttons, so they turn each other off and on. DLGTEMPLATE 1 BEGIN DIALOG "Sample Dialog", 100, 30, 90, 300, 150, WS_VISIBLE, FCF_SYSMENU | FCF_TITLEBAR BEGIN CONTROL "", 109, 44, 104, 72, 35, WC_COMBOBOX, CBS_DROPDOWN | ES_ANY | WS_VISIBLE | WS_GROUP | WS_TABSTOP CONTROL "", 112, 156, 128, 133, 9, WC_ENTRYFIELD, ES_LEFT | ES_AUTOSCROLL | ES_MARGIN | ES_ANY | WS_VISIBLE | WS_GROUP | WS_TABSTOP CONTROL "~Preload", 102, 20, 96, 50, 10, WC_BUTTON, BS_AUTORADIOBUTTON | WS_VISIBLE | WS_GROUP | WS_TABSTOP CONTROL "~Load on Call", 103, 20, 80, 72, 10, WC_BUTTON, BS_AUTORADIOBUTTON | WS_VISIBLE | WS_TABSTOP CONTROL "~Fixed", 104, 104, 96, 50, 10, WC_BUTTON, BS_AUTORADIOBUTTON | WS_VISIBLE | WS_GROUP | WS_TABSTOP CONTROL "~Moveable", 105, 104, 80, 50, 10, WC_BUTTON, BS_AUTORADIOBUTTON | WS_VISIBLE | WS_TABSTOP CONTROL "~Discardable", 106, 20, 56, 68, 10, WC_BUTTON, BS_AUTOCHECKBOX | WS_VISIBLE | WS_GROUP | WS_TABSTOP CONTROL "", 107, 172, 84, 48, 12, WC_SPINBUTTON, SPBS_MASTER | SPBS_ALLCHARACTERS | SPBS_JUSTLEFT | WS_VISIBLE | WS_GROUP | WS_TABSTOP CONTROL "", 108, 236, 84, 48, 12, WC_SPINBUTTON, SPBS_MASTER | SPBS_ALLCHARACTERS | SPBS_JUSTLEFT | WS_VISIBLE | WS_GROUP | WS_TABSTOP CONTROL "", 113, 172, 56, 48, 12, WC_SPINBUTTON, SPBS_MASTER | SPBS_ALLCHARACTERS | SPBS_JUSTLEFT | WS_VISIBLE | WS_GROUP | WS_TABSTOP CONTROL "", 114, 236, 56, 48, 12, WC_SPINBUTTON, SPBS_MASTER | SPBS_ALLCHARACTERS | SPBS_JUSTLEFT | WS_VISIBLE | WS_GROUP | WS_TABSTOP CONTROL "", 121, 16, 26, 268, 12, WC_ENTRYFIELD, ES_LEFT | ES_AUTOSCROLL | ES_MARGIN | ES_ANY | WS_VISIBLE | WS_GROUP | WS_TABSTOP CONTROL "~Memory Options", 101, 10, 48, 150, 72, WC_STATIC, SS_GROUPBOX | DT_LEFT | DT_TOP | DT_MNEMONIC | WS_VISIBLE | WS_GROUP CONTROL "T~ype", 110, 8, 128, 27, 8, WC_STATIC, SS_TEXT | DT_LEFT | DT_TOP | DT_MNEMONIC | WS_VISIBLE | WS_GROUP CONTROL "~Text", 111, 130, 128, 24, 8, WC_STATIC, SS_TEXT | DT_LEFT | DT_TOP | DT_MNEMONIC | WS_VISIBLE | WS_GROUP CONTROL "X", 115, 172, 96, 32, 8, WC_STATIC, SS_TEXT | DT_LEFT | DT_TOP | DT_MNEMONIC | WS_VISIBLE CONTROL "Size & Position", 116, 163, 48, 129, 72, WC_STATIC, SS_GROUPBOX | DT_LEFT | DT_TOP | DT_MNEMONIC | WS_VISIBLE | WS_GROUP CONTROL "Y", 117, 238, 96, 32, 8, WC_STATIC, SS_TEXT | DT_LEFT | DT_TOP | DT_MNEMONIC | WS_VISIBLE CONTROL "Width", 118, 172, 72, 45, 8, WC_STATIC, SS_TEXT | DT_LEFT | DT_TOP | DT_MNEMONIC | WS_VISIBLE CONTROL "Height", 119, 236, 72, 45, 8, WC_STATIC, SS_TEXT | DT_LEFT | DT_TOP | DT_MNEMONIC | WS_VISIBLE CONTROL "Comment", 120, 10, 20, 282, 28, WC_STATIC, SS_GROUPBOX | DT_LEFT | DT_TOP | DT_MNEMONIC | WS_VISIBLE | WS_GROUP DEFPUSHBUTTON "OK", DID_OK, 6, 4, 51, 14 PUSHBUTTON "Cancel", DID_CANCEL, 60, 4, 51, 14 PUSHBUTTON "Help", 998, 114, 4, 51, 14, BS_HELP END END Output ═══ 5.2.4.5. Fonts, Icons, Pointers, and Bitmaps ═══ Fonts, Icons, Pointers, and Bitmaps Syntax ─┬─ FONT ─┬─ IDENTIFIER ─┬───────────┬─ FILENAME ────┤ │ BITMAP │ │ MEMORY │ └─ POINTER ─┘ └─ OPTIONS ─┘ Notes Fonts, icons, pointers and bitmaps are all treated in the same way. (In fact icons and pointers are more or less identical). They are all defined externally in their own binary file, and are bound in when the resources are compiled. There are editors available to design your own binary resources. An Icon Editor (also used for pointers and small bitmaps) is included in base OS/2, and there is a Font Editor in the Toolkit. Example BITMAP IDB_SMILEY Smiley.Bmp ICON IDI_MYPROG Whiz.Ico FONT IDF_BIGFONT Large.Fnt Output ═══ 5.2.5. Roundup ═══ Roundup Well, there you have Resources in a nutshell. You will find that tools such as Borland's Resource Workshop and those included with IBM's Toolkit can save you from ever having to understand all that we have just discussed, as you can point and click and the work is done for you. However, it is very useful to know just how resources are created as it gives you a better understanding of just what you are doing. Now for those of you who have eagerly gone to consult your manuals (or the header files for that matter), you will have noticed that I left out a few resource types. I did this to save both time and space, and also because they are infrequently used. I won't dare say that it is left as an excercise for the reader, but if there is enough demand I will cover the remaining types in a later article. ═══ 5.2.6. What Next? ═══ What Next? Next we look at the Graphics Programming Interface (GPI). I would love to hear from you about what you thought, how useful it was, whether or not there was too much or not enough information, if it was too wordy or too simple - in short any and all comments are most welcome. Also, if there is something specific you would like to see covered, drop me a line too. ═══ 5.2.7. Bibliography ═══ Bibliography The following references were used in the preparation of this article: ■ OS/2 Version 2.0 - The Redbooks ■ OS/2 Version 2.0 Technical Library The following products were mentioned: ■ IBM OS/2 Toolkit Version 2.0 ■ Borland C++ for OS/2 Version 1.0 ═══ 6. Future Attractions ═══ Coming up in the future, we have: o Introduction to PM Part 4 o Writing Installable File Systems o Getting Started with IPF o And much more! ═══ 7. Contributors to this issue ═══ o Andre Asselin o Gavin R Baker o Steve Luzynski o Charles Oldham o Larry Salomon ═══ 7.1. Andre Asselin ═══ Andre Asselin recently graduated Cum Laude from Rensselaer Polytechnic Institute with a Bachelor of Science degree in Computer Science. He has worked with OS/2 since version 1.3, including several beta copies of version 2.0, and also has extensive experience with MS-DOS and Microsoft Windows. He has co-oped twice in IBM's TCP/IP Development Department in Raleigh NC, where his responsibilities included the NFS client and server components of TCP/IP for OS/2. In August, he will be rejoining the group full time. Andre is also a member of Alpha Sigma Phi Fraternity, and enjoys hockey, the beach, and a good science fiction novel. He can be reached via email at assela@rpi.edu or on CompuServe at 73765,673. ═══ 7.2. Gavin R Baker ═══ Gavin R Baker Gavin R Baker is the man behind ThinkSoft, a consulting firm based in Melbourne Australia which specialises in developing custom software. He is an active member of Team OS/2, and is the Editor for the Victorian Independent OS/2 Developer's Group (VioDevGrp). When he isn't programming, he is also a musician, an actor, and wastes lots of time reading Net News. He can be contacted thusly: net: demogrb@lust.latrobe.edu.au cis: 100026,270 ═══ 7.3. Steve Luzynski ═══ Steve Luzynski is the editor and creator of this magazine. He is currently a Computer Engineering student at University of Missouri at Columbia, after leaving Case Western Reserve University in Cleveland, OH, where it was just too cold. Steve has released several small programs for IBM's TCP/IP using Pat Mueller's rxSock library for REXX. He is currently working on the ultimate file manager, designed in conjuction with several hard-core DOS file manager users. It should see release as soon as Steve figures out how exactly to do all of this cool C++ 'object' stuff in OS/2 PM. Did you know you can't export a member function of a class? Steve can by reached via e-mail at: Internet - sal8@po.cwru.edu sluzynski@delphi.com 72677.2140@compuserve.com Steve.Luzynski@f304.n280.z1.fidonet.org Delphi - sluzynski Compuserve - 72677,2140 FidoNet - Steve Luzynski @ 1:280/304 (Multitasking Systems, K.C., MO) The old fashioned kind of mail can currently find Steve home for the summer at: Steve Luzynski 3604 S. 10th ST CT Blue Springs, MO 64015-6238 ═══ 7.4. Charles Oldham ═══ Charles R. (C. R.) Oldham resides in Mesa, Arizona with his wife Amy. He is currently working as the systems analyst for the North Central Association, a non-profit organization that accredits schools in the 20 state region roughly in the central United States. C. R. is an experienced Dos, Macintosh, and Unix programmer who is eagerly moving into the area of OS/2 applications development and is trying to take as many people with him as he can. C. R. can be reached via Internet email or snail mail at the following addresses: Internet Email: cro@socrates.ed.asu.edu U. S. Mail: C. R. Oldham North Central Association Arizona State University Tempe, AZ 85287-3011 ═══ 7.5. Larry Salomon ═══ Larry Salomon wrote his first Presentation Manager application for OS/2 version 1.1 in 1989. Since that time, he has written numerous VIO and PM applications, including the Scramble applet included with OS/2 and the I-Brow/Magnify/Screen Capture trio included with the IBM Professional Developers Kit CD-ROM currently being distributed by IBM. Currently, he works for International Masters Publishers in Stamford, Connecticut and resides in Bellerose, New York with his wife Lisa. ═══ ═══ EMX/GCC can be retrieved from the official OS/2 anonymous FTP site, ftp-os2. nmsu.edu, in the directory /pub/os2/2.x/unix/gnu/emx-0.8f. An installation guide for the system can be found in the first issue of EDM/2. ═══ ═══ DevHlps - A set of calls available to IFSs and device drivers to perform common functions, such as allocating memory. ═══ ═══ Doscalls - Any of the DosXxx calls available in OS/2, such as DosOpen, DosRead, etc. ═══ ═══ FSD (File System Driver) - A system extension that allows OS/2 to access files stored in different disk formats or across a network. Also called an IFS. ═══ ═══ FSH (File System Helpers) - A set of calls available to IFSs that perform common functions, such as allocating memory and managing semaphores. ═══ ═══ GDT (Global Descriptor Table) - The table of selectors that are valid across all processes. ═══ ═══ IFS (Installable File System) - A system extension that allows OS/2 to access files stored in different disk formats or across a network. Also called an FSD. ═══ ═══ IPE (Internal Processing Error) - An unrecoverable system halt caused by buggy ring 0 code. ═══ ═══ LDT (Local Descriptor Table) - The table of selectors that is allocated on a per process basis. Every process has its own LDT. A particular LDT selector is valid only in its own process. ═══ ═══ NFS (Network File System) - A protocol developed by Sun Microsystems to allow UNIX machines to share files across a network. ═══ ═══ UNC (Universal Naming Convention) - The '\\server\path\file' naming convention, where 'server' refers to the server the file is on. ═══ ═══ VMS - DEC's proprietary operating system for its VAX series of computers. One of the nice features it supports is version levels for files (sort of an automatic backup).