═══ 1. June 1994 Title Page ═══ Welcome to EDM/2 - The Electronic OS/2 Developer's Magazine! Portions copyright (c) by Larry Salomon Jr. Volume 2, issue 6 Copyright Notice and Other Stuff The Editor-in-Chief 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 This month has been another hectic one. After a couple of months, when releasing a new issue meant around the 10th or so, I decided to get this one out on time. You can see how well I did. At least one good thing has happened this past month: we now have cable here at our house, so my wife watches the Chinese channel (she's from the mainland) instead of griping about how much time I spend on the computer. Seriously, there is a lot of stuff in this issue. The Scratch Patch is teeming with goodies for you to snatch up, and there is part 2 of the sprites series as well as how to interpret and decompile resources from EXE and DLL files. Great stuff! Format Change In the quest to make EDM/2 easier to concatenate, Carsten suggested adding a header to the beginning of each article and column, to show the title of the article or column you are reading. This inspired me to also add a footer at the bottom of each section with the article/column/panel name, the date, and the volume and issue number. These have been done this issue; contact us to let us know what you think about them. While I'm talking about him, let me publicly acknowledge the work above-and-beyond the call of duty that Carsten has put into each issue. The quality, in my not-so-humble opinion, has gone up considerably since he started catching my mistakes and those of the authors. Thanks a lot for your hard work! Conferences PC Expo is just around the corner. If you plan to be in New York City, I'm interested in meeting you. Send me email. Speaking of which, become more than a Team OS/2 member; become more than an OS/2 Evangelist; become an EDM/2 Promoter. If you go to a SIG that either has OS/2 as its reason for existence, or is multi-platform oriented with development being the primary focus, let me know; for the IBM PSP Conference, I drew some fliers that I handed out throughout the week that I was there, and I can easily send you a PostScript version of the flyer so that you can tell everyone about EDM/2 at your next gathering. Title Page - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 2. Features for June 1994 ═══ The following articles constitute this issue's features: o Sprites and Animation - Part 2 o Resources and Decompiling Them o Visual REXX Faceoff Features - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 2.1. Sprites and Animation - Part 2 ═══ ═══ 2.1.1. Introduction ═══ Sprites and Animation - Part 2 Written by Larry Salomon, Jr. Introduction Last month, we took a good look at the beginnings of a sprite library that is well-suited for simple animation. We looked at: o how a sprite is drawn o how the drawing of sprites requires a mask, which adds an extra burden on the management of the sprite data structure by the library o how a sprite is moved in a manner that minimizes flicker But even discussing these things left some holes, namely the background and the workarea that enables the flicker-free movement. This month, we will wrap up our discussion on the design of the library and will begin delving into the code for the sprite library itself. Next month's conclusion will look into the intricacies of animating a set of sprites using the i495.exe sample as a starting point. Ch-Ch-Ch-Changes Ah, there's nothing like quintessential Bowie... Since last month, I have integrated the sprite library into the Common/2 library that was on hobbes (if this new version isn't there already, wait for a week and remind me if I still have forgotten). However, I have kept the original version and have modified it to provide semaphore exclusion to the data structures (actually, I surgically cut-n-pasted the related functions from Common/2 ), and it is this version that we will present in this series. It should be noted, though, that the version in Common/2 does come complete with an online reference. Sprites and Animation (Part 2) - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 2.1.2. Monkey Bars and the Like ═══ Monkey Bars and the Like A distinguishing characteristic between a sprite and a bitmap is that the former has a transparency color which means that whatever is below the sprite shows through it. If the background were simply a solid color, this could be done through sleight-of-hand by drawing the bitmap with the background color in the areas that are to be see-through. Movement would be quite simple, reduced to a few WinFillRect() calls and a single GpiWCBitBlt(). Unfortunately, a sprite can make no assumptions about the underlying surface: it could be a solid color or it could be a complex drawing or it even could be another sprite. Worse yet, we cannot simply take what is already on the screen, for what shall we restore there if - for example - the sprite has its visibility state set to FALSE? Because of this (and other reasons), we need to maintain yet another data structure which will, among other things, manage a static bitmap to be used for the background; we will call it a playground since it is the area in which we will let the sprites play. Conceptually, a playground is the "blackboard" while each sprite is just a chalk drawing on the blackboard, so all drawing related functions are considered to be associated with a playground instead of the sprites that occupy a playground. The playground support must provide the following functions: o Create a playground o Destroy a playground o Add a sprite to the playground o Remove a sprite from the playground o Draw the entire playground Adding and removing sprites from the playground is needed so that we can access data structures in the playground given the sprite handle. For example, only one work area is needed per playground since one thread at most can access an HPS, so we put the work area in the playground, but need to access this work area from each sprite. Drawing a playground involves drawing the background and then looping through its membership list to draw each sprite at its current position. This is intended to provide a simple method of processing the WM_PAINT message within an application. ──────────────────── If all of these design issues get confusing, remember that I have the power of hindsight and you do not, so do not get dismayed or (worse yet) think that I have some divine capability to think of these things before writing a line of code; I had the same trial-n-error development process that you would have were our places exchanged. Sprites and Animation (Part 2) - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 2.1.3. Final Design Considerations ═══ Final Design Considerations In even the most elementary computer programming classes, the concept of code reuse is hammered into the brains of the students; here, this concept is stressed as well. There are many code blocks that are needed in various places throughout the library, so these code blocks were turned into functions. Mutually exclusive access to the data structures is also an important part of any good library, and it is implemented here through the use of a mutex sempahore and a status flag; the status flag indicates if access has already been granted to a function, i.e. if a function calls another function, the called function will deadlock if it requests the semaphore. The access-granting function is accessSem(), and it would be worth the time to understand how this is implemented to avoid confusion later. Sprites and Animation (Part 2) - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 2.1.4. Where's Your Library Card? ═══ Where's Your Library Card? Since utilitizing the library involves far more complex coding than the code for the library itself (did I ever say that animation was easy?), we will look at the library itself. If you have not already done so, unzip the source.zip file and take a look at the files within. MAKEFILE the makefile for the library SPRITE.C the source file SPRITE.HPP the .H preprocessor file. This is fed to HEADER.EXE which generates an implementor's .H file and a user's .H file. You should change the first two lines of this file so that the paths make sense. //@HEADER OUTFILE INT D:\SOURCE\SPRITE\SPRITE.H //@HEADER OUTFILE EXT D:\MYINC\SPRITE.H The INT line defines the file name of the "internal" use .H file, while the EXT line defines the file name of the "external" use .H file. SPRITE.H Generated by the command "HEADER -tINT SPRITE.HPP" SPRITE.OBJ Object file SPRITE.LIB Library file HEADER.EXE Header file preprocessor. I think I have placed this on hobbes, possibly under the name HPREP. If not, this is an older version, but it will do. (I keep forgetting to bring the newer version in to work.) Before we can begin to understand the code, we must first understand the data structures on which the code operates, so we first look at the playground: typedef struct _PLAYGROUND { ULONG ulSig; ULONG ulStatus; HMTX hsmAccess; HAB habAnchor; HDC hdcWork; HPS hpsWork; HBITMAP hbmWork; HBITMAP hbmBack; BITMAPINFOHEADER2 bmihBack; LONG lBackColor; BOOL bUpdate; HSPRITE ahsSprites[MAX_SPRITES]; ULONG ulNumMembers; } PLAYGROUND, *HPLAYGROUND; typedef HPLAYGROUND *PHPLAYGROUND; ulSig 4-byte signature for the data structure, used for parameter validation. ulStatus 32-bit status flags used for semaphore access at this time. hsmAccess mutex (mutual exclusion) semaphore handle. habAnchor anchor block handle of the calling thread. hdcWork OD_MEMORY device context handle for the work area. hpsWork presentation space handle associated with hdcWork. hbmWork bitmap handle set into hpsWork. hbmBack bitmap handle for the background bitmap. If NULLHANDLE, the playground has a background color instead (lBackColor). bmihBack bitmap information header for hbmBack. If hbmBack is NULLHANDLE, the cx and cy fields indicate the size of the playground; otherwise the size of the playground is specified by the size of the background bitmap. lBackColor the background color of the playground, if hbmBack is NULLHANDLE. bUpdate update flag. If FALSE, no drawing is actually performed. This is useful for changing sprites in place. ahsSprites array of sprite handles comprising the membership list. ulNumMembers current number of members. We will see how these fields are used when we look at the code. But now, we will look at the sprite structure: typedef struct _SPRITE { ULONG ulSig; ULONG ulStatus; HMTX hsmAccess; HAB habAnchor; HBITMAP hbmBitmap; HBITMAP hbmMask; BITMAPINFOHEADER2 bmihBitmap; BITMAPINFOHEADER2 bmihMask; struct _PLAYGROUND *hpgPlay; POINTL ptlPos; BOOL bVisible; } SPRITE, *HSPRITE; typedef HSPRITE *PHSPRITE; ulSig 4-byte signature for the data structure, used for parameter validation. ulStatus 32-bit status flags used for semaphore access at this time. hsmAccess mutex (mutual exclusion) semaphore handle. habAnchor anchor block handle of the calling thread. hbmBitmap bitmap handle which defines the sprite. hbmMask bitmap handle which defines the mask. bmihBitmap bitmap information header for hbmBitmap. bmihMask bitmap information header for hbmMask. hpgPlay playground handle of which the sprite is a member. ptlPos current position. bVisible current visibility state. A comment on the list of exposed functions below: notice the symmetry of the function names. For each create, there is a destroy; querys have corresponding sets when appropriate; the add has a remove. While much is often said about intuitiveness of the user-interface, the same concepts along with the advantages gained can be applied to the "programmer-interface". The non-exposed (internal) functions, obviously, do not need to follow this guideline, although it does help if more than one person is developing and/or maintaining the code. Each function below is a hypertext link to its code and the explanation of the code; feel free to explore the functions in any order. Internal Functions o accessSem o clipBltPoints o drawSpriteAt o drawBackAt o getMemHps o queryHandleType External Functions o SprAddSprite o SprCreatePlayground o SprCreateSprite o SprDestroyPlayground o SprDestroySprite o SprDrawPlayground o SprDrawSprite o SprQueryPlaygroundBack o SprQueryPlaygroundColor o SprQueryPlaygroundSize o SprQuerySpritePosition o SprQuerySpriteRect o SprQuerySpriteSize o SprQuerySpriteVisibility o SprQueryUpdateFlag o SprRemoveSprite o SprSetPlaygroundBack o SprSetPlaygroundColor o SprSetPlaygroundSize o SprSetSpritePosition o SprSetSpriteVisibility o SprSetUpdateFlag Sprites and Animation (Part 2) - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 2.1.5. Summary ═══ Summary This month, we finished our discussion of the design of the sprite library by describing the need for a master data structure called the playground, and the code to implement the library routines was presented to illustrate the concepts that we have already learned in our discussions up to this point. Next month, we will take these underpinings and will apply them to the i495.exe application to see how they can be utililized to perform rudimentary animation. All comments, suggestions, bugs, etc. are welcome c/o the author. Sprites and Animation (Part 2) - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ queryHandleType ═══ queryHandleType The purpose of this function is parameter validation, and uses the ulSig field of both data structures. Note that for non-accessible memory pointers, this function will still cause the application to trap (although it is possible to check for NULL). static ULONG queryHandleType(PVOID pvHandle) //------------------------------------------------------------------------- // This function returns a QH_* constant specifying the handle type. It // will be replaced by CmnQueryHandle() when this subsystem is integrated // into Common/2. // // Input: pvHandle - points the the handle to query // Returns: QH_ERROR if error, QH_* constant otherwise //------------------------------------------------------------------------- { if (pvHandle==NULL) { return QH_ERROR; } /* endif */ switch (((PHEADER)pvHandle)->ulSig) { case SIG_HSPRITE: return QH_HSPRITE; case SIG_HPLAYGROUND: return QH_HPLAYGROUND; default: return QH_ERROR; } /* endswitch */ } Sprites and Animation (Part 2) - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ accessSem ═══ accessSem The purpose of this function is to provide mutually exclusive access to the data structures used by the library. By requiring that all data structures have a common set of fields defined first (semaphore handle, etc.), we can cast the data structures to the type PHANDLE to allow us access to the common fields. static USHORT accessSem(PHEADER phHandle,USHORT usAction) //------------------------------------------------------------------------- // This function provides semaphore access for mutual exclusion of private // data access. // // Input: phHandle - points to the handle header // usAction - specifies the action to perform: // ACCSEM_SET - requests access to the handle // ACCSEM_CLEAR - relinquishes access to the handle // ACCSEM_ALREADYSET - not used // ACCSEM_NOTSET - not used // Returns: ACCSEM_ERROR if an error occurred, else the action to take // on the next call to this function //------------------------------------------------------------------------- { switch (usAction) { case ACCSEM_SET: if ((phHandle->ulStatus & HSTATUS_INLIBRARY)!=0) { return ACCSEM_ALREADYSET; } /* endif */ DosRequestMutexSem(phHandle->hsmAccess,SEM_INDEFINITE_WAIT); phHandle->ulStatus|=HSTATUS_INLIBRARY; return ACCSEM_CLEAR; case ACCSEM_CLEAR: if ((phHandle->ulStatus & HSTATUS_INLIBRARY)==0) { return ACCSEM_NOTSET; } /* endif */ DosReleaseMutexSem(phHandle->hsmAccess); phHandle->ulStatus&=~HSTATUS_INLIBRARY; return ACCSEM_SET; case ACCSEM_ALREADYSET: return ACCSEM_NOTSET; case ACCSEM_NOTSET: return ACCSEM_ALREADYSET; default: return ACCSEM_ERROR; } /* endswitch */ } Sprites and Animation (Part 2) - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ getMemHps ═══ getMemHps There is nothing special about this function that needs to be noted. static HPS getMemHps(HAB habAnchor) //------------------------------------------------------------------------- // This function creates an HPS associated with a memory HDC. The // HDC handle can be retrieved using the GpiQueryDevice() function. // // Input: habAnchor - anchor block of the calling thread. // Returns: HPS handle if successful, NULLHANDLE otherwise //------------------------------------------------------------------------- { HDC hdcMem; SIZEL szlHps; HPS hpsMem; hdcMem=DevOpenDC(habAnchor,OD_MEMORY,"*",0,NULL,NULLHANDLE); if (hdcMem==NULLHANDLE) { return NULLHANDLE; } /* endif */ szlHps.cx=0; szlHps.cy=0; hpsMem=GpiCreatePS(habAnchor, hdcMem, &szlHps, PU_PELS|GPIT_MICRO|GPIA_ASSOC); if (hpsMem==NULLHANDLE) { DevCloseDC(hdcMem); } /* endif */ return hpsMem; } Sprites and Animation (Part 2) - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ clipBltPoints ═══ clipBltPoints The library clips all drawing to the playground size. (A future enhancement to the library would be to allow the application to specify where in a window the lower-left corner of the playground should be.) This function clips a pair of rectangles (which are defined by a pair of points, meaning that we have four points total) to a rectangle defined to be (0,0)-(pszlPlay->cx,pszlPlay->cy). You can see the clipping effect by enlarging the size of the i495 window. static BOOL clipBltPoints(HAB habAnchor,PPOINTL pptlArray,PSIZEL pszlPlay) //------------------------------------------------------------------------- // This function clips the first two points in pptlArray to a rectangle // of size pszlPlay. The last two points in pptlArray are then adjusted // by the amount clipped. // // It is assumed that the first two points refer to a coordinate space // of size pszlPlay and that the two rectangles formed by the first and // last pair of points in pptlArray are of the same size. // // Input: habAnchor - anchor block of the calling thread. // pptlArray - points to array of 4 points for GpiBitBlt() // pszlPlay - points to the size of the playground to clip to // Output: pptlArray - points to adjusted array // Returns: TRUE if at least one pel was *not* clipped, FALSE if all // points fell outside of the clipping region. //------------------------------------------------------------------------- { RECTL rclPlay; RECTL rclDest; RECTL rclInter; RECTL rclDelta; rclPlay.xLeft=0; rclPlay.yBottom=0; rclPlay.xRight=pszlPlay->cx-1; rclPlay.yTop=pszlPlay->cy-1; rclDest.xLeft=pptlArray[0].x; rclDest.yBottom=pptlArray[0].y; rclDest.xRight=pptlArray[1].x; rclDest.yTop=pptlArray[1].y; WinIntersectRect(habAnchor,&rclInter,&rclPlay,&rclDest); //---------------------------------------------------------------------- // If the result is an empty rectangle, return FALSE to indicate so. //---------------------------------------------------------------------- if (WinIsRectEmpty(habAnchor,&rclInter)) { return FALSE; } /* endif */ rclDelta.xLeft=rclDest.xLeft-rclInter.xLeft; rclDelta.yBottom=rclDest.yBottom-rclInter.yBottom; rclDelta.xRight=rclDest.xRight-rclInter.xRight; rclDelta.yTop=rclDest.yTop-rclInter.yTop; pptlArray[0].x-=rclDelta.xLeft; pptlArray[0].y-=rclDelta.yBottom; pptlArray[1].x-=rclDelta.xRight; pptlArray[1].y-=rclDelta.yTop; pptlArray[2].x-=rclDelta.xLeft; pptlArray[2].y-=rclDelta.yBottom; pptlArray[3].x-=rclDelta.xRight; pptlArray[3].y-=rclDelta.yTop; return TRUE; } Sprites and Animation (Part 2) - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ drawSpriteAt ═══ drawSpriteAt This function draws a sprite at the specified position; it calls clipBltPoints() to insure that all necessary clipping is performed. An advantage of having this as a function is that the transition to z-order implementation is eased somewhat. It is because of this that the background is not drawn in this function, but is removed to its own function - drawBackAt(). static BOOL drawSpriteAt(HPS hpsDraw, HSPRITE hsSprite, PSIZEL pszlSize, PPOINTL pptlPos) //------------------------------------------------------------------------- // This function draws the sprite at the specified position. It is assumed // that the background has already been drawn into hpsDraw before this // function is called. // // Input: hpsDraw - handle of the presentation space to draw in // hsSprite - handle of the sprite to draw // pszlSize - points to the size of hpsDraw. If NULL, the size // of the playground is used. // pptlPos - points to the point specifying the position. If // NULL, the sprite's current position is used. // Returns: TRUE if successful, FALSE otherwise //------------------------------------------------------------------------- { POINTL ptlUse; SIZEL szlUse; POINTL aptlPoints[4]; if (!hsSprite->hpgPlay->bUpdate) { return TRUE; } /* endif */ //---------------------------------------------------------------------- // Initialize the local variables with either what was passed in or // the defaults as noted above in the function prologue //---------------------------------------------------------------------- if (pptlPos==NULL) { ptlUse=hsSprite->ptlPos; } else { ptlUse=*pptlPos; } /* endif */ if (pszlSize==NULL) { SprQueryPlaygroundSize(hsSprite->hpgPlay,&szlUse); } else { szlUse=*pszlSize; } /* endif */ aptlPoints[0].x=ptlUse.x; aptlPoints[0].y=ptlUse.y; aptlPoints[1].x=aptlPoints[0].x+hsSprite->bmihMask.cx-1; aptlPoints[1].y=aptlPoints[0].y+hsSprite->bmihMask.cy-1; aptlPoints[2].x=0; aptlPoints[2].y=0; aptlPoints[3].x=aptlPoints[2].x+hsSprite->bmihMask.cx; aptlPoints[3].y=aptlPoints[2].y+hsSprite->bmihMask.cy; if (clipBltPoints(hsSprite->habAnchor,aptlPoints,&szlUse)) { //------------------------------------------------------------------- // Blit the mask and then the bitmap //------------------------------------------------------------------- GpiWCBitBlt(hpsDraw, hsSprite->hbmMask, 4, aptlPoints, ROP_SRCAND, BBO_IGNORE); GpiWCBitBlt(hpsDraw, hsSprite->hbmBitmap, 4, aptlPoints, ROP_SRCPAINT, BBO_IGNORE); } /* endif */ return TRUE; } Sprites and Animation (Part 2) - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ drawBackAt ═══ drawBackAt This function draws the background at the specified position with the specified size. This is used to copy the background onto the display as well as into the work area as needed. Notice that this is where background coloring is done if a background bitmap was not specified. static BOOL drawBackAt(HPS hpsDraw, HPLAYGROUND hpgPlay, PRECTL prclDest, PSIZEL pszlDest, PRECTL prclSrc) //------------------------------------------------------------------------- // This function draws the background in the specified presentation space. // // Input: hpsDraw - handle of the presentation space to draw in // hpgPlay - handle of the playground containing the background // prclDest - points to the destination rectangle. If NULL, the // value of prclSrc is used. // pszlDest - points to the size of hpsDraw. If NULL, the size of // the playground is used. // prclSrc - points to the source rectangle. If NULL, the entire // background is painted. // Returns: TRUE if successful, FALSE otherwise //------------------------------------------------------------------------- { RECTL rclUseSrc; RECTL rclUseDest; SIZEL szlUse; POINTL aptlPoints[4]; if (!hpgPlay->bUpdate) { return TRUE; } /* endif */ if (prclSrc==NULL) { rclUseSrc.xLeft=0; rclUseSrc.yBottom=0; rclUseSrc.xRight=hpgPlay->bmihBack.cx; rclUseSrc.yTop=hpgPlay->bmihBack.cy; } else { rclUseSrc=*prclSrc; } /* endif */ if (prclDest==NULL) { rclUseDest=rclUseSrc; rclUseDest.xRight--; rclUseDest.yTop--; } else { rclUseDest=*prclDest; } /* endif */ if (pszlDest==NULL) { szlUse.cx=hpgPlay->bmihBack.cx; szlUse.cy=hpgPlay->bmihBack.cy; } else { szlUse=*pszlDest; } /* endif */ aptlPoints[0].x=rclUseDest.xLeft; aptlPoints[0].y=rclUseDest.yBottom; aptlPoints[1].x=rclUseDest.xRight; aptlPoints[1].y=rclUseDest.yTop; aptlPoints[2].x=rclUseSrc.xLeft; aptlPoints[2].y=rclUseSrc.yBottom; aptlPoints[3].x=rclUseSrc.xRight; aptlPoints[3].y=rclUseSrc.yTop; if (clipBltPoints(hpgPlay->habAnchor,aptlPoints,&szlUse)) { //------------------------------------------------------------------- // If there is a background bitmap, blit it, otherwise black out the // area. //------------------------------------------------------------------- if (hpgPlay->hbmBack!=NULLHANDLE) { GpiWCBitBlt(hpsDraw, hpgPlay->hbmBack, 4, aptlPoints, ROP_SRCCOPY, BBO_IGNORE); } else { //---------------------------------------------------------------- // WinFillRect() excludes the top and right of the rectangle //---------------------------------------------------------------- rclUseDest.xRight++; rclUseDest.yTop++; WinFillRect(hpsDraw,&rclUseDest,hpgPlay->lBackColor); } /* endif */ } /* endif */ return TRUE; } Sprites and Animation (Part 2) - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ SprAddSprite ═══ SprAddSprite This function adds a sprite handle to the end of the ahsSprites array within the HPLAYGROUND structure. By insuring - in SprRemoveSprite() - that the array is always compact, we save a test for NULL in the SprDrawPlayground() loop. Note that there are no functions for modifying the order of the sprite within the array, because as of now there is no need. However, once z-ordering is added to the library, another function - SprSetLayer() - will be added to set the sprite's position to the top, bottom, previous, or next position in the z-order stack. SPRERROR EXPENTRY SprAddSprite(HPLAYGROUND hpgPlay,HSPRITE hsSprite) //------------------------------------------------------------------------- // This function labels a sprite as a "member" of the specified playground. // Doing so allows the application to control the sprite's position, // visibility, etc. on a drawing surface. // // Input: hpgPlay - handle to the playground // hsSprite - handle to the sprite to add // Returns: SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise //------------------------------------------------------------------------- { USHORT usAction; if (queryHandleType(hpgPlay)!=QH_HPLAYGROUND) { return SPR_ERR_BADHANDLE; } else if (queryHandleType(hsSprite)!=QH_HSPRITE) { return SPR_ERR_BADHANDLE; } /* endif */ usAction=accessSem((PHEADER)hpgPlay,ACCSEM_SET); if (hsSprite->hpgPlay!=NULL) { accessSem((PHEADER)hpgPlay,usAction); return SPR_ERR_HASPLAYGROUND; } else if (hpgPlay->ulNumMembers==MAX_SPRITES) { accessSem((PHEADER)hpgPlay,usAction); return SPR_ERR_PLAYGROUNDFULL; } /* endif */ hpgPlay->ahsSprites[hpgPlay->ulNumMembers]=hsSprite; hpgPlay->ulNumMembers++; hsSprite->hpgPlay=hpgPlay; accessSem((PHEADER)hpgPlay,usAction); return SPR_ERR_NOERROR; } Sprites and Animation (Part 2) - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ SprCreatePlayground ═══ SprCreatePlayground Note the use of calloc(). Using any of the C runtime is not a good thing because it is implemented (usually) using suballocation, meaning an application can accidentally increment hpgPlay by 2 bytes (for example) and would not get a trap notification by OS/2; instead unpredictable behavior would occur, which misleads the developer into thinking a stack error is occuring. It would be better to use DosAllocMem() instead. SPRERROR EXPENTRY SprCreatePlayground(HAB habAnchor,PHPLAYGROUND phpgPlay) //------------------------------------------------------------------------- // This function creates a playground to which sprites can be added. // // Input: habAnchor - anchor block of the calling thread. // Output: phpgPlay - points to the variable with the HPLAYGROUND handle // Returns: SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise //------------------------------------------------------------------------- { BITMAPINFOHEADER2 bmihInfo; LONG lValue; *phpgPlay=calloc(1,sizeof(PLAYGROUND)); if (*phpgPlay==NULL) { *phpgPlay=NULL; return SPR_ERR_NOMEMORY; } /* endif */ (*phpgPlay)->ulSig=SIG_HPLAYGROUND; (*phpgPlay)->ulStatus=0; if (DosCreateMutexSem(NULL,&(*phpgPlay)->hsmAccess,0,FALSE)) { free(*phpgPlay); return SPR_ERR_RESOURCE; } /* endif */ (*phpgPlay)->habAnchor=habAnchor; (*phpgPlay)->hpsWork=getMemHps(habAnchor); if ((*phpgPlay)->hpsWork==NULLHANDLE) { free(*phpgPlay); *phpgPlay=NULL; return SPR_ERR_RESOURCE; } /* endif */ (*phpgPlay)->hdcWork=GpiQueryDevice((*phpgPlay)->hpsWork); Since the workarea is used for moving sprites with position overlap, the size of the bitmap to be set in the HPS is (MAX_SPRITE_CX*2,MAX_SPRITE_CY*2). bmihInfo.cbFix=16; bmihInfo.cx=MAX_SPRITE_CX*2; bmihInfo.cy=MAX_SPRITE_CY*2; bmihInfo.cPlanes=1; DevQueryCaps((*phpgPlay)->hdcWork,CAPS_COLOR_BITCOUNT,1,&lValue); bmihInfo.cBitCount=lValue; (*phpgPlay)->hbmWork=GpiCreateBitmap((*phpgPlay)->hpsWork, &bmihInfo, 0, NULL, NULL); if ((*phpgPlay)->hbmWork==NULLHANDLE) { GpiDestroyPS((*phpgPlay)->hpsWork); DevCloseDC((*phpgPlay)->hdcWork); free(*phpgPlay); *phpgPlay=NULL; return SPR_ERR_RESOURCE; } /* endif */ GpiSetBitmap((*phpgPlay)->hpsWork,(*phpgPlay)->hbmWork); (*phpgPlay)->lBackColor=CLR_BLACK; (*phpgPlay)->bUpdate=TRUE; (*phpgPlay)->hbmBack=NULLHANDLE; (*phpgPlay)->ulNumMembers=0; return SPR_ERR_NOERROR; } Sprites and Animation (Part 2) - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ SprCreateSprite ═══ SprCreateSprite See SprCreatePlayground() for notes on using calloc(). SPRERROR EXPENTRY SprCreateSprite(HAB habAnchor, HBITMAP hbmBitmap, PHSPRITE phsSprite) //------------------------------------------------------------------------- // This function creates a sprite from the specified bitmap. The sprite // cannot be moved, shown, etc., however, until it is associated with a // playground. // // The color black is used as the transparency color. If you need to use // black in the bitmap without it becoming transparent, use the next // closest color. // // New sprites are initialized as being at position (0,0) and hidden. // // Note that, once this function is called, the bitmap is managed by // the sprite subsystem. The bitmap should *NOT* be deleted by the // application or else unpredictable results will occur. // // Input: habAnchor - anchor block of the calling thread. // hbmBitmap - handle to the bitmap // Output: phsSprite - points to the sprite handle // Returns: SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise //------------------------------------------------------------------------- { HPS hpsMem; HDC hdcMem; POINTL aptlPoints[4]; *phsSprite=calloc(1,sizeof(SPRITE)); if (*phsSprite==NULL) { *phsSprite=NULL; return SPR_ERR_NOMEMORY; } /* endif */ (*phsSprite)->ulSig=SIG_HSPRITE; (*phsSprite)->ulStatus=0; if (DosCreateMutexSem(NULL,&(*phsSprite)->hsmAccess,0,FALSE)) { free(*phsSprite); return SPR_ERR_RESOURCE; } /* endif */ (*phsSprite)->habAnchor=habAnchor; (*phsSprite)->hbmBitmap=hbmBitmap; (*phsSprite)->ptlPos.x=0; (*phsSprite)->ptlPos.y=0; (*phsSprite)->bVisible=FALSE; (*phsSprite)->bmihBitmap.cbFix=16; GpiQueryBitmapInfoHeader((*phsSprite)->hbmBitmap,&(*phsSprite)->bmihBitmap); //---------------------------------------------------------------------- // Get an OD_MEMORY HDC and HPS to create the mask in. Since we will // save the bitmap handle, but don't give a $%#@ about the HDC/HPS, they // can be local variables. //---------------------------------------------------------------------- hpsMem=getMemHps(habAnchor); if (hpsMem==NULLHANDLE) { free(*phsSprite); *phsSprite=NULL; return SPR_ERR_RESOURCE; } /* endif */ hdcMem=GpiQueryDevice(hpsMem); (*phsSprite)->bmihMask=(*phsSprite)->bmihBitmap; (*phsSprite)->bmihMask.cPlanes=1; (*phsSprite)->bmihMask.cBitCount=1; (*phsSprite)->hbmMask=GpiCreateBitmap(hpsMem, &(*phsSprite)->bmihMask, 0, NULL, NULL); if ((*phsSprite)->hbmMask==NULLHANDLE) { GpiDestroyPS(hpsMem); DevCloseDC(hdcMem); free(*phsSprite); *phsSprite=NULL; return SPR_ERR_RESOURCE; } /* endif */ GpiSetBitmap(hpsMem,(*phsSprite)->hbmMask); aptlPoints[0].x=0; aptlPoints[0].y=0; aptlPoints[1].x=aptlPoints[0].x+(*phsSprite)->bmihMask.cx-1; aptlPoints[1].y=aptlPoints[0].y+(*phsSprite)->bmihMask.cy-1; aptlPoints[2].x=0; aptlPoints[2].y=0; aptlPoints[3].x=aptlPoints[2].x+(*phsSprite)->bmihBitmap.cx; aptlPoints[3].y=aptlPoints[2].y+(*phsSprite)->bmihBitmap.cy; //---------------------------------------------------------------------- // Set the foreground to white and the background to black so that this // works. The resulting behavior in the GpiWCBitBlt() call is // inconsistent with the docs, so I don't know what to think. //---------------------------------------------------------------------- GpiSetColor(hpsMem,CLR_WHITE); GpiSetBackColor(hpsMem,CLR_BLACK); GpiWCBitBlt(hpsMem, (*phsSprite)->hbmBitmap, 4, aptlPoints, ROP_SRCCOPY, BBO_IGNORE); GpiSetBitmap(hpsMem,NULLHANDLE); GpiDestroyPS(hpsMem); DevCloseDC(hdcMem); return SPR_ERR_NOERROR; } Sprites and Animation (Part 2) - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ SprDestroyPlayground ═══ SprDestroyPlayground This function destroys the sprites in the playground before destroying the playground itself. However, if you look in SprDestroySprite() you'll see a check to see if the sprite is still a member of a playground (and fails if it is); thus, we have to call SprRemoveSprite() first. SPRERROR EXPENTRY SprDestroyPlayground(HPLAYGROUND hpgPlay) //------------------------------------------------------------------------- // This function destroys the playground including any sprites that are // still members of it. All resources consumed by the playground, // including the back bitmap, are returned to the system. // // Input: hpgPlay - handle to the playground // Returns: SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise //------------------------------------------------------------------------- { USHORT usAction; ULONG ulIndex; HSPRITE hsSprite; if (queryHandleType(hpgPlay)!=QH_HPLAYGROUND) { return SPR_ERR_BADHANDLE; } /* endif */ usAction=accessSem((PHEADER)hpgPlay,ACCSEM_SET); if (hpgPlay->hbmBack!=NULLHANDLE) { GpiDeleteBitmap(hpgPlay->hbmBack); } /* endif */ for (ulIndex=0; ulIndexulNumMembers; ulIndex++) { hsSprite=hpgPlay->ahsSprites[ulIndex]; SprRemoveSprite(hpgPlay,hsSprite); SprDestroySprite(hsSprite); } /* endfor */ GpiSetBitmap(hpgPlay->hpsWork,NULLHANDLE); if (hpgPlay->hbmBack!=NULLHANDLE) { GpiDeleteBitmap(hpgPlay->hbmBack); } /* endif */ GpiDestroyPS(hpgPlay->hpsWork); DevCloseDC(hpgPlay->hdcWork); accessSem((PHEADER)hpgPlay,usAction); free(hpgPlay); return SPR_ERR_NOERROR; } Sprites and Animation (Part 2) - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ SprDestroySprite ═══ SprDestroySprite There is nothing special about this function that needs to be noted. SPRERROR EXPENTRY SprDestroySprite(HSPRITE hsSprite) //------------------------------------------------------------------------- // This function destroys the sprite and returns all resources to the // system. // // Input: hsSprite - handle to the sprite // Returns: SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise //------------------------------------------------------------------------- { USHORT usAction; if (queryHandleType(hsSprite)!=QH_HSPRITE) { return SPR_ERR_BADHANDLE; } /* endif */ usAction=accessSem((PHEADER)hsSprite,ACCSEM_SET); if (hsSprite->hpgPlay!=NULL) { accessSem((PHEADER)hsSprite,usAction); return SPR_ERR_HASPLAYGROUND; } /* endif */ GpiDeleteBitmap(hsSprite->hbmBitmap); GpiDeleteBitmap(hsSprite->hbmMask); accessSem((PHEADER)hsSprite,usAction); free(hsSprite); return SPR_ERR_NOERROR; } Sprites and Animation (Part 2) - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ SprDrawPlayground ═══ SprDrawPlayground See SprAddSprite() for notes about ahsSprites being a compact array for performance reasons. SPRERROR EXPENTRY SprDrawPlayground(HPS hpsDraw,HPLAYGROUND hpgPlay) //------------------------------------------------------------------------- // This function redraws the playground and all sprites belonging to the // playground. // // Input: hpsDraw - handle to the HPS to draw the playground in // hpgPlay - handle to the playground // Returns: SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise //------------------------------------------------------------------------- { USHORT usAction; ULONG ulIndex; if (queryHandleType(hpgPlay)!=QH_HPLAYGROUND) { return SPR_ERR_BADHANDLE; } /* endif */ usAction=accessSem((PHEADER)hpgPlay,ACCSEM_SET); if (hpgPlay->bUpdate) { drawBackAt(hpsDraw,hpgPlay,NULL,NULL,NULL); for (ulIndex=0; ulIndexulNumMembers; ulIndex++) { SprDrawSprite(hpsDraw,hpgPlay->ahsSprites[ulIndex]); } /* endfor */ } /* endif */ accessSem((PHEADER)hpgPlay,usAction); return SPR_ERR_NOERROR; } Sprites and Animation (Part 2) - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ SprDrawSprite ═══ SprDrawSprite Given the drawSpriteAt() and drawBackAt() functions, the implementation of this function is fairly trivial. SPRERROR EXPENTRY SprDrawSprite(HPS hpsDraw,HSPRITE hsSprite) //------------------------------------------------------------------------- // This function draws a sprite // // Input: hpsDraw - handle to the HPS to draw the sprite in // hsSprite - handle to the sprite // Returns: SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise //------------------------------------------------------------------------- { USHORT usAction; RECTL rclSprite; if (queryHandleType(hsSprite)!=QH_HSPRITE) { return SPR_ERR_BADHANDLE; } /* endif */ usAction=accessSem((PHEADER)hsSprite,ACCSEM_SET); if (hsSprite->hpgPlay==NULL) { accessSem((PHEADER)hsSprite,usAction); return SPR_ERR_HASNOPLAYGROUND; } /* endif */ if ((!hsSprite->bVisible) || (!hsSprite->hpgPlay->bUpdate)) { accessSem((PHEADER)hsSprite,usAction); return SPR_ERR_NOERROR; } /* endif */ rclSprite.xLeft=hsSprite->ptlPos.x; rclSprite.yBottom=hsSprite->ptlPos.y; rclSprite.xRight=rclSprite.xLeft+hsSprite->bmihMask.cx; rclSprite.yTop=rclSprite.yBottom+hsSprite->bmihMask.cy; drawBackAt(hpsDraw,hsSprite->hpgPlay,NULL,NULL,&rclSprite); drawSpriteAt(hpsDraw,hsSprite,NULL,NULL); accessSem((PHEADER)hsSprite,usAction); return SPR_ERR_NOERROR; } Sprites and Animation (Part 2) - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ SprQueryPlaygroundBack ═══ SprQueryPlaygroundBack There is nothing special about this function that needs to be noted. SPRERROR EXPENTRY SprQueryPlaygroundBack(HPLAYGROUND hpgPlay, HBITMAP *phbmBack) //------------------------------------------------------------------------- // This function returns the handle of the background bitmap currently in // use. // // Input: hpgPlay - handle to the playground // Output: phbmBack - points to the handle to the background bitmap. // Returns: SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise //------------------------------------------------------------------------- { USHORT usAction; if (queryHandleType(hpgPlay)!=QH_HPLAYGROUND) { return SPR_ERR_BADHANDLE; } /* endif */ usAction=accessSem((PHEADER)hpgPlay,ACCSEM_SET); *phbmBack=hpgPlay->hbmBack; accessSem((PHEADER)hpgPlay,usAction); return SPR_ERR_NOERROR; } Sprites and Animation (Part 2) - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ SprQueryPlaygroundColor ═══ SprQueryPlaygroundColor There is nothing special about this function that needs to be noted. SPRERROR EXPENTRY SprQueryPlaygroundColor(HPLAYGROUND hpgPlay, PLONG plBackColor) //------------------------------------------------------------------------- // This function returns the background color of the playground and is // only valid if the playground doesn't have a bitmap. // // Input: hpgPlay - handle to the playground // plBackColor - points to the variable to receive the background // color // Returns: SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise //------------------------------------------------------------------------- { USHORT usAction; if (queryHandleType(hpgPlay)!=QH_HPLAYGROUND) { return SPR_ERR_BADHANDLE; } /* endif */ usAction=accessSem((PHEADER)hpgPlay,ACCSEM_SET); if (hpgPlay->hbmBack!=NULLHANDLE) { accessSem((PHEADER)hpgPlay,usAction); return SPR_ERR_HASBACKGROUND; } /* endif */ *plBackColor=hpgPlay->lBackColor; accessSem((PHEADER)hpgPlay,usAction); return SPR_ERR_NOERROR; } Sprites and Animation (Part 2) - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ SprQueryPlaygroundSize ═══ SprQueryPlaygroundSize There is nothing special about this function that needs to be noted. SPRERROR EXPENTRY SprQueryPlaygroundSize(HPLAYGROUND hpgPlay,PSIZEL pszlSize) //------------------------------------------------------------------------- // This function returns the size of the playground. For playgrounds with // bitmaps set as the background, the returned value is the size of the // bitmap. Otherwise, the returned value is that which was specified on // the last call to SprSetPlaygroundSize(). // // Input: hpgPlay - handle to the playground // pszlSize - points to the variable to receive the size of the // playground // Returns: SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise //------------------------------------------------------------------------- { USHORT usAction; if (queryHandleType(hpgPlay)!=QH_HPLAYGROUND) { return SPR_ERR_BADHANDLE; } /* endif */ usAction=accessSem((PHEADER)hpgPlay,ACCSEM_SET); pszlSize->cx=hpgPlay->bmihBack.cx; pszlSize->cy=hpgPlay->bmihBack.cy; accessSem((PHEADER)hpgPlay,usAction); return SPR_ERR_NOERROR; } Sprites and Animation (Part 2) - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ SprQuerySpritePosition ═══ SprQuerySpritePosition There is nothing special about this function that needs to be noted. SPRERROR EXPENTRY SprQuerySpritePosition(HSPRITE hsSprite,PPOINTL pptlPos) //------------------------------------------------------------------------- // This function returns the current position of the sprite. Note that // a sprite has a current position even if it is hidden. // // Input: hsSprite - handle to the sprite // Output: pptlPos - points to the current position // Returns: SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise //------------------------------------------------------------------------- { USHORT usAction; if (queryHandleType(hsSprite)!=QH_HSPRITE) { return SPR_ERR_BADHANDLE; } /* endif */ usAction=accessSem((PHEADER)hsSprite,ACCSEM_SET); if (hsSprite->hpgPlay==NULL) { accessSem((PHEADER)hsSprite,usAction); return SPR_ERR_HASNOPLAYGROUND; } /* endif */ *pptlPos=hsSprite->ptlPos; accessSem((PHEADER)hsSprite,usAction); return SPR_ERR_NOERROR; } Sprites and Animation (Part 2) - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ SprQuerySpriteRect ═══ SprQuerySpriteRect Since the bounding rectangle of a sprite varies with its position, and since a sprite cannot have a position until it is a member of a playground, this function checks for membership and fails if there is none. Note that the rectangle returned is all-inclusive, i.e. the upper and right edge of the rectangle is part of the sprite. SPRERROR EXPENTRY SprQuerySpriteRect(HSPRITE hsSprite,PRECTL prclRect) //------------------------------------------------------------------------- // This function returns the bounding rectangle of the sprite at its // current position. // // Input: hsSprite - handle to the sprite // Output: prclRect - points to the current bounding rectangle // Returns: SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise //------------------------------------------------------------------------- { USHORT usAction; if (queryHandleType(hsSprite)!=QH_HSPRITE) { return SPR_ERR_BADHANDLE; } /* endif */ usAction=accessSem((PHEADER)hsSprite,ACCSEM_SET); if (hsSprite->hpgPlay==NULL) { accessSem((PHEADER)hsSprite,usAction); return SPR_ERR_HASNOPLAYGROUND; } /* endif */ prclRect->xLeft=hsSprite->ptlPos.x; prclRect->yBottom=hsSprite->ptlPos.y; prclRect->xRight=prclRect->xLeft+hsSprite->bmihBitmap.cx-1; prclRect->yTop=prclRect->yBottom+hsSprite->bmihBitmap.cy-1; accessSem((PHEADER)hsSprite,usAction); return SPR_ERR_NOERROR; } Sprites and Animation (Part 2) - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ SprQuerySpriteSize ═══ SprQuerySpriteSize Unlike SprQuerySpritePosition() and SprQuerySpriteRect(), SprQuerySpriteSize() is not variant, but is fixed at the time of creation, so we do not need to check for membership in a playground. SPRERROR EXPENTRY SprQuerySpriteSize(HSPRITE hsSprite,PSIZEL pszlSize) //------------------------------------------------------------------------- // This function returns the current size of the sprite. // // Input: hsSprite - handle to the sprite // Output: pszlSize - points to the current size // Returns: SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise //------------------------------------------------------------------------- { USHORT usAction; if (queryHandleType(hsSprite)!=QH_HSPRITE) { return SPR_ERR_BADHANDLE; } /* endif */ usAction=accessSem((PHEADER)hsSprite,ACCSEM_SET); pszlSize->cx=hsSprite->bmihBitmap.cx; pszlSize->cy=hsSprite->bmihBitmap.cy; accessSem((PHEADER)hsSprite,usAction); return SPR_ERR_NOERROR; } Sprites and Animation (Part 2) - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ SprQuerySpriteVisibility ═══ SprQuerySpriteVisibility There is nothing special about this function that needs to be noted. SPRERROR EXPENTRY SprQuerySpriteVisibility(HSPRITE hsSprite,PBOOL pbVisible) //------------------------------------------------------------------------- // This function returns the visibility state of the sprite // // Input: hsSprite - handle to the sprite // Output: pbVisible - points to the visibility state // Returns: SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise //------------------------------------------------------------------------- { USHORT usAction; if (queryHandleType(hsSprite)!=QH_HSPRITE) { return SPR_ERR_BADHANDLE; } /* endif */ usAction=accessSem((PHEADER)hsSprite,ACCSEM_SET); if (hsSprite->hpgPlay==NULL) { accessSem((PHEADER)hsSprite,usAction); return SPR_ERR_HASNOPLAYGROUND; } /* endif */ *pbVisible=hsSprite->bVisible; accessSem((PHEADER)hsSprite,usAction); return SPR_ERR_NOERROR; } Sprites and Animation (Part 2) - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ SprQueryUpdateFlag ═══ SprQueryUpdateFlag There is nothing special about this function that needs to be noted. SPRERROR EXPENTRY SprQueryUpdateFlag(HPLAYGROUND hpgPlay,PBOOL pbUpdate) //------------------------------------------------------------------------- // This function returns the setting of the update flag. See the notes // for SprSetUpdateFlag() for more information about this setting. // // Input: hpgPlay - handle to the playground // pbUpdate - points to the variable to receive the update flag // Returns: SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise //------------------------------------------------------------------------- { USHORT usAction; if (queryHandleType(hpgPlay)!=QH_HPLAYGROUND) { return SPR_ERR_BADHANDLE; } /* endif */ usAction=accessSem((PHEADER)hpgPlay,ACCSEM_SET); *pbUpdate=hpgPlay->bUpdate; accessSem((PHEADER)hpgPlay,usAction); return SPR_ERR_NOERROR; } Sprites and Animation (Part 2) - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ SprRemoveSprite ═══ SprRemoveSprite This function removes a sprite from the ahsSprites field of the HPLAYGROUND structure and moves all members after the sprite down one slot in the array. SPRERROR EXPENTRY SprRemoveSprite(HPLAYGROUND hpgPlay,HSPRITE hsSprite) //------------------------------------------------------------------------- // This function removes the sprite from the membership list of the // specified playground. The sprite can then be added to another // playground, or this one at a later time. // // Since there is a limited number of sprites that can be members of // a playground, this function can be used to temporarily remove unused // sprites from a playground so that others can be used. // // Input: hpgPlay - handle to the playground // hsSprite - handle to the sprite to remove // Returns: SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise //------------------------------------------------------------------------- { USHORT usAction; ULONG ulIndex; if (queryHandleType(hpgPlay)!=QH_HPLAYGROUND) { return SPR_ERR_BADHANDLE; } else if (queryHandleType(hsSprite)!=QH_HSPRITE) { return SPR_ERR_BADHANDLE; } /* endif */ usAction=accessSem((PHEADER)hpgPlay,ACCSEM_SET); for (ulIndex=0; ulIndexulNumMembers; ulIndex++) { if (hpgPlay->ahsSprites[ulIndex]==hsSprite) { break; } /* endif */ } /* endfor */ if (ulIndex==hpgPlay->ulNumMembers) { accessSem((PHEADER)hpgPlay,usAction); return SPR_ERR_HASNOPLAYGROUND; } /* endif */ //---------------------------------------------------------------------- // Adjust the member array by moving all of the sprites after the one // being removed to the slot just before there current position. Then, // decrement the number of members and we're done. //---------------------------------------------------------------------- hpgPlay->ulNumMembers--; while (ulIndexulNumMembers) { hpgPlay->ahsSprites[ulIndex]=hpgPlay->ahsSprites[ulIndex+1]; ulIndex++; } /* endwhile */ hpgPlay->ahsSprites[ulIndex]=NULL; hsSprite->hpgPlay=NULL; accessSem((PHEADER)hpgPlay,usAction); return SPR_ERR_NOERROR; } Sprites and Animation (Part 2) - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ SprSetPlaygroundBack ═══ SprSetPlaygroundBack There is nothing special about this function that needs to be noted. SPRERROR EXPENTRY SprSetPlaygroundBack(HPLAYGROUND hpgPlay, HBITMAP hbmNew, HBITMAP *phbmOld) //------------------------------------------------------------------------- // This function sets the background bitmap of the playground. // // Note that, once this function is called, the bitmap is managed by // the sprite subsystem. The bitmap should *NOT* be deleted by the // application unless the bitmap is "unset" from the playground (by // calling this function again with a different handle). // // Input: hpgPlay - handle to the playground // hbmNew - handle to the new bitmap to used as the background // Output: phbmOld - points to the handle to the old background bitmap. // This can be NULL, meaning that the application isn't interested // in receiving this value. // Returns: SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise //------------------------------------------------------------------------- { USHORT usAction; if (queryHandleType(hpgPlay)!=QH_HPLAYGROUND) { return SPR_ERR_BADHANDLE; } /* endif */ usAction=accessSem((PHEADER)hpgPlay,ACCSEM_SET); if (phbmOld!=NULL) { *phbmOld=hpgPlay->hbmBack; } /* endif */ hpgPlay->hbmBack=hbmNew; //---------------------------------------------------------------------- // We're only interested in the cx and cy fields //---------------------------------------------------------------------- hpgPlay->bmihBack.cbFix=16; GpiQueryBitmapInfoHeader(hpgPlay->hbmBack,&hpgPlay->bmihBack); accessSem((PHEADER)hpgPlay,usAction); return SPR_ERR_NOERROR; } Sprites and Animation (Part 2) - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ SprSetPlaygroundColor ═══ SprSetPlaygroundColor Note that we do not force a repaint, since I didn't want to deal with passing in an HPS on too many functions. It is conceivable that the background color can be set immediately after creation of the playground and let it get displayed the next time WM_PAINT is processed. SPRERROR EXPENTRY SprSetPlaygroundColor(HPLAYGROUND hpgPlay,LONG lBackColor) //------------------------------------------------------------------------- // This function sets the new background color of the playground and is // only valid if the playground doesn't have a bitmap. // // Input: hpgPlay - handle to the playground // lBackColor - specifies the new background color // Returns: SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise //------------------------------------------------------------------------- { USHORT usAction; if (queryHandleType(hpgPlay)!=QH_HPLAYGROUND) { return SPR_ERR_BADHANDLE; } /* endif */ usAction=accessSem((PHEADER)hpgPlay,ACCSEM_SET); if (hpgPlay->hbmBack!=NULLHANDLE) { accessSem((PHEADER)hpgPlay,usAction); return SPR_ERR_HASBACKGROUND; } /* endif */ hpgPlay->lBackColor=lBackColor; accessSem((PHEADER)hpgPlay,usAction); return SPR_ERR_NOERROR; } Sprites and Animation (Part 2) - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ SprSetPlaygroundSize ═══ SprSetPlaygroundSize This function is allowed only if there is no background bitmap. SPRERROR EXPENTRY SprSetPlaygroundSize(HPLAYGROUND hpgPlay,PSIZEL pszlSize) //------------------------------------------------------------------------- // This function sets the playground size for playgrounds that do not have // a bitmap set as the background. // // Input: hpgPlay - handle to the playground // pszlSize - points to the size of the playground // Returns: SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise //------------------------------------------------------------------------- { USHORT usAction; if (queryHandleType(hpgPlay)!=QH_HPLAYGROUND) { return SPR_ERR_BADHANDLE; } /* endif */ usAction=accessSem((PHEADER)hpgPlay,ACCSEM_SET); if (hpgPlay->hbmBack!=NULLHANDLE) { accessSem((PHEADER)hpgPlay,usAction); return SPR_ERR_HASBACKGROUND; } /* endif */ hpgPlay->bmihBack.cx=pszlSize->cx; hpgPlay->bmihBack.cy=pszlSize->cy; accessSem((PHEADER)hpgPlay,usAction); return SPR_ERR_NOERROR; } Sprites and Animation (Part 2) - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ SprSetSpritePosition ═══ SprSetSpritePosition SPRERROR EXPENTRY SprSetSpritePosition(HPS hpsDraw, HSPRITE hsSprite, PPOINTL pptlNew) //------------------------------------------------------------------------- // This function changes the position of the sprite. This function is // optimized so that, if the rectangle bounding the sprite at the new // position overlaps the old, only one "bit blit" to the specified HPS // is done, eliminating flicker. // // Input: hpsDraw - handle to the HPS to draw the sprite in once it is // moved // hsSprite - handle to the sprite // Returns: SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise //------------------------------------------------------------------------- { USHORT usAction; SIZEL szlPlay; SIZEL szlWork; RECTL rclOld; RECTL rclNew; RECTL rclUnion; RECTL rclSrc; RECTL rclDest; POINTL ptlWork; POINTL aptlPoints[4]; if (queryHandleType(hsSprite)!=QH_HSPRITE) { return SPR_ERR_BADHANDLE; } /* endif */ usAction=accessSem((PHEADER)hsSprite,ACCSEM_SET); if (hsSprite->hpgPlay==NULL) { accessSem((PHEADER)hsSprite,usAction); return SPR_ERR_HASNOPLAYGROUND; } /* endif */ if ((hsSprite->bVisible) && (hsSprite->hpgPlay->bUpdate)) { szlWork.cx=MAX_SPRITE_CX*2; szlWork.cy=MAX_SPRITE_CY*2; SprQueryPlaygroundSize(hsSprite->hpgPlay,&szlPlay); Note how we query the sprite rectangle before and after the position changes and then call WinUnionRect() to check for overlap. This determines our course of action. If there is no overlap, call drawBackAt() and SprDrawSprite() to erase the sprite at its old position and redraw at its new position. Otherwise, perform the delta processing (see below). SprQuerySpriteRect(hsSprite,&rclOld); hsSprite->ptlPos=*pptlNew; SprQuerySpriteRect(hsSprite,&rclNew); WinUnionRect(hsSprite->habAnchor,&rclUnion,&rclOld,&rclNew); if ((rclUnion.xRight-rclUnion.xLeft>MAX_SPRITE_CX*2) || (rclUnion.yTop-rclUnion.yBottom>MAX_SPRITE_CY*2)) { rclSrc.xLeft=rclOld.xLeft; rclSrc.yBottom=rclOld.yBottom; rclSrc.xRight=rclSrc.xLeft+hsSprite->bmihBitmap.cx; rclSrc.yTop=rclSrc.yBottom+hsSprite->bmihBitmap.cy; drawBackAt(hpsDraw,hsSprite->hpgPlay,NULL,NULL,&rclSrc); SprDrawSprite(hpsDraw,hsSprite); } else { rclUnion contains the bounding rectangle of the old and new positions, so first transfer this rectangle from the background to the workarea (offset by (-rclUnion.xLeft,-rclUnion.yBottom)). rclSrc=rclUnion; rclSrc.xRight++; rclSrc.yTop++; rclDest.xLeft=0; rclDest.yBottom=0; rclDest.xRight=rclUnion.xRight-rclUnion.xLeft; rclDest.yTop=rclUnion.yTop-rclUnion.yBottom; drawBackAt(hsSprite->hpgPlay->hpsWork, hsSprite->hpgPlay, &rclDest, &szlWork, &rclSrc); Once the background has been drawn, call drawSpriteAt() with a position also offset by (-rclUnion.xLeft,-rclUnion.yBottom). This completes our drawing in the workarea; now we simply need to remove the offset, clip to the playground, and call GpiBitBlt() to transfer the entire rclUnion-sized rectangle from the workarea to the screen. ptlWork.x=hsSprite->ptlPos.x-rclUnion.xLeft; ptlWork.y=hsSprite->ptlPos.y-rclUnion.yBottom; drawSpriteAt(hsSprite->hpgPlay->hpsWork,hsSprite,&szlWork,&ptlWork); //---------------------------------------------------------------- // GpiBitBlt is non-inclusive on source AND target //---------------------------------------------------------------- aptlPoints[0].x=rclUnion.xLeft; aptlPoints[0].y=rclUnion.yBottom; aptlPoints[1].x=rclUnion.xRight+1; aptlPoints[1].y=rclUnion.yTop+1; aptlPoints[2].x=0; aptlPoints[2].y=0; aptlPoints[3].x=rclUnion.xRight-rclUnion.xLeft+1; aptlPoints[3].y=rclUnion.yTop-rclUnion.yBottom+1; if (clipBltPoints(hsSprite->habAnchor,aptlPoints,&szlPlay)) { GpiBitBlt(hpsDraw, hsSprite->hpgPlay->hpsWork, 4, aptlPoints, ROP_SRCCOPY, BBO_IGNORE); } /* endif */ } /* endif */ } else { hsSprite->ptlPos=*pptlNew; } /* endif */ accessSem((PHEADER)hsSprite,usAction); return SPR_ERR_NOERROR; } Sprites and Animation (Part 2) - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ SprSetSpriteVisibility ═══ SprSetSpriteVisibility There is nothing special about this function that needs to be noted. SPRERROR EXPENTRY SprSetSpriteVisibility(HPS hpsDraw, HSPRITE hsSprite, BOOL bVisible) //------------------------------------------------------------------------- // This function shows or hides a sprite. // // Input: hpsDraw - handle to the HPS to draw in once the sprite is // shown or hidden // hsSprite - handle to the sprite // bVisible - new visibility state // Returns: SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise //------------------------------------------------------------------------- { USHORT usAction; RECTL rclSprite; if (queryHandleType(hsSprite)!=QH_HSPRITE) { return SPR_ERR_BADHANDLE; } /* endif */ usAction=accessSem((PHEADER)hsSprite,ACCSEM_SET); if (hsSprite->hpgPlay==NULL) { accessSem((PHEADER)hsSprite,usAction); return SPR_ERR_HASNOPLAYGROUND; } /* endif */ if (hsSprite->bVisible!=bVisible) { hsSprite->bVisible=bVisible; if (hsSprite->hpgPlay->bUpdate) { if (hsSprite->bVisible) { SprDrawSprite(hpsDraw,hsSprite); } else { rclSprite.xLeft=hsSprite->ptlPos.x; rclSprite.yBottom=hsSprite->ptlPos.y; rclSprite.xRight=rclSprite.xLeft+hsSprite->bmihMask.cx; rclSprite.yTop=rclSprite.yBottom+hsSprite->bmihMask.cy; drawBackAt(hpsDraw,hsSprite->hpgPlay,NULL,NULL,&rclSprite); } /* endif */ } /* endif */ } /* endif */ accessSem((PHEADER)hsSprite,usAction); return SPR_ERR_NOERROR; } Sprites and Animation (Part 2) - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ SprSetUpdateFlag ═══ SprSetUpdateFlag There is nothing special about this function that needs to be noted. SPRERROR EXPENTRY SprSetUpdateFlag(HPLAYGROUND hpgPlay,BOOL bUpdate) //------------------------------------------------------------------------- // This function sets the update flag for the playground. If FALSE, no // drawing actually takes place in any of the functions requiring an HPS, // and the value of the HPS handle may be NULLHANDLE. If TRUE, updating // is reenabled, but you should still call SprDrawPlayground() to refresh // the screen with the current contents. // // Input: hpgPlay - handle to the playground // bUpdate - specifies the new update flag // Returns: SPR_ERR_NOERROR if successful, SPR_ERR_* constant otherwise //------------------------------------------------------------------------- { USHORT usAction; if (queryHandleType(hpgPlay)!=QH_HPLAYGROUND) { return SPR_ERR_BADHANDLE; } /* endif */ usAction=accessSem((PHEADER)hpgPlay,ACCSEM_SET); hpgPlay->bUpdate=bUpdate; accessSem((PHEADER)hpgPlay,usAction); return SPR_ERR_NOERROR; } Sprites and Animation (Part 2) - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 2.2. Resources and Decompiling Them ═══ ═══ 2.2.1. Introduction ═══ Resources and Decompiling Them Written by Martin Lafaix Introduction What's that? OS/2 comes with a resource compiler, RC, which allows us to put resources in an executable file; but, it would sometimes be useful to do just the opposite, namely, extracting resources from an executable (or from a DLL). Why? If we were able to extract resources, it would help us adapting this lovely little tool, which unfortunately has all its messages and menus in, say, Chinese... :-) and which is no longer supported by its author. Or, it would allow us to correct those lovely typographical errors in the base OS/2 system, too. (At least, the French version includes some boring typos, in menu items and shortcuts :-( ) Or, it would even allow us to grab some lovely dialog box and include it in our wonderful projects. Contents This article contains four parts. The first one describes the general executable file structure, the second one describes the 16-bit EXE structure, the third describes the 32-bit EXE structure, and the fourth describes the RES to RC translation. Sample code will be given in REXX, which will use many user-defined functions, such as readw() or readl(). A (short) bibliography can be found at the end of this document. Resources and Decompiling Them - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 2.2.2. A Voyage to OMF ═══ A Voyage to OMF In the following sections, "EXE" will be used as a generic term for .EXE or .DLL files. The Old DOS Header Each EXE starts with an old DOS header. Two fields interest us, namely e_magic and e_lfanew. The first one contains "MZ" and allowed us to recognize the EXE header, and the second one contains the offset of the new EXE header, which is where the fun begins. The remaining fields are used for a DOS 'stub', that is, a program which displays a message like: This program cannot be run in a DOS session. This message is displayed whenever the program is run from vanilla DOS. struct exe_hdr /* DOS 1, 2, 3 .EXE header */ { unsigned short e_magic; /* Magic number */ unsigned short e_cblp; /* Bytes on last page of file */ unsigned short e_cp; /* Pages in file */ unsigned short e_crlc; /* Relocations */ unsigned short e_cparhdr; /* Size of header in paragraphs */ unsigned short e_minalloc; /* Minimum extra paragraphs needed */ unsigned short e_maxalloc; /* Maximum extra paragraphs needed */ unsigned short e_ss; /* Initial (relative) SS value */ unsigned short e_sp; /* Initial SP value */ unsigned short e_csum; /* Checksum */ unsigned short e_ip; /* Initial IP value */ unsigned short e_cs; /* Initial (relative) CS value */ unsigned short e_lfarlc; /* File address of relocation table */ unsigned short e_ovno; /* Overlay number */ unsigned short e_res[ERES1WDS];/* Reserved words */ unsigned short e_oemid; /* OEM identifier (for e_oeminfo) */ unsigned short e_oeminfo; /* OEM information; e_oemid specific */ unsigned short e_res2[ERES2WDS];/* Reserved words */ long e_lfanew; /* File address of new exe header */ }; Figure 1. The DOS 1, 2, 3 .EXE header. Recognizing it from REXX In the following code, infile contains the EXE filename. base will then contain the new header offset. if charin(infile,,2) = 'MZ' then base = 1+l2d(charin(infile,61,4)) Resources and Decompiling Them - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 2.2.3. Exploring 16-Bit Headers ═══ Exploring 16-Bit Headers Recognizing a 16-bit EXE header A 16-bit EXE header starts with the 'NE' magic number; a 16-bit EXE structure starts with this magic number: struct new_exe /* New .EXE header */ { unsigned short ne_magic; /* Magic number NE_MAGIC */ unsigned char ne_ver; /* Version number */ unsigned char ne_rev; /* Revision number */ unsigned short ne_enttab; /* Offset of Entry Table */ unsigned short ne_cbenttab; /* Number of bytes in Entry Table */ long ne_crc; /* Checksum of whole file */ unsigned short ne_flags; /* Flag word */ unsigned short ne_autodata; /* Automatic data segment number */ unsigned short ne_heap; /* Initial heap allocation */ unsigned short ne_stack; /* Initial stack allocation */ long ne_csip; /* Initial CS:IP setting */ long ne_sssp; /* Initial SS:SP setting */ unsigned short ne_cseg; /* Count of file segments */ unsigned short ne_cmod; /* Entries in Module Reference Table */ unsigned short ne_cbnrestab; /* Size of non-resident name table */ unsigned short ne_segtab; /* Offset of Segment Table */ unsigned short ne_rsrctab; /* Offset of Resource Table */ unsigned short ne_restab; /* Offset of resident name table */ unsigned short ne_modtab; /* Offset of Module Reference Table */ unsigned short ne_imptab; /* Offset of Imported Names Table */ long ne_nrestab; /* Offset of Non-resident Names Table */ unsigned short ne_cmovent; /* Count of movable entries */ unsigned short ne_align; /* Segment alignment shift count */ unsigned short ne_cres; /* Count of resource entries */ unsigned char ne_exetyp; /* Target operating system */ unsigned char ne_flagsothers; /* Other .EXE flags */ char ne_res[NERESBYTES]; /* Pad structure to 64 bytes */ }; Figure 2. The OS/2 286 .EXE header. The following fields interest us: Field Contents ne_cseg is the number of segments in the EXE. Segments containing resources are at the end of the segment table. ne_segtab is the offset of the segment table. Each entry in this table contains the following: ssector (WORD) is the segment's beginning sector, from the beginning of the EXE. See ne_align below for more explanations on how to compute the segment's effective position in EXE. cb (WORD) is the segment's size in bytes. sflags (WORD) is the segment's flags. Interesting bits are: NSMOVE 0x0010 Moveable segment flag NSSHARED 0x0020 Shared segment flag NSPRELOAD 0x0040 Preload segment flag NSDISCARD 0x1000 Segment is discardable smin (WORD) is the minimum allocation in bytes. This field's value is not used with segments containing resources. Note: The segment table offset is from the beginning of the 286 EXE header, not from the beginning of the EXE. ne_rsrctab is the offset of the resource table. Each entry in this table contains two fields: etype (WORD) is the resource type. ename (WORD) is the resource name (well, for OS/2, it's a number). Note: The resource table offset is from the beginning of the 286 EXE header, not from the beginning of the EXE. ne_align is the segment alignment shift count. It's the number of bits we should shift the segment's beginning sector value to find the segment's position in EXE. For example, if ne_align is 4, segments will be aligned on 16-byte boundaries (that is, the EXE will be composed of 16-byte 'sectors'). ne_cres is the number of resources in the EXE. Each resource uses a segment. More information on the 16-bit EXE header can be found in NEWEXE.H, which comes with the Developer's toolkit. Unfortunately, it isn't very informative. Extracting a Resource From The EXE The process of extracting resources from an EXE to a RES file is quite simple. We walk through the resource table (rsrctab), and, for each entry, we find and emit the corresponding segment. (We have to twiddle the segment flag and create a small header for the resource, but that's not a big deal.) o First, we have to find the corresponding resource table entry (resource number cnt): call charin infile,base+rsrctab+cnt*4,0 o Then, we have to read the entry's content: etype = readw() ename = readw() o Then, we have to find and read the corresponding segment table entry: call segin cseg-rsrccnt+1+cnt o Then we...(I'm using a procedure here for readability.) segin: call charin infile,base+segtab+(arg(1)-1)*8,0 ssector = readw() cb = readw() sflags = readw() smin = readw() o We then calculate the resource offset: pos = 1+(2**segshift)*ssector o And we translate the segment flag (from NSMMOVE, etc. to MOVEABLE, etc.): flags = 0 if bit(sflags,10) then flags = flags+64 if bit(sflags,12) then flags = flags+16 if bit(sflags,4) then flags = flags+4096 if \ bit(sflags,11) then flags = flags+32 o We are now ready to write the resource header to a RES file: call emit 'FF'x||d2w(etype)'FF'x||d2w(ename)d2w(flags)d2l(cb) o And, last but not least, we have to write the resource data, too: call emit charin(infile,pos,cb) Note: The RES format is explained in the section The RES File Format Resources and Decompiling Them - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 2.2.4. Exploring 32-Bit Headers ═══ Exploring 32-Bit Headers Recognizing a 32-bit EXE header A 32-bit EXE header starts with the 'LX' magic number; a 32-bit structure starts with this magic number: struct e32_exe /* New 32-bit .EXE header */ { unsigned char e32_magic[2]; /* Magic number E32_MAGIC */ unsigned char e32_border; /* The byte ordering for the .EXE */ unsigned char e32_worder; /* The word ordering for the .EXE */ unsigned long e32_level; /* The EXE format level for now = 0 */ unsigned short e32_cpu; /* The CPU type */ unsigned short e32_os; /* The OS type */ unsigned long e32_ver; /* Module version */ unsigned long e32_mflags; /* Module flags */ unsigned long e32_mpages; /* Module # pages */ unsigned long e32_startobj; /* Object # for instruction pointer */ unsigned long e32_eip; /* Extended instruction pointer */ unsigned long e32_stackobj; /* Object # for stack pointer */ unsigned long e32_esp; /* Extended stack pointer */ unsigned long e32_pagesize; /* .EXE page size */ unsigned long e32_pageshift; /* Page alignment shift in .EXE */ unsigned long e32_fixupsize; /* Fixup section size */ unsigned long e32_fixupsum; /* Fixup section checksum */ unsigned long e32_ldrsize; /* Loader section size */ unsigned long e32_ldrsum; /* Loader section checksum */ unsigned long e32_objtab; /* Object table offset */ unsigned long e32_objcnt; /* Number of objects in module */ unsigned long e32_objmap; /* Object page map offset */ unsigned long e32_itermap; /* Object iterated data map offset */ unsigned long e32_rsrctab; /* Offset of Resource Table */ unsigned long e32_rsrccnt; /* Number of resource entries */ unsigned long e32_restab; /* Offset of resident name table */ unsigned long e32_enttab; /* Offset of Entry Table */ unsigned long e32_dirtab; /* Offset of Module Directive Table */ unsigned long e32_dircnt; /* Number of module directives */ unsigned long e32_fpagetab; /* Offset of Fixup Page Table */ unsigned long e32_frectab; /* Offset of Fixup Record Table */ unsigned long e32_impmod; /* Offset of Import Module Name Table */ unsigned long e32_impmodcnt; /* Number of entries in Import Module Name Table */ unsigned long e32_impproc; /* Offset of Import Procedure Name Table unsigned long e32_pagesum; /* Offset of Per-Page Checksum Table */ unsigned long e32_datapage; /* Offset of Enumerated Data Pages */ unsigned long e32_preload; /* Number of preload pages */ unsigned long e32_nrestab; /* Offset of Non-resident Names Table */ unsigned long e32_cbnrestab; /* Size of Non-resident Name Table */ unsigned long e32_nressum; /* Non-resident Name Table Checksum */ unsigned long e32_autodata; /* Object # for automatic data object */ unsigned long e32_debuginfo; /* Offset of the debugging information */ unsigned long e32_debuglen; /* The length of the debugging info. in bytes */ unsigned long e32_instpreload;/* Number of instance pages in preload section of .EXE file */ unsigned long e32_instdemand; /* Number of instance pages in demand load section of .EXE file */ unsigned long e32_heapsize; /* Size of heap - for 16-bit apps */ unsigned long e32_stacksize; /* Size of stack */ unsigned char e32_res3[E32RESBYTES3]; /* Pad structure to 196 bytes */ }; Figure 3. The OS/2 386 .EXE header. The following fields interest us: Field Contents e32_pageshift is the page alignment shift in the EXE. It's the number of bits we should shift the page offset value to find the page's position in EXE. For example, if e32_pageshift is 4, pages will be aligned on 16-byte boundaries. e32_objtab is the object table offset. Each entry contains the following fields: 32 1 32 1 ┌───────────────────────┬───────────────────────┐ │ size │ base │ ├───────────────────────┼───────────────────────┤ │ flags │ pagemap │ ├───────────────────────┼───────────────────────┤ │ mapsize │ reserved │ └───────────────────────┴───────────────────────┘ o size is the object virtual size. o base is the object base virtual address. o flags is the object attribute flags. o pagemap is the object page map index. o mapsize is the number of entry in the object's page map. o reserved is, well, reserved :-) We're only interested by flags and pagemap. That is, flags contains the resource flags (MOVEABLE, LOADONCALL, and so on), while pagemap allows us to find the object's pages within the EXE. (See the "Tables and Maps Relations" section below, for more explanations on pagemap, the object table, and other tables.) flags' interesting bits are: OBJWRITE 0x0002L Writeable Object OBJDISCARD 0x0010L Object is Discardable OBJSHARED 0x0020L Object is Shared OBJPRELOAD 0x0040L Object has preload pages Note: The object table offset is from the beginning of the 386 EXE header, not from the beginning of the EXE. e32_objmap is the object page map offset. Each entry contains the following fields: 32 1 16 1 16 1 ┌───────────────────────┬────────────┬───────────┐ │ pagedataoffset │ pagesize │ pageflags │ └───────────────────────┴────────────┴───────────┘ o pagedataoffset is the file offset of page. o pagesize is the number of bytes of page data. o pageflags is per-page attributes. Note: The object page map offset is from the beginning of the 386 EXE header, not from the beginning of the EXE. e32_rsrctab is the offset of the resource table. Each entry contains the following fields: 16 1 16 1 32 1 ┌───────────┬───────────┬────────────────────────┐ │ type │ name │ cb │ ├───────────┼───────────┴────────────┬───────────┘ │ obj │ offset │ └───────────┴────────────────────────┘ 16 1 32 1 o type is the resource type. o name is the resource name. o cb is the resource size, in bytes. o obj is the number of the object containing the resource. o offset is the resource's offset within object. Resource will be in object obj, starting at the specified offset: 0 offset offset+cb ┌───────────────────────────────────────────── obj│ │<- resource ->│ ... └───────────────────────────────────────────── Note: The resource table offset is from the beginning of the 386 EXE header, not from the beginning of the EXE. e32_rsrccnt is the number of resources in the EXE. e32_datapage is the offset of Enumerated data page. It's the position of the first data page in the EXE. Warning: This offset is from the beginning of the EXE, not from the beginning of the 386 EXE header. It's THE exception :-/ More information on the 32-bit EXE header can be found on exe386.h, which comes with the Developer's toolkit. It's not that informative, though, and OMF.INF is much better. I highly recommend it. Tables and Maps Relations In this section, we will view the various relations between tables and maps. ┌───────────────────────────────┬──┬─────────┬──┬───────── file │ │ │ │ │ ... └───────────────────────────────┴──┴─────────┴──┴─────────   object's page 1│ │ object's page 2 └──────────┐ │ │ │ │ │ : │ │ │ │ │ │ : │ │ │ │ r-1│ │ │ │ ┌────├────┤ │ │ ├────┤ : │ │ │ p │ │ ─┘ │ r │ │ ─────┐ │ │ │ ├────┤ │ ├────┤ │ o-1│ │ │ p+1│ │ ───┘ r+1│ │ └────├────┤ │ ├────┤ │ │ o │ │ ─────┘ : │ │ : │ │ ├────┤ │ │ │ │ o+1│ │ │ │ resource object page map table table Figure 4. Tables and maps relations. To find the data of a resource r, we first have to read the corresponding entry in the resource table. The obj field of this entry allows us to find the object which contains the data. The pagemap field of the object table entry then allows us to locate the object's pages in the EXE, via the page map. To find the resource data, we then just have to read cb bytes from object, starting at offset. Extracting a Resource From The EXE The process of extracting resources from a 32-bit EXE to a RES file is similar to the 16-bit EXE to RES conversion. We walk through the resource table (rsrctab), and, for each entry, we find and emit the corresponding resource. (We have to twiddle the object flag and create a small header for the resource, but that's not a big deal.) Note: In the following code, we'll assume that objects span over consecutive pages. That is, we will not handle the case where the object's pages are arranged discontinuously in the EXE. o First, we have to find the corresponding resource table entry (resource number cnt): call charin infile,base+rsrctab+cnt*14,0 o Then, we have to read the entry's content: etype = readw() /* resource type */ ename = readw() /* resource name */ cb = readl() /* resource size */ eobj = readw() /* object containing resource */ eoffset = readl() /* resource's offset in eobj */ call objin eobj (I'm using a procedure here for readability.) objin: call charin infile,base+objtab+(arg(1)-1)*24,8 oflags = readl() /* object attributes */ opagemap = readl() /* object page map index */ omapsize = readl() /* -- not used -- */ opagedataoffset = l2d(charin(infile,base+objmap+(opagemap-1)*8,4)) o We then calculate the resource offset: pos = 1+datapage+eoffset+(2**pageshift)*opagedataoffset o And we translate the object flag (from OBJPRELOAD, ... to LOADONCALL, ...): flags = 0 if bit(oflags,10) then flags = flags+64 if bit(oflags,11) then flags = flags+16 if bit(oflags,12) then flags = flags+4096 if \ bit(oflags,15) then flags = flags+32 o We are now ready to write the resource header to a RES file: call emit 'FF'x||d2w(etype)'FF'x||d2w(ename)d2w(flags)d2l(cb) o And, last but not least, we have to write the resource data, too: call emit charin(infile,pos,cb) Note: The RES format is explained in the next section (The RES file format). Resources and Decompiling Them - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 2.2.5. The RES to RC Translation ═══ The RES to RC Translation And now the last part. First, we will describe the RES format, and we will then describe some resources data. We will focus our interest on the human-readable resources, such as menus, stringtables and so on. Note: An important exception will be the dialog templates, for the following two reasons: o The Dialog editor already does this. o It's not that different from the others resources, and it would necessitated an even more fastidious enumeration. For these reasons, our resource decompiler will not extract dialog templates from RES to RC. And we will finally describe briefly the included resource decompiler, rdc. The RES format The RES file is an aggregate of resources. Each resource is composed of a header, followed by the resource data. 8 1 16 1 8 1 16 1 16 1 ┌────┬─────────────┬────┬────────────┬───────────┐ │0xFF│ Type │0xFF│ Id │ Flags │ ├────┴─────────────┴────┼────────────┴───────────┘ │ cb │ └───────────────────────┘ 32 1 Figure 5. The resource header. Type is the resource type (see below). Id is the resource name/identifier. Flags is the resource attributes (MOVEABLE, LOADONCALL, ...). cb is the resource size. The following types may appear: RT_POINTER 1 /* mouse pointer shape */ RT_BITMAP 2 /* bitmap */ RT_MENU 3 /* menu template */ RT_DIALOG 4 /* dialog template */ RT_STRING 5 /* string tables */ RT_FONTDIR 6 /* font directory */ RT_FONT 7 /* font */ RT_ACCELTABLE 8 /* accelerator tables */ RT_RCDATA 9 /* binary data */ RT_MESSAGE 10 /* error msg tables */ RT_DLGINCLUDE 11 /* dialog include file name */ RT_VKEYTBL 12 /* key to vkey tables */ RT_KEYTBL 13 /* key to UGL tables */ RT_CHARTBL 14 /* glyph to character tables */ RT_DISPLAYINFO 15 /* screen display information */ RT_FKASHORT 16 /* function key area short form */ RT_FKALONG 17 /* function key area long form */ RT_HELPTABLE 18 /* Help table for Cary Help manager */ RT_HELPSUBTABLE 19 /* Help subtable for Cary Help manager */ RT_FDDIR 20 /* DBCS uniq/font driver directory */ RT_FD 21 /* DBCS uniq/font driver */ Other values for type denote user-defined resources. Resource data format We first have to read the resource header: res2rc: /* convert .RES format to .RC */ call skip 1 /* skipping the 'FF'x */ rt = readw() /* the resource type */ call skip 1 /* skipping the 'FF'x */ id = readw() /* the resource ID/name */ opt = readw() /* the resource flag */ cb = readl() /* the resource data size */ Then, according to the resource type, we'll have to do specific operations: select when rt = 1 then call emit 'POINTER 'id' 'option()' 'file('ptr')nl when rt = 2 then call emit 'BITMAP 'id' 'option()' 'file('bmp')nl when rt = 7 then call emit 'FONT 'id' 'option()' 'file('fon')nl If the resource is a pointer, a bitmap, a font or an icon, the resource data is the corresponding pointer, bitmap, font or icon. We then just have to recreate a file containing this data. when rt = 3 then do; call emit 'MENU 'id' 'option()nl'BEGIN'nl; call emit menuout(' ')nl'END'nl; end If the resource is a menu, it's not that simple :-) The resource data is the corresponding menu structure: 16 1 16 1 16 1 16 1 16 1 ┌──────┬──────┬──────┬──────┬──────┬──────────────────── │ cb │ type │ cp │ offs │ count│ ... └──────┴──────┴──────┴──────┴──────┴──────────────────── cb is the size of the menu data. type is the menu type (Only 0 and 1 are valid). cp is the menu code page (850 by default). offs is the starting offset of the menu data, from the start of the structure. count is the number of item composing the menu. If the menu type is 1, count is followed by another 16-bit field, ppoffs (presentation parameter offset, from the start of the structure). But we won't handle type 1 menus, so... Every item has the following format: 16 1 16 1 16 1 ┌──────┬──────┬──────┬────────────────────────────────── │style │attrib│ Id │ [Optional Data] └──────┴──────┴──────┴────────────────────────────────── style is the item style (MIS_*). attrib is the item attributes (MIA_*). Id is the item identifier. If the item contains data (that is, if item is a submenu, or has the MIS_BITMAP, MIS_STRING or ... style), then the previous structure is followed by the corresponding data: MIS_SUBMENU Data is a menu structure, as previously defined. MIS_STRING Data is a null-terminated string. MIS_BITMAP Data can be any of the following: 'FF'x, followed by a 16-bit word, representing the resource identifier. '00'x. No resource identifier provided. "#", and subsequent characters make up the decimal representation of the resource identifier. So, if the resource is a menu, we will have to emit each item, recursively (as a menu can contain a submenu, ...). when rt = 5 then call emit 'STRINGTABLE 'option()nl'BEGIN'strout()'END'nl when rt = 10 then call emit 'MESSAGETABLE 'option()nl'BEGIN'strout()'END'nl If the resource is a stringtable or a messagetable, then we have to emit the corresponding table. Each string/messagetable contains up to 16 strings. (In a RC file, you can have more than one stringtable, with more than 16 strings, but rc does not preserve your ordering -- string IDs are maintained, though.) In the RES file, STRINGTABLE data looks like the following: 16 1 8 1 1 len+1 8 1 ┌──────┬───┬─────────────────────┬───┬────────────────── │ dummy│len│string1 0│len│string2 ... └──────┴───┴─────────────────────┴───┴────────────────── Each string is zero-terminated. If len is zero, the string does not exists. when rt = 8 then do; call emit 'ACCELTABLE 'id' 'option()nl'BEGIN'nl||keyout()'END'nl; end If the resource is an acceltable, then we have to emit the corresponding table. ACCELTABLE resource data looks like the following: 16 1 16 1 16 1 16 1 16 1 ┌─────┬─────┬─────┬─────┬─────┬─────┬─────────────────── │count│ cp │type1│key1 │cmd1 │type2│... ... └─────┴─────┴─────┴─────┴─────┴─────┴─────────────────── count is the number of keys in the acceltable. cp is the acceltable codepage. And the type/key/cmd triplets describe the accel-keys : type is the key's type (VIRTUALKEY, shifted, ...). key is the key's value (VK_F1, "a", ...). cmd is the accel command. when rt = 11 then do; call emit 'DLGINCLUDE 'id' 'charin(infile,,cb)nl; cb = 0; end If the resource is a dlginclude statement, then the resource data will contain the included file name. Note: This information is of little value if you don't have the included file... when rt = 18 then call emit 'HELPTABLE 'id||nl'BEGIN'htout()'END'nl If the resource is a helptable, then the resource data will contain the following: 16 1 16 1 16 1 16 1 ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────── │wnd 1│sub 1│-----│ext 1│wnd 2│sub 2│-----│ext 2│ ... └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────── < helpitem 1 > < helpitem 2 > wnd n is the application window ID. sub n is the help subtable ID. ext n is the extended help panel ID. when rt = 19 then call emit 'HELPSUBTABLE 'id||hstout()nl If the resource is a HELPSUBTABLE, then the resource data will contain the following: 16 1 16 1 16 1 ┌─────┬─────┬─────┬─────┬─────┬───────────────────────── │size │wnd 1│help1│wnd 2│help2│ ... └─────┴─────┴─────┴─────┴─────┴───────────────────────── Each subitem contains size items (the default size value is 2): wnd n is the child window ID. help n is the help panel ID. And, if size is more than 2, the remaining integers have an application-defined meaning. otherwise call emit 'RESOURCE 'rt' 'id' 'option()' 'file('dat') If the resource is of any other type, then we emit the 'RESOURCE' generic statement, and we put the resource data in a .DAT file. The rc compiler will handle that gracefully. :-) end /* select */ The Resource decompiler The interesting part, at last! A resource decompiler (named rdc.cmd) is provided in rdc.zip. It's usage is as follow: Usage: rdc [] <.EXE input file> [<.RC output file>] -r - Extract .res file -h - Access Help Figure 6. The resource decompiler usage. Note: Please note the following: o It's not highly polished. o The RES to RC translation part is known to be buggy. o You can't directly obtain a RC file from an EXE. You have to take a two-step process (first, rdc -r xxx.exe, and then rdc xxx.res xxx.rc). o If files named res_*.* are present in the current directory, you'll get a strange result. (And it may trash these files.) o The default RC file extension is '.RC2'. It'll protect you from bad surprises. o And, as stated earlier, it does not extract dialog templates from RES to RC files (but it does extract them from EXE to RES). But, to be optimistic, it works just fine most of the time :-) Resources and Decompiling Them - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 2.2.6. Summary ═══ Summary In the previous parts, we have seen how to extract resources from an executable (a .EXE or a .DLL), and how to extract some resources from a .RES file, as we have focused our interest on the 'human-readable' resources. While I realize there are still many obscure points, I hope you will find the included information useful. And I'll try my best to answer all questions on it. Resources and Decompiling Them - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 2.2.7. Bibliography ═══ Bibliography On EXE format OMF.INF describes the new 32-bit file format. It's a good discussion on OMF (Object Module Format) and the 386 EXE header. It's available on ftp-os2.cdrom.com. EXE.H, EXE386.H and NEWEXE.H These C header files contain the various EXE header structures. But it's not really a good place to start with. On RC/RES format Control Program Guide and Reference This on-line manual describes the DosGetResource/DosFreeResource APIs. It's part of the Developer's toolkit. PM Reference This on-line manual contains much useful information on the resource data format (In Related Information/Resource File). It's part of the Developer's toolkit. Tools Reference This on-line manual contains the Resource Compiler reference. It describes all RC statements/directives. It's part of the Developer's toolkit. Resources and Decompiling Them - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ outname ═══ outname This function, which requires two parameters, returns an output filename (if one does not already exists). outname: /* return name made from infile and extension */ if outfile = '' then if lastpos('.',arg(1)) > lastpos('\',arg(1)) then outfile = left(arg(1),lastpos('.',arg(1)))arg(2) else outfile = arg(1)'.'arg(2) return outfile Resources and Decompiling Them - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ readw ═══ readw This function reads one word (two bytes) from current file position. The file's position is updated. readw: /* read one word from infile */ return w2d(charin(infile,,2)) Resources and Decompiling Them - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ readl ═══ readl This function reads one long word (four bytes) from current file position. The file's position is updated. readl: /* read one long from infile */ return l2d(charin(infile,,4)) Resources and Decompiling Them - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ skip ═══ skip This function skips arg(1) chars in current file. The file's position is updated. skip: /* skip arg(1) chars */ return charin(infile,,arg(1)) Resources and Decompiling Them - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ bit ═══ bit This function returns bit arg(2) of arg(1). arg(1) can contain up to 32 bits. Note: bits are numbered from left to right. bit: /* return bit arg(2) of arg(1) */ return substr(x2b(d2x(arg(1),4)), arg(2),1) Resources and Decompiling Them - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ w2d ═══ w2d This function translates a little-endian word to a REXX integer. w2d: /* little-endian word to decimal */ w = c2x(arg(1)) return x2d(substr(w,3,2)substr(w,1,2)) Resources and Decompiling Them - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ d2w ═══ d2w This function translates a REXX integer to a little-endian word. d2w: /* decimal to little-endian word */ w = d2x(arg(1),4) return x2c(substr(w,3,2)substr(w,1,2)) Resources and Decompiling Them - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ l2d ═══ l2d This function translates a little-endian long word to a REXX integer. l2d: /* little-endian long to decimal */ l = c2x(arg(1)) return x2d(substr(l,7,2)substr(l,5,2)substr(l,3,2)substr(l,1,2)) Resources and Decompiling Them - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ d2l ═══ d2l This function translates a REXX integer to a little-endian long word. d2l: /* decimal to little-endian long */ l = d2x(arg(1),8) return x2c(substr(l,7,2)substr(l,5,2)substr(l,3,2)substr(l,1,2)) Resources and Decompiling Them - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ emit ═══ emit This function writes arg(1) to output file. emit: /* write data to output file */ return charout(outfile,arg(1)) Resources and Decompiling Them - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ option ═══ option This function translates the option's attributes into a RC string. option: /* convert flags to option string */ if bit(opt,10) then r = 'PRELOAD'; else r = 'LOADONCALL' if bit(opt,12) then r = r' MOVEABLE' if bit(opt, 4) then r = r' DISCARDABLE' if \ (bit(opt,4) | bit(opt,12)) then r = r' FIXED' if r = 'LOADONCALL MOVEABLE DISCARDABLE' then r = '' return r Resources and Decompiling Them - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ file ═══ file This function creates a new file, with extension arg(1), and fill it with cb bytes of infile. file: /* write cb bytes to res_xxx.arg(1) */ r = 'res_'right(fnum,4,'0')'.'arg(1) call charout r,charin(infile,,cb) fnum = fnum+1; cb = 0 call stream r,'c','close' return r Resources and Decompiling Them - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ strout ═══ strout This function extracts a string/messagetable definitions, and returns a string containing the table. strout: /* extract strings definitions */ call skip 2 id = (id-1)*16; cb = cb-2; r = nl do while cb > 0 len = x2d(c2x(charin(infile,,1))) if len > 1 then r = r' 'left(id,8)'"'charin(infile,,len-1)'"'nl call skip 1 id = id+1; cb = cb-len-1 end /* do */ return r Resources and Decompiling Them - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ keyout ═══ keyout This functions extracts an acceltable definition, and returns a string containing the acceltable. keyout: /* extract acceltable definitions */ procedure expose nl cb infile outfile cnt = readw() cp = readw() cb = cb-4 if cp \= 850 then call emit arg(1)'CODEPAGE 'cp||nl do cnt typ = readw() key = readw() if \ bit(typ,15) & key >= 32 & key <= 255 then key = '"'d2c(key)'"'; else key = '0x'd2x(key) cmd = readw() cb = cb-6; t = '' if bit(typ,16) then t = t', CHAR' if bit(typ,15) then t = t', VIRTUALKEY' if bit(typ,14) then t = t', SCANCODE' if bit(typ,13) then t = t', SHIFT' if bit(typ,12) then t = t', CONTROL' if bit(typ,11) then t = t', ALT' if bit(typ,10) then t = t', LONEKEY' if bit(typ, 8) then t = t', SYSCOMMAND' if bit(typ, 7) then t = t', HELP' call emit ' 'left(key',',8)left(cmd',',8)substr(t,3)nl end /* do */ return '' Resources and Decompiling Them - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ htout ═══ htout This function returns a string containing the HELPTABLE definition. htout: /* extract helptable definitions */ r = nl i = readw() do while i \= 0 r = r' HELPITEM 'i', 'readw() call skip 2 r = r', 'readw()nl; cb = cb-8 i = readw() end /* do */ cb = cb-2 return r Resources and Decompiling Them - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ hstout ═══ hstout This function returns a string containing the HELPSUBTABLE definition. hstout: /* extract helpsubtable definitions */ sis = readw() if sis \= 2 then r = nl'SUBITEMSIZE 'sis; else r = '' r = r||nl'BEGIN'nl; cb = cb-2 i = readw() do while i \= 0 r = r||' HELPSUBITEM 'i do sis-1; r = r', 'readw(); end cb = cb-2*sis; r = r||nl i = readw(); end /* do */ cb = cb-2 return r'END' Resources and Decompiling Them - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ itemout ═══ itemout This functions emits the current menu item. itemout: /* extract menu item definition */ procedure expose nl cb infile outfile cb = cb-6; s = ''; a = ''; r = arg(1)'MENUITEM "'; x = '| MIS_'; y = '| MIA_' sty = readw() att = readw() iid = readw() if \ (bit(sty,13) | bit(sty,14)) then do c = charin(infile); cb = cb-1 if c = 'FF'x & bit(sty,15) then do; r = r'#'readw(); cb = cb-2; end else do while c \= '00'x; r = r||c; c = charin(infile); cb = cb-1; end end if bit(sty,15) then s = s x'BITMAP' if bit(sty,14) then s = s x'SEPARATOR' if bit(sty,13) then s = s x'OWNERDRAW' if bit(sty,12) then s = s x'SUBMENU' if bit(sty,11) then s = s x'MULTMENU' if bit(sty,10) then s = s x'SYSCOMMAND' if bit(sty, 9) then s = s x'HELP' if bit(sty, 8) then s = s x'STATIC' if bit(sty, 7) then s = s x'BUTTONSEPARATOR' if bit(sty, 6) then s = s x'BREAK' if bit(sty, 5) then s = s x'BREAKSEPARATOR' if bit(sty, 4) then s = s x'GROUP' if bit(sty, 3) then s = s x'SINGLE' if bit(att,11) then a = a y'NODISMISS' if bit(att, 4) then a = a y'FRAMED' if bit(att, 3) then a = a y'CHECKED' if bit(att, 2) then a = a y'DISABLED' if bit(att, 1) then a = a y'HILITED' if a \= '' then a = ','substr(a,3) if s \= '' then s = ','substr(s,3); else if a \= '' then s = ',' call emit r'", 'iid||s||a||nl if bit(sty,12) then do; call emit arg(1)'BEGIN'nl; call emit menuout(arg(1)' ','')arg(1)'END'nl; end return Resources and Decompiling Them - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ menuout ═══ menuout This functions emit the current menu or submenu. menuout: /* extract menus definitions */ procedure expose nl cb infile outfile cb = cb-10; cbs = readw() typ = readw() cp = readw() off = readw() cnt = readw() if arg(2) \= '' then do if cp \= 850 then call emit 'CODEPAGE 'cp||nl call emit arg(2) end /* do */ do cnt; call itemout arg(1); end return '' Resources and Decompiling Them - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 2.3. Visual REXX Faceoff ═══ ═══ 2.3.1. Introduction ═══ Visual Rexx Faceoff Written by Gordon Zeglinski Introduction This issue sees the first part of the Visual Rexx Faceoff. We start by looking at VX-REXX. The Watcom VX-REXX package includes two 3.5" HD floppies and a 700+ page manual. The manual is nicely written, but the reference section is slightly hard to use. (Fortunately, one doesn't have to use it!) The first few chapters are instructions on how to do things, the rest of the manual is all reference. There are plenty of sample programs included. Visual Rexx Faceoff - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 2.3.2. Installation ═══ To install VX-REXX, you need about 5-6 megs of free disk space. The install program is pretty simple and non-intrusive. It fits in nicely with the OS/2 environment. There's nothing I hate more than these "ego-maniac" install programs that go out of their way to make you sit and watch them install. Fortunately, this package doesn't have one of those. After answering a few questions, popping in the two disks, and rebooting, we have the following folder on our desktop. Figure 1. Installation Folder Visual Rexx Faceoff - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 2.3.3. Look and Feel ═══ The interface is neat and easy to use. It consists of the window you are designing and a tool palette, as shown in figure 2. Figure 2. User Interface VX-REXX uses SOM to implement its "tools". Each of the objects in the Tools window (except for the pointer) is a SOM object. Note, just because VX-REXX is based in SOM does not mean it's a WPS app, which is not a bad thing. VX-REXX does mimic the feel of the WPS. Each object has a pop-up menu that can be used to change the various properties of the object you have clicked on. Figure 2 shows the popup for the main window; other objects have similar menus. Figure 3 shows the properties notebook for a static text object. Figure 3. Properties Notebook Overall, if you are used to the WPS, you will quickly learn how to configure and create objects. We now get to the best part of the interface. I found the reference section of the manual a bit difficult to use. It's hard to find answers to questions like "How do I put text into the damn listbox?!". Fortunately, you don't have to look in the manual for this. VX has a code insertion ability. To use this ability, you bring up a context menu, select "Code Insert", then select the action you want the code to perform. The inserted code will usually require some editing. In addition to the popup menu method of code insertion, the user can drag a control from the window they are designing and drop it on the code window. VX-REXX will then prompt the user for the action to perform, after which the code necessary to perform the action is inserted. Visual Rexx Faceoff - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 2.3.4. Wrapping Things Up ═══ I have found one annoying thing/bug in VX, but before I comment on it, I will wait for a response from Watcom tech support. Next issue, we'll look at VisPro REXX (from HockWare) and see how VX-REXX compares to it. Visual Rexx Faceoff - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 3. Columns ═══ The following columns can be found in this issue: o /dev/EDM/BookReview o C++ Corner o Introduction to PM Programming o Scratch Patch Columns - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 3.1. /dev/EDM/BookReview ═══ ═══ 3.1.1. Introduction ═══ /dev/EDM2/BookReview Written by Carsten Whimster Introduction /dev/EDM2/BookReview is a monthly column which focuses on development oriented books and materials. The column is from a beginning PM programmer's eyes, because that's what I am. Try to pick up whichever book strikes your fancy, and join the growing group of people following our introductory PM programming columns. I will review books aimed at beginners for a while, and then move on from there. Please send me your comments and thoughts so that I can make this column as effective as possible. After all, this is our magazine, and it will be most effective with reader feedback. This book is the last of my current collection, and even though it was written for OS/2 1.3, and thus only covers the 16-bit functions, it still manages to give decent coverage to concepts which are 16/32 bit insensitive. /dev/EDM/BookReview - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 3.1.2. Errata ═══ Errata There are three things on my agenda this month. First of all, I got mail from a couple of people who disagreed with my rosy evaluation of Writing OS/2 2.1 Device Drivers in C, 2nd Edition, Mastrianni. I guess I should have made a couple of things a little clearer. If you are to use the book, you need Mastrianni's library, unless you write your own DevHlp functions, and the library costs around $150 US. Secondly, the reason I feel strongly about his book is that Mastrianni has done a lot of work abstracting device driver writing away from naked assembler, giving the opportunity to many more people to write device drivers than otherwise would have attempted it. This is the main accomplishment, I think. In any case, you must judge for yourself whether it is worth it. If you do decide to write a device driver, you'll most likely need the device driver kit from IBM, but from what I have heard, it is in dire need of cleaning up. It is supposedly messy, expensive, and incomplete. IBM would really do us all a huge favour by putting out a better kit, and in fact, they may be headed in that direction. Supposedly Mastrianni has been hired by IBM, and I presume he'll be working on their kit development team. If anyone has any factual information on this development, I'd love to hear it. One can always hope. In any case, the book will help you get off to a faster, surer start than if you were to try it on your own. Secondly, I have had a report of an inaccuracy in Real World Programming for OS/2 2.1, Blain, Delimon, and English. Here is the deal, courtesy of Gordon Zeglinski: on page 440, the following occurs: !DosSubSet(pHeap,DOSSUB_SPARSE_OBJ|DOSSUB_SERIALIZE)) If we look in the header file however, we find #define DosSubSet DosSubSetMem #define DOSSUBSET DosSubSetMem APIRET APIENTRY DosSubSetMem(PVOID pbBase, ULONG flag, ULONG cb); So the code fragment does not even have the correct number of arguments. It will not compile without a warning in C, and will not compile at all under C++. Look at Gordon's C++ Queue Object column for the proper way to use this API. Finally, from what I have heard, the new Watcom C/C++ 10.0 is imminent, and from reviews of the beta, it is something. It will have an IDE, and an assembler (yes!), on top of the usual stuff, and of course will include the new 2.1 toolkit. If you are about to buy a C/C++ compiler, wait until this comes out before you decide which one to spend your hard-earned money on. It looks like IBM may finally get a run for their money in the high-end compiler-department. As a foot-note, I am working (with a little help from my friends here at EDM/2) on a tabular content format for what each book covers, and what it does not cover. Exiting stuff, and this may be just what you have been waiting for to be able to decide which book has what you need. Thanks to Gordon (again) for the great idea. /dev/EDM/BookReview - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 3.1.3. OS/2 Presentation Manager GPI ═══ OS/2 Presentation Manager GPI Sample code fans will like the layout of this book. In some ways, it is quite similar to Real World Programming for OS/2 2.1. Each chapter starts out with a small motivating section, followed by detail on how to actually apply the concepts being presented. The sample code follows at the end of the chapter, and consists of complete snippets of code, with some meaningful function, although whole programs are not included. This is in keeping with the author's stated objective of assisting the intermediate to advanced PM programmer in becoming a proficient GPI programmer. With its 318 pages, the book is fairly substantial, considering that it only covers one major topic. Here are the chapter headings: 1. Introduction: Simple Text and Graphics Output 2. Presentation Spaces and Device Contexts 3. Drawing Primitives and Attributes 4. Fonts 5. Bitmaps 6. Color Tables 7. Coordinate Spaces and Transformation 8. Clipping and Regions 9. Orders, Elements, and Segments 10. Correlation and Boundary Data Accumulation 11. MetaFiles 12. Printing 13. The OS/2 2.0 32-Bit Operating System 14. Appendix 1: DevOpenDC Parameters 15. Appendix 2: PMPRINT Queue Processor Parameters 16. Appendix 3: Introduction to Transforms and Matrices 17. Appendix 4: Sample MetaFile Internals 18. Appendix 5: Sample Orders 19. Appendix 6: GPI Functions Supported only by a Normal-PS Before using this book, you should already know how to set up a skeleton program, with message queue, window procedure, and all the rest of the trimmings. This book ONLY talks about the GPI and a few Dev functions. The first chapter introduces the methodology and intent of the GPI APIs. Brief explanations of device contexts, presentation spaces, and so on are presented. Personally, I would have liked a section explaining the design choices made in the GPI APIs, but that doesn't hurt the coverage of the book. One thing I did miss, though, was a little humour. Like so many programming books, humour is conspicuous by its absense. I am not calling for a Monty Python-esque treatment of the material, just a little light-hearted jesting. This would make it more enjoyable, and less dry. Oh well, it's not the first time I have missed that, and it won't be the last. The general feeling of the subsequent chapters is one of a programmer who knows his field very well, but has a lot to cover. Each sentence has terse information to present, and it never lets up. This is not a book you sit down and read in a couple of days! Every sentence has to be thought through after reading it! You probably have to try the code from each chapter in your own applications before really understanding what's going on. Chapter two lays the ground-work for all GPI programming. Device contexts and presentation spaces are explained again in much greater detail than the first time. There are limitations to each choice of PS, and the correct type of DC must be used. This seems logical at first sight, but can be complicated when your requirements are not clearly delineated. Unfortunately, the diagrams used in the book look like something the original word-processors might have produced, not very professional. Not good for a book on presentation graphics and printing. The various drawing primitives and attribute groups, characters and text, lines and curves, filled areas or patterns, markers, and image and bitblt pixel operations are introduced in depth, and their relationship to each other are explained. Non-bundle attributes are explained next. Many functions and their related possibilities are explained. Each of chapters four to eleven introduces its own area, and finally in chapter twelve, the particular problems of printing are demonstrated. I did feel that chapter five on bitmaps was short, given the disproportionate amount of interest this particular topic usually has. There are a lot of gotcha's in GPI programming, and a lot of little rules you have to be aware of. Most people will not really use that much of the GPI, except to output a text-string here and there, and for these people, this book is overkill. It is not intended as a reference book, there are other books for that purpose. This book is intended, in the author's own words "...to provide answers (illustrated by programming examples) to any questions that Application Developers may have concerning the GPI and Dev functions. Rather than providing a tutorial description, I have concentrated on making the description of each topic as comprehensive as possible." The end result is a book which is similar to a reference in coverage, but more like a tutorial in presentation. My final impression is a good one, even if the book is a little dry. It concentrates on presenting OS/2's way of programming with graphics, not on teaching the un-initiated about graphics programming. The one major short-coming is that it was written for 16-bit OS/2 1.3. I find it strange that the book has not been re-written for OS/2 2.x, but perhaps the sales of this book were disappointing because of the slowness with which 1.3 caught on. Now is a different story, though. With over 5 million copies of OS/2 sold at last count, we need an updated version of this book. My final evaluation of this book reflects this. /dev/EDM/BookReview - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 3.1.4. Summary and Ratings ═══ Summary and Ratings This book ought to be updated. There aren't many books on the market specifically about the GPI, and with the current boom going on with OS/2 2.1, 2.11, warp/personal OS/2, and so on, it would be perfect timing to have a new edition now. The book is terse, well-written and accurate, but needs descriptions of the 32-bit functions, the new functions, and how to interface the two with 16-bit functions. ┌────────────────────────────────────┬────────────┬────┬─────────────────────────────────────────────────┐ │BOOK │AUDIENCE │MARK│COMMENTS │ ├────────────────────────────────────┼────────────┼────┼─────────────────────────────────────────────────┤ │Real World Programming for OS/2 2.1,│Intermediate│B+ │Lots of good code examples, but sometimes it is │ │Blain, Delimon, and English, SAMS │to Advanced │ │too complex for novices. Accurate. Well │ │Publishing. ISBN 0-672-30300-0. │PM C │ │organized. The index needs a little beefing up. │ │US$40, CAN$50. │programmers │ │Good, but not entirely complete how-to reference.│ │ │ │ │Good purchase. │ ├────────────────────────────────────┼────────────┼────┼─────────────────────────────────────────────────┤ │Learning to Program OS/2 2.0 │Beginning PM│B- │This book can be both frustrating and very │ │Presentation Manager by Example, │C │ │rewarding. It is not very large, and a bit │ │Knight, Van Nostrand Reinhold. ISBN │Programmers │ │pricey, but has some excellent chapters on │ │0-442-01292-6. US$40, CAN$50. │ │ │beginning topics, such as messages, resources, │ │ │ │ │IPF, and dialog boxes. Strictly for beginners. │ ├────────────────────────────────────┼────────────┼────┼─────────────────────────────────────────────────┤ │Writing OS/2 2.1 Device Drivers in │Advanced C │A- │The only thing a device driver programmer would │ │C, 2nd Edition, Mastrianni, Van │Programmers,│ │not find in here is how to write SCSI, ADD, and │ │Nostrand Reinhold. ISBN │familiar │ │IFS drivers. Most everything else is in here, │ │0-442-01729-4. US$35, CAN$45. │with │ │along with skeleton examples. An optional DevHlp│ │ │hardware │ │library of C-callable functions can be purchased │ │ │programming │ │by those who don't have time to write their own. │ ├────────────────────────────────────┼────────────┼────┼─────────────────────────────────────────────────┤ │OS/2 Presentation Manager GPI, Winn,│Intermediate│C+ │This book needs updating for OS/2 2.x. It is a │ │Van Nostrand Reinhold. ISBN │to advanced │ │well-written in-depth coverage of the OS/2 way of│ │0-442-00739-6. US$35, CAN$45. │PM C │ │programming for graphics. It is not an │ │ │programmers │ │introductory PM or graphics programming book. │ │ │ │ │You should know how to create windows etc. │ └────────────────────────────────────┴────────────┴────┴─────────────────────────────────────────────────┘ This table contains all books I have reviewed, so that you can find what you are looking for at a glance. I will be careful to rate books fairly relative to each other. 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. Please note that books aimed at different audiences should only be compared with great care, if at all. I intend to concentrate on the strong points of the books I review, but I will point out any weaknesses in a constructive manner. Read the reviews carefully. BOOK: The name of the book, author(s), publishing company, ISBN, and approximate price. 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, accuracy, organization, readability, and quality of index all weigh heavily here, but the single most important item is how well the book covers what it says it covers. 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 │ ├───┼─────────────────────────────────────────────────────────────────────────────┤ │B+ │Very good book with minor flaws or omissions │ ├───┼─────────────────────────────────────────────────────────────────────────────┤ │B │Good book with some flaws and omissions │ ├───┼─────────────────────────────────────────────────────────────────────────────┤ │B- │Good book, but in need of improvement │ ├───┼─────────────────────────────────────────────────────────────────────────────┤ │C+ │Mediocre book with some potential, but in need of some updating │ ├───┼─────────────────────────────────────────────────────────────────────────────┤ │C │Mediocre book with some good sections, but badly in need of fixing │ ├───┼─────────────────────────────────────────────────────────────────────────────┤ │C- │Mediocre book, little good material, desperately in need of an overhaul │ ├───┼─────────────────────────────────────────────────────────────────────────────┤ │D │Don't buy this book unless you need it, and nothing else exists │ ├───┼─────────────────────────────────────────────────────────────────────────────┤ │F │Don't buy this book. Period │ └───┴─────────────────────────────────────────────────────────────────────────────┘ COMMENTS: This is a summary of the review proper, although in a very brief format. /dev/EDM/BookReview - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 3.1.5. Coming Up ═══ Coming Up Next month I will be looking at The Art of OS/2 C Programming, Panov, Salomon and Panov, if it gets here in time (sounds very familiar :). Otherwise I will review some REXX book. The books I intend to review are (not necessarily in this order): o The Art of OS/2 C Programming, Panov, Salomon and Panov o OS/2 Presentation Manager Programming, Petzold - 1994 - not yet published :( o The Design of OS/2, 2nd Edititon, Kogan and Deitel - 1994 - not published yet? I will review the old version if someone sends me one, but if I have to buy it I will wait for the new edition. This list is not set in stone, but they are books I am interested in. I am considering reviewing the IBM OS/2 Redbooks, since they are readily and cheaply available, and look like good introductory reference. I am also 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? And what is different about the Special Edition? Finally, I am considering reviewing Designing OS/2 Applications, Reich, mostly because it promises to present a different angle on OS/2 programming, namely that of how to design OS/2 applications, rather than how to program OS/2 applications. If anyone has a book they want to see reviewed, I will be happy to oblige as long as I can afford it. Of course, requests can be satisfied quicker when accompanied by a book. :) Publishers can send me books at the address on my personal page at the end of the magazine, and I will review all OS/2 development-related books I receive. /dev/EDM/BookReview - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 3.2. C++ Corner ═══ ═══ 3.2.1. Introduction ═══ C++ Corner Written by Gordon Zeglinski Introduction This issue's C++ column is in response to a reader's email. Things are extremely hectic, so sometimes email doesn't get answered as promptly as I would like it to be. Keep the mail coming, but please be patient; I'll answer it as soon as I can. In this issue, we will look at multi-threading using the IThread class. The IThread class is part of the ICLUI shipped with C-Set++ 2.0 and 2.1; however, we will be building upon the drag and drop example of last issue, which requires C-Set++ 2.1. C++ Corner - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 3.2.2. The IThread Class ═══ The IThread Class The IThread class is used to manipulate threads in the ICLUI. An instance of IThread and the thread it represents are not bound together. If the thread terminates, the instance is not destroyed. If the instance is destroyed, the thread continues executing. Starting a Thread with IThread There are two methods that can be used to start a thread. The first, is to use the one of the constructors that take a function address or a reference to an instance of IThreadFn as an argument. The second is to use the "start member" function. We will look at both methods here. The following code snippet illustrates both methods of starting a thread. In this example, we use a member function of the class foo as the threads starting function. class foo{ public: foo(); void fooThread(); }; void main(){ //Create an instance of the foo class foo fooInst; // create the member thread dispatch object IThreadMemberFn ThreadFnc(fooInst,&foo::fooThread); // create a reference to the member thread dispatch object IReference ThreadFncRef(&ThreadFnc); // create a thread object and start the thread IThread ImmediateDispatch(ThreadFncRef); // create a thread object IThread DelayedDispatch; /* Do some work here */ DelayedDispatch.start(ThreadFncRef); } The class IThreadMemberFn holds both an instance of a class and a pointer to a member function of that class. These two pieces of information are need to start the thread using the correct member function and instance. Note, that since IThreadMemberFn is a template class, it can be used with any class. In this example, we only looked at starting threads on member functions. The method of starting threads on non-member functions is almost identical to the one outlined above. Thus, it is left up to the reader to explore this further. C++ Corner - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 3.2.3. Building a Simple Test Application ═══ Building a Simple Test Application This section should look familiar to those who have read the last issue. We will take our application from last issue, and move the file loading routine into a separate thread. In this section, we will look at only the modifications made to last issues code that are needed to accomplish our goal here. The class definition for MyFrame has been expanded to allow multi-threaded loading of files into the MLE. class MyFrame:public IFrameWindow{ public: MyFrame(const char *Title); void StartLoadThread(); //Added to start thread void SetFileName(IString &S){FileName=S;} //Added to set the filename protected: void LoadFile(); //Added: load thread start point // New members to support multi-threaded loading IReference ThreadFncRef; IThreadMemberFn *ExecuteThreadFnc; IThread ExecuteThread; IString FileName; IMultiLineEdit EditWin; AFileProvider FileProvider; }; Added to the version of MyFrame shown above, are several several new member functions that handle the multi-threaded file loading. The constructor is modified as follows to initialize the new data members. MyFrame::MyFrame(const char *Title): IFrameWindow(Title,IResourceId(1), //------------------- IFrameWindow::titleBar| // IFrameWindow::sizingBorder| // IFrameWindow::minimizeButton| // Create the Frame IFrameWindow::systemMenu| // Window IFrameWindow::shellPosition| // IFrameWindow::minimizeButton| // IFrameWindow::windowList| // IFrameWindow::maximizeButton), //-------------------- EditWin(10,this,this){ // Create the Edit Window // ID=10, use this frame // window as the parent and //owner //--------------------- setIcon(IResourceId(1)); setClient(&EditWin); //enable default drag and drop handler IDMHandler::enableDragDropFor(&EditWin); //attach the provider EditWin.setItemProvider(&FileProvider); //Create the thread support members. ExecuteThreadFnc=new IThreadMemberFn(*this, &MyFrame::LoadFile); ThreadFncRef=IReference(ExecuteThreadFnc); show(); } The targetDrop routine is modified so that it uses the member functions we added to MyFrame to set the file name and start the thread which will read in the file. In the following code, we use the fact that the parent of the MLE is an instance of MyFrame to get the instance of the MyFrame window and call the thread creation function. Boolean AFileItem::targetDrop( IDMTargetDropEvent &Event){ IMultiLineEdit *DropWin=(IMultiLineEdit *)this->targetOperation()->targetWindow(); IString fname = this->containerName() + this->sourceName(); MyFrame *FrameWin=(MyFrame*) DropWin->parent(); FrameWin->SetFileName(fname); FrameWin->StartLoadThread(); return true; } Now that we've seen how the old code was modified, we look at the two new member functions of MyFrame. These functions don't do anything that we haven't already seen. void MyFrame::StartLoadThread(){ ExecuteThread.start(ThreadFncRef); } void MyFrame::LoadFile(){ //erase the edit window EditWin.removeAll(); //load the file into the edit window EditWin.importFromFile(FileName); } The source code to this issue is packaged the same way as the source code in the previous issue. C++ Corner - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 3.2.4. Summary ═══ Summary In this issue, we have seen how to use IThread and its supporting classes to create a multi-threaded PM application by building upon our drag and drop example from last issue. C++ Corner - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 3.3. Introduction to PM Programming ═══ ═══ 3.3.1. Introduction ═══ Introduction to PM Programming Written by Larry Salomon, Jr. Introduction The purpose of this column is to provide the readers out there who 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. Last Month Last month, we explored more of the PM APIs and began looking into the WC_ENTRYFIELD window class. This month, we will continue where we left off, and will continue in directions unknown; unknown, that is, unless you continue reading. Introduction to PM Programming - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 3.3.2. More Messages ═══ More Messages Let us first begin by looking at the remaining entryfield messages. EM_CLEAR This message is sent to delete the selected text in the entryfield. Parameters param1 ulReserved (ULONG) Reserved, 0. param2 ulReserved (ULONG) Reserved, 0. Returns reply bSuccess (BOOL) TRUE successful completion FALSE an error occurred. EM_COPY This message is sent to copy the selected text to the clipboard. Parameters param1 ulReserved (ULONG) Reserved, 0. param2 ulReserved (ULONG) Reserved, 0. Returns reply bSuccess (BOOL) TRUE successful completion FALSE an error occurred. EM_CUT This message is sent to copy the selected text to the clipboard and then delete it. This is equivalent to sending an EM_COPY message followed by an EM_CLEAR message. Parameters param1 ulReserved (ULONG) Reserved, 0. param2 ulReserved (ULONG) Reserved, 0. Returns reply bSuccess (BOOL) TRUE successful completion FALSE an error occurred. EM_PASTE This message is sent to paste text from the clipboard into the entryfield. If there is selected text, it is replaced with the pasted contents; otherwise, the text is inserted at the current cursor position. Parameters param1 ulReserved (ULONG) Reserved, 0. param2 ulReserved (ULONG) Reserved, 0. Returns reply bSuccess (BOOL) TRUE successful completion FALSE an error occurred. EM_SETFIRSTCHAR This message is sent to set the zero-based index of the first character visible in the entryfield. Parameters param1 sFirstChar (SHORT) 0-based index of the first visible character param2 ulReserved (ULONG) Reserved, 0. Returns reply bSuccess (BOOL) TRUE successful completion FALSE an error occurred. EM_SETINSERTMODE This message is sent to set the insert mode of the entryfield. Parameters param1 bInsert (BOOL) TRUE insert mode FALSE overwrite mode param2 ulReserved (ULONG) Reserved, 0. Returns reply bOldState (BOOL) TRUE the entryfield was previously in insert mode FALSE the entryfield was previously in overwrite mode EM_SETREADONLY This message is sent to set the read-only state of the entryfield. Parameters param1 bReadOnly (BOOL) TRUE the entryfield should be read-only FALSE the entryfield should be editable param2 ulReserved (ULONG) Reserved, 0. Returns reply bOldState (BOOL) TRUE the entryfield was previously read-only FALSE the entryfield was previously editable EM_SETSEL This message is sent to set the current selection of the entryfield. Parameters param1 sMinSel (SHORT) The first 0-based point of selection sMaxSel (SHORT) The last 0-based point of selection param2 ulReserved (ULONG) Reserved, 0. Returns reply bSuccess (BOOL) TRUE successful completion FALSE an error occurred. EM_SETTEXTLIMIT This message is sent to set the maximum number of characters the entryfield can contain. Parameters param1 usLimit (USHORT) maximum number of characters allowed param2 ulReserved (ULONG) Reserved, 0. Returns reply bSuccess (BOOL) TRUE successful completion FALSE an error occurred. Introduction to PM Programming - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 3.3.3. Conceptually Speaking ═══ Conceptually Speaking Of Selections, Anchor Points, and Cursor Points What is a selection? A selection is an area that is defined by the user. Any "objects" (defined by the context of the selection) that are contained within the selected area are defined to be selected. Any selection consists of two things: an anchor point, which is defined to be the point where the selection was started and is fixed, and a cursor point, which is defined to the be place where the cursor/pointer currently is and moves with the cursor/pointer. A selection is performed, according to CUA guidelines, using either the mouse or the keyboard. Using the mouse requires the user to press one of the buttons (usually the first) and hold the button while moving the mouse. When the selection has been made, the button is released. Using the keyboard, the user presses the Shift key while moving the cursor with the arrow keys. When the Shift key is released, the selection is completed. There are other semantics associated with selections when using the mouse, but we will not discuss them here. The point of these definitions is to allow you to understand the purpose of the EM_SETSEL message. Its two parameters are slightly different that what was discussed above; if the anchor point is always before the cursor point ("before" is used, since the entryfield can be considered as being a one-dimensional stream of characters), then sMinSel is the anchor point and sMaxSel is the cursor point, otherwise the two are reversed. Clipboard For those of you who have no GUI experience, the clipboard is a temporary storage place for placing items to be used later in the same or other applications. Items of predefined, or application-defined formats, may be placed on the clipboard. Its capabilities and interfaces are beyond the scope of this discussion, but it is important that you know what it is. Text Limits When you create an entryfield, by default it allows up to 32 characters maximum to be entered. If, however, you need more (or less) than this amount, you must first send it an EM_SETTEXTLIMIT message, which tells the entryfield to allocate more or less memory for its use. Introduction to PM Programming - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 3.3.4. Notifications ═══ Notifications The entryfield also sends notifications to its owner. These notifications are listed below. o EN_CHANGE - this is sent whenever the entryfield's contents have changed. o EN_KILLFOCUS - this is sent whenever the entryfield is losing the input focus. o EN_MEMERROR - this is sent whenever the entryfield cannot allocate memory in response to an EM_SETTEXTLIMIT message. o EN_OVERFLOW - this is sent whenever an attempt to insert or paste more text than is allowed by the text limit is made. o EN_SCROLL - this is sent whenever the entryfield has scrolled from a call to WinScrollWindow(), the cursor moving beyond the visible area, or when the text has changed. o EN_SETFOCUS - this is sent whenever the entryfield is gaining the input focus. The notifications you will use the most often are the EN_CHANGE, EN_SETFOCUS, and EN_KILLFOCUS ones, although the others have their uses. Introduction to PM Programming - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 3.3.5. Summary ═══ Summary This month, we finished looking at the entryfield messages, the notifications sent by the entryfield, and the concepts associated with the new messages. Next month, we will begin looking at the WC_LISTBOX class, and will continue with nameDlgProc(). Introduction to PM Programming - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 3.4. Scratch Patch ═══ ═══ 3.4.1. Introduction ═══ Scratch Patch Written by Larry Salomon, Jr. Introduction 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). Scratch Patch - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 3.4.2. Corrections ═══ Corrections If you remember, last month I stated that APAR number PJ13781 was created against the Palette Manager; well, it has been closed. The reason is, if you'll look in the source code (ugpm.zip), you'll see that a call to WinGetPS() is made without 1) creating a message queue first and 2) without releasing it later. This is causing the instabilities that were reported. My apologies for not figuring this out before, but I never looked at the source. Scratch Patch - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 3.4.3. Gotcha Notes! ═══ Gotcha Notes! When using the IBM User Interface Class Libraries, if you use the new operator to create an instance of your main window in the main() function, you must use the delete operator after the IApplication::current().run() call or else the application will not close after the main window has been destroyed. Scratch Patch - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 3.4.4. Snippet(s) of the Month ═══ Snippet(s) of the Month "When it rains, it pours." Eberhard Mattes (mattes@azu.informatik.uni-stuttgart.de) submitted the following item: This is the template for a dialog box that contains a system icon. Note that DLGEDIT doesn't know how to handle SS_SYSICON. DLGTEMPLATE 3141 BEGIN DIALOG "A System Icon", 3141, 20, 20, 200, 64, , FCF_TITLEBAR | FCF_SYSMENU BEGIN CONTROL SPTR_ICONQUESTION, 0, 90, 28, 0, 0, WC_STATIC, WS_VISIBLE | SS_SYSICON DEFPUSHBUTTON "OK", 1, 8, 8, 32, 16 END END ──────────────────── Jeff Garzik (jgarzik@pantera.atl.ga.us) submitted the following item: /* * DosStartSession() sample code for a DOS session * by Jeff Garzik (jgarzik@pantera.atl.ga.us) * * It has been tested under BC++ 1.0 and emx+gcc 0.8h. */ #define INCL_DOS /* we're lazy */ #include #include #include #include #include int main (int argc, char *argv[]) { STARTDATA sd; /* DosStartSession parm structure */ PID pid = 0, pid2 = 0; /* unused but necessary */ ULONG SessID; /* "" */ RESULTCODES rcResults; /* "" */ APIRET rc; /* OS/2 API return code */ /* the program we want to run */ char s[128] = "c:\\os2\\mdos\\qbasic.exe", /* the parms of the program to be exec'd */ s1[128] = "/EDITOR c:\\autoexec.bat", /* MS-DOS settings of program */ DosSettings[200] = "DOS_BACKGROUND_EXECUTION=0\0" "DPMI_MEMORY_LIMIT=0\0" "EMS_MEMORY_LIMIT=0\0" "HW_TIMER=1\0" "IDLE_SECONDS=10\0" "IDLE_SENSITIVITY=20\0" "XMS_MEMORY_LIMIT=0\0" "\0"; /* fill in data for DosStartSession() */ memset((void *)&sd, 0, sizeof(sd)); sd.Length = sizeof(STARTDATA); sd.Related = SSF_RELATED_CHILD; sd.FgBg = SSF_FGBG_FORE; sd.TraceOpt = SSF_TRACEOPT_NONE; sd.PgmTitle = "My First DOS Sub-Process"; /* not really necessary */ sd.PgmName = s; sd.PgmInputs = s1; sd.TermQ = NULL; /* * The two following settings are somewhat different for * DOS sessions. You CANNOT set the environment of a * DOS session. The Environment field in STARTDATA is * instead used for the DOS settings. InheritOpt is really * irrelevant because DOS sessions get their environment * from the settings in DOS_AUTOEXEC (defaults to C:\AUTOEXEC.BAT) * exclusively */ sd.Environment = DosSettings; /* or 0 for no settings */ sd.InheritOpt = SSF_INHERTOPT_SHELL; /* set to SFF_TYPE_WINDOWEDVDM for windowed session */ sd.SessionType = SSF_TYPE_VDM; sd.IconFile = 0; sd.PgmHandle = 0; sd.PgmControl = 0; sd.InitXPos = 0; /* unused for fullscreen session */ sd.InitYPos = 0; /* unused for fullscreen session */ sd.InitXSize = 0; /* unused for fullscreen session */ sd.InitYSize = 0; /* unused for fullscreen session */ sd.Reserved = 0; sd.ObjectBuffer = NULL; sd.ObjectBuffLen = 0; if ((rc = DosStartSession(&sd, &SessID, &pid)) != 0) printf("\nDosStartSession error %ld\n\n", rc); else printf("Successfully executed DOS Editor on AUTOEXEC.BAT.\n"); DosWaitChild(DCWA_PROCESS, DCWW_WAIT, &rcResults, &pid2, pid); printf("\nPress any key to continue..."); getch(); putchar('\n'); return 0; } ──────────────────── Someone sent me the following in a uuencoded format, and I forgot to save their name and email address! Sounds familiar? :) #define INCL_DOS #define INCL_DOSDEVIOCTL #define INCL_ERRORS #include #include /*+-------------------------------------------------------------------+ |Function QueryCommPorts | | | |Purpose : | | Determine nr. of commports available on the PC. | | Try to figure out which port is used by the mouse. | |Inputparameters : | | *cCommPorts : pointer to a byte to place the number of available| | comm ports | | bVerbose : bool that indicates whether or not a status report| | must be displayed | |Outputparameters : | | cMousePort : byte that contains the nr of the comm port used by | | the mouse | +-------------------------------------------------------------------+*/ BYTE QueryNrCommPorts( BYTE *cCommPorts, BOOL bVerbose ) { BYTE cMousePort = -1; BOOL bMouse = FALSE; HFILE hfMouse; ULONG ulAction; APIRET rc; struct { USHORT DeviceID; USHORT CommPort; USHORT SecDeviceID; } DataWords; ULONG ulDataLength = sizeof( DataWords ); CHAR szDeviceID[6][30] = { "Unknown", "Bus mouse", "Serial Mouse", "Inport mouse", "PS/2*-style pointing device", "IBM* 8516 Touch Display" }; /*+----------------------------------------------+ | Determine the number of comm ports on the PC | +----------------------------------------------+*/ DosDevConfig( cCommPorts, DEVINFO_RS232 ); /*+---------------------------------------------+ | Determine nr of comm port used by the mouse | +---------------------------------------------+*/ if ( DosOpen( "MOUSE$", &hfMouse, &ulAction, 0, FILE_SYSTEM, OPEN_ACTION_OPEN_IF_EXISTS, OPEN_ACCESS_READONLY | OPEN_SHARE_DENYNONE, 0 ) == NO_ERROR ) { rc = DosDevIOCtl( hfMouse, IOCTL_POINTINGDEVICE, 0x6B, /* Query Pointing Device ID */ NULL, 0L, NULL, (PVOID) &DataWords, ulDataLength, &ulDataLength ); if ( rc != NO_ERROR ) { printf( "\nError while querying mouse info.\n" ); goto end; } else { bMouse = TRUE; cMousePort = DataWords.CommPort; } } /*+-------------------+ |Print status report| +-------------------+*/ if ( bVerbose ) { printf( "\nSTATUS REPORT COMM PORTS\n\n" ); printf( "Comm ports available : %d\n", *cCommPorts ); if ( bMouse ) { printf( "Mouse detected.\n" ); printf( "\tType : %s\n", szDeviceID[ DataWords.DeviceID ] ); printf( "\tComm port : %d\n", DataWords.CommPort ); } } end: return ( cMousePort ); } ──────────────────── Finally, I came up with the following function. typedef VOID (* _Optlink PFNDRVENUM)(CHAR,PVOID); VOID enumDrives(PFNDRVENUM pfnEnumFn,PVOID pvData) //-------------------------------------------------- // This function enumerates all of the drives at the // time the function is called, and for each local // drive calls the callback specified. // // Input: pfnEnumFn - callback function // pvData - pointer to user-defined data to // pass to callback // // Callback has the following form: // // BOOL _Optlink callBack(CHAR chDrive,PVOID pvData) // // Input: chDrive - uppercase drive letter // pvData - as was passed to enumDrives() //-------------------------------------------------- { BYTE abBuffer[1024]; ULONG ulCurrent; ULONG ulDriveMap; CHAR chDrive; CHAR achDrive[3]; PFSQBUFFER2 pfsqbBuffer; ULONG ulSzBuf; APIRET arReturn; //----------------------------------------------- // Disable errors so that we don't get any "device // is locked" popups. //----------------------------------------------- DosError(FERR_DISABLEHARDERR | FERR_DISABLEEXCEPTION); DosQueryCurrentDisk(&ulCurrent,&ulDriveMap); ulDriveMap>>=2; chDrive='C'; memset(achDrive,0,sizeof(achDrive)); achDrive[1]=':'; pfsqbBuffer=(PFSQBUFFER2)abBuffer; while (chDrive<='Z') { if ((ulDriveMap % 2)==1) { achDrive[0]=chDrive; ulSzBuf=sizeof(abBuffer); memset(pfsqbBuffer,0,ulSzBuf); arReturn=DosQueryFSAttach(achDrive, 0, FSAIL_QUERYNAME, pfsqbBuffer, &ulSzBuf); if (arReturn==0) { switch (pfsqbBuffer->iType) { case FSAT_LOCALDRV: (*pfnEnumFn)(chDrive,pvData); break; default: break; } /* endswitch */ } /* endif */ } /* endif */ ulDriveMap>>=1; chDrive++; } /* endwhile */ DosError(FERR_ENABLEHARDERR | FERR_ENABLEEXCEPTION); } Scratch Patch - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 3.4.5. Documentation Chop Shop ═══ Documentation Chop Shop In the File Systems section of the Control Program Guide and Reference, there are a couple of problems. 1. The API DosQueryFileInfo() is referenced when DosQueryFSInfo() is intended. 2. The sample to retrieve file system information via DosQueryFSAttach() is incorrect. Declaring a variable of type FSQBUFFER2 and passing it to DosQueryFSAttach() will return ERROR_BUFFER_OVERFLOW. You must pass a pointer to a buffer (stack-based or dynamically allocated) that has sufficient space. ──────────────────── _wpPopulate is listed in the online reference as having a third parameter of WPFolder * and its value is the "real name of the folder to populate". In actuality, it is a PSZ which is the path of the folder to populate. Scratch Patch - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 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) - this is still quite the "black magic" topic, about which we have seen only one article. There are many out there who do WPS programming for a living, but they have not been sufficiently enlightened about our need. :) 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) - we recently 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. Scratch Patch - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 4. How Do I Get EDM/2? ═══ 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 the following sites: - ftp.cdrom.com in the /pub/os2/2_x/program/newsltr directory. - ftp.luth.se in the /pub/os2/programming/newsletter directory. - generalhq.pc.cc.cmu.edu o The EDM/2 mailing list. Send an empty message to edm2-info@knex.mind.org to receive a file containing (among other things) instructions for subscribing to EDM/2. This is a UUCP connection, so be patient please. o IBM's external gopher/WWW server in Almaden. The address is index.almaden.ibm.com and it is in the "Non-IBM-Originated" submenu of the "OS/2 Information" menu; the URL is "gopher://index.almaden.ibm.com/1nonibm/os2nonib.70". On Compuserve All back issues are available in the OS/2 Developers Forum 2. IBM Internal o IBM's internal gopher/WWW server in Almaden. The address is n6tfx.almaden.ibm.com and it is in the "Non-IBM-Originated Files" menu; the URL is "gopher://n6tfx.almaden.ibm.com/1!!nonibm/nonibm.70". 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. How do I Get EDM/2? - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 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). o Carsten Whimster - bcrwhims@undergrad.math.uwaterloo.ca (Internet). The following people contributed to this issue in one form or another (in alphabetical order): o Martin Lafaix o Larry Salomon, Jr. o Carsten Whimster o Gordon Zeglinski o Network distributors Contributors - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 5.1. Martin Lafaix ═══ Martin Lafaix Martin Lafaix is a computer science student at the UniversitВ de Nice-Sophia Antipolis. He currently works on his PhD thesis (key areas: functional language, type as value, programming in the large, ...). He can be reached at the following address: lafaix@sophia.inria.fr Contributors - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 5.2. Larry Salomon, Jr. ═══ Larry Salomon, Jr. Larry Salomon, Jr. 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 Cheyenne Software in Roslyn, New York and resides in Bellerose, New York with his wife Lisa. Larry can be reached electronically via the Internet at os2man@panix.com. Contributors - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 5.3. 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, and compiler courses as much as possible. This is not too difficult obviously, since this covers most of what they try to teach us in any case :). This summer and fall I am working at the University as a tutor in CS241, an introductory course to compilers. I am a TEAM-OS/2 member, and try to keep up with several OS/2 groups on the Internet. I am a beginning OS/2 PM programmer with a few projects on the go, and many more in my head. I use EMX/GCC 2.5.8, Watcom C/C++ 9.5, and Watcom VX-REXX 2.0, and I am anxiously awaiting Watcom C/C++ 10.0's impending release. 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 Contributors - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 5.4. 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. Contributors - EDM/2 - June 1994 - Volume 2, Issue 6 ═══ 5.5. 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 Hethmon (hethmon@apac.ag.utk.edu) - Compuserve o Gess Shankar (gess@knex.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! Contributors - EDM/2 - June 1994 - Volume 2, Issue 6