USER-DEFINED RESOURCES

The ”user-defined resource“ is convenient for attaching miscellaneous data to your .EXE file and obtaining access to that data within the program. The data can be in any format you want. The Windows functions used to access user-defined resources return a far pointer to the data when Windows loads the data into memory. You can do whatever you want with that data. For instance, suppose you have a file called PROGHELP.TXT that contains ”help“ text for your program. This file needn't be a pure ASCII file: It can also contain binary data, such as pointers that would aid your program in referencing various sections of this file. Reference this file with a statement in your resource script that looks like this:

helptext TEXT proghelp.txt

For helptext (the name of the resource) and TEXT (the type of the resource), you can use any names you want. I've capitalized TEXT simply to make it look like the ICON, CURSOR, and BITMAP statements. What we're doing here is making up our own type of resource, called TEXT.

During program initialization (for example, during processing of the WM_CREATE message), you can obtain a handle to this resource:

hResource = LoadResource (hInstance,

FindResource (hInstance, "TEXT", "helptext")) ;

The variable hResource is defined with type HANDLE. Despite its name, LoadResource does not actually load the resource into memory just yet. The LoadResource and FindResource functions used together like this are essentially equivalent to the LoadIcon and LoadCursor functions. In fact, LoadIcon and LoadCursor use the LoadResource and FindResource functions.

You can use numbers rather than names for the resource name and resource type. The numbers can be converted to far pointers in the FindResource call using MakeIntResource. The numbers used for the resource type must be greater than 255. (Windows uses numbers between 1 and 9 when calling FindResource for existing resource types.)

When you need access to the text, call LockResource:

lpHelpText = LockResource (hResource) ;

LockResource loads the resource into memory (if it has not already been loaded), locks it using the GlobalLock function, and returns a far pointer to it. When you are finished accessing the memory, unlock the segment:

UnlockResource (hResource) ;

This allows Windows to move the segment in memory. When you're finished with the resource, you can free it from memory:

FreeResource (hResource) ;

The resource will be freed when your program terminates, even if you don't call FreeResource.

Normally, user-defined resources are not discardable unless you include the DISCARDABLE keyword before the filename in the resource script. But if you use the pointer returned from LockResource to alter as well as read the data, don't make the resource DISCARDABLE. Note also that the same resource is shared among all instances of the program. If each instance needs its own copy of the resource, you should make the resource discardable, use LockResource to obtain a pointer to the resource, use GlobalAlloc to obtain a global memory block of the same size, use GlobalLock to lock that block, and then copy the contents of the resource into the global memory block.

Let's look at a sample program that uses three resources—an icon, a string table, and a user-defined resource. The POEPOEM program, shown in Figure 8-3, displays the text of Edgar Allan Poe's ”Annabel Lee“ in its client area. The user-defined resource is the file POEPOEM.ASC, which contains the text of the poem. The text file is terminated with a backslash (\).

POEPOEM.MAK

#-----------------------

# POEPOEM.MAK make file

#-----------------------

poepoem.exe : poepoem.obj poepoem.def poepoem.res

link poepoem, /align:16, NUL, /nod slibcew libw, poepoem

rc poepoem.res

poepoem.obj : poepoem.c poepoem.h

cl -c -Gsw -Ow -W2 -Zp poepoem.c

poepoem.res : poepoem.rc poepoem.ico poepoem.asc poepoem.h

rc -r poepoem.rc

POEPOEM.C

/*-------------------------------------------------

POEPOEM.C -- Demonstrates User-Defined Resource

(c) Charles Petzold, 1990

-------------------------------------------------*/

#include <windows.h>

#include "poepoem.h"

long FAR PASCAL WndProc (HWND, WORD, WORD, LONG) ;

char szAppName [10] ;

char szCaption [35] ;

HANDLE hInst ;

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,

LPSTR lpszCmdLine, int nCmdShow)

{

HWND hwnd ;

MSG msg ;

WNDCLASS wndclass ;

if (!hPrevInstance)

{

LoadString (hInstance, IDS_APPNAME, szAppName, sizeof szAppName) ;

LoadString (hInstance, IDS_CAPTION, szCaption, sizeof szCaption) ;

wndclass.style = CS_HREDRAW | CS_VREDRAW ;

wndclass.lpfnWndProc = WndProc ;

wndclass.cbClsExtra = 0 ;

wndclass.cbWndExtra = 0 ;

wndclass.hInstance = hInstance ;

wndclass.hIcon = LoadIcon (hInstance, szAppName) ;

wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;

wndclass.hbrBackground = GetStockObject (WHITE_BRUSH) ;

wndclass.lpszMenuName = NULL ;

wndclass.lpszClassName = szAppName ;

RegisterClass (&wndclass) ;

}

else

{

GetInstanceData (hPrevInstance, szAppName, sizeof szAppName) ;

GetInstanceData (hPrevInstance, szCaption, sizeof szCaption) ;

}

hInst = hInstance ;

hwnd = CreateWindow (szAppName, szCaption,

WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,

CW_USEDEFAULT, CW_USEDEFAULT,

CW_USEDEFAULT, CW_USEDEFAULT,

NULL, NULL, hInstance, NULL) ;

ShowWindow (hwnd, nCmdShow) ;

UpdateWindow (hwnd) ;

while (GetMessage (&msg, NULL, 0, 0))

{

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

return msg.wParam ;

}

long FAR PASCAL WndProc (HWND hwnd, WORD message, WORD wParam, LONG lParam)

{

static HANDLE hResource ;

static HWND hScroll ;

static short nPosition, cxChar, cyChar, cyClient, nNumLines, xScroll ;

char szPoemRes [15] ;

char far *lpText ;

HDC hdc ;

PAINTSTRUCT ps ;

RECT rect ;

TEXTMETRIC tm ;

switch (message)

{

case WM_CREATE :

hdc = GetDC (hwnd) ;

GetTextMetrics (hdc, &tm) ;

cxChar = tm.tmAveCharWidth ;

cyChar = tm.tmHeight + tm.tmExternalLeading ;

ReleaseDC (hwnd, hdc) ;

xScroll = GetSystemMetrics (SM_CXVSCROLL) ;

hScroll = CreateWindow ("scrollbar", NULL,

WS_CHILD | WS_VISIBLE | SBS_VERT,

0, 0, 0, 0,

hwnd, 1, hInst, NULL) ;

LoadString (hInst, IDS_POEMRES, szPoemRes, sizeof szPoemRes) ;

hResource = LoadResource (hInst,

FindResource (hInst, szPoemRes, "TEXT")) ;

lpText = LockResource (hResource) ;

nNumLines = 0 ;

while (*lpText != '\\' && *lpText != '\0')

{

if (*lpText == '\n')

nNumLines ++ ;

lpText = AnsiNext (lpText) ;

}

*lpText = '\0' ;

GlobalUnlock (hResource) ;

SetScrollRange (hScroll, SB_CTL, 0, nNumLines, FALSE) ;

SetScrollPos (hScroll, SB_CTL, 0, FALSE) ;

return 0 ;

case WM_SIZE :

MoveWindow (hScroll, LOWORD (lParam) - xScroll, 0,

xScroll, cyClient = HIWORD (lParam), TRUE) ;

SetFocus (hwnd) ;

return 0 ;

case WM_SETFOCUS :

SetFocus (hScroll) ;

return 0 ;

case WM_VSCROLL :

switch (wParam)

{

case SB_TOP :

nPosition = 0 ;

break ;

case SB_BOTTOM :

nPosition = nNumLines ;

break ;

case SB_LINEUP :

nPosition -= 1 ;

break ;

case SB_LINEDOWN :

nPosition += 1 ;

break ;

case SB_PAGEUP :

nPosition -= cyClient / cyChar ;

break ;

case SB_PAGEDOWN :

nPosition += cyClient / cyChar ;

break ;

case SB_THUMBPOSITION :

nPosition = LOWORD (lParam) ;

break ;

}

nPosition = max (0, min (nPosition, nNumLines)) ;

if (nPosition != GetScrollPos (hScroll, SB_CTL))

{

SetScrollPos (hScroll, SB_CTL, nPosition, TRUE) ;

InvalidateRect (hwnd, NULL, TRUE) ;

}

return 0 ;

case WM_PAINT :

hdc = BeginPaint (hwnd, &ps) ;

lpText = LockResource (hResource) ;

GetClientRect (hwnd, &rect) ;

rect.left += cxChar ;

rect.top += cyChar * (1 - nPosition) ;

DrawText (hdc, lpText, -1, &rect, DT_EXTERNALLEADING) ;

GlobalUnlock (hResource) ;

EndPaint (hwnd, &ps) ;

return 0 ;

case WM_DESTROY :

FreeResource (hResource) ;

PostQuitMessage (0) ;

return 0 ;

}

return DefWindowProc (hwnd, message, wParam, lParam) ;

}

POEPOEM.RC

/*----------------------------

POEPOEM.RC resource script

----------------------------*/

#include "poepoem.h"

poepoem ICON poepoem.ico

AnnabelLee TEXT poepoem.asc

STRINGTABLE

{

IDS_APPNAME, "poepoem"

IDS_CAPTION, """Annabel Lee"" by Edgar Allen Poe"

IDS_POEMRES, "AnnabelLee"

}

POEPOEM.ICO

POEPOEM.H

/*-----------------------

POEPOEM.H header file

-----------------------*/

#define IDS_APPNAME 0

#define IDS_CAPTION 1

#define IDS_POEMRES 2

POEPOEM.ASC

It was many and many a year ago,

In a kingdom by the sea,

That a maiden there lived whom you may know

By the name of Annabel Lee;

And this maiden she lived with no other thought

Than to love and be loved by me.

I was a child and she was a child

In this kingdom by the sea,

But we loved with a love that was more than love --

I and my Annabel Lee --

With a love that the wing\ged seraphs of Heaven

Coveted her and me.

And this was the reason that, long ago,

In this kingdom by the sea,

A wind blew out of a cloud, chilling

My beautiful Annabel Lee;

So that her highborn kinsmen came

And bore her away from me,

To shut her up in a sepulchre

In this kingdom by the sea.

The angels, not half so happy in Heaven,

Went envying her and me --

Yes! that was the reason (as all men know,

In this kingdom by the sea)

That the wind came out of the cloud by night,

Chilling and killing my Annabel Lee.

But our love it was stronger by far than the love

Of those who were older than we --

Of many far wiser than we --

And neither the angels in Heaven above

Nor the demons down under the sea

Can ever dissever my soul from the soul

Of the beautiful Annabel Lee:

For the moon never beams, without bringing me dreams

Of the beautiful Annabel Lee;

And the stars never rise, but I feel the bright eyes

Of the beautiful Annabel Lee:

And so, all the night-tide, I lie down by the side

Of my darling -- my darling -- my life and my bride,

In her sepulchre there by the sea --

In her tomb by the sounding sea.

[May 1849]

\

POEPOEM.DEF

;------------------------------------

; POEPOEM.DEF module definition file

;------------------------------------

NAME POEPOEM

DESCRIPTION 'Demo of User-Defined Resource (c) Charles Petzold, 1990'

EXETYPE WINDOWS

STUB 'WINSTUB.EXE'

CODE PRELOAD MOVEABLE DISCARDABLE

DATA PRELOAD MOVEABLE MULTIPLE

HEAPSIZE 1024

STACKSIZE 8192

EXPORTS WndProc

In the POEPOEM.RC resource script, the user-defined resource is given the type TEXT and the name AnnabelLee:

AnnabelLee TEXT poepoem.asc

During WM_CREATE processing in WndProc, a handle to the resource is obtained using FindResource and LoadResource. The resource is locked using LockResource, and a small routine replaces the backslash (\) at the end of the file with a 0. (This is for the benefit of the DrawText function used later.) In most cases it's not a good idea to write on a user-defined resource directly, because the same resource is shared among all instances of the program. However, later instances of POEPOEM will not encounter problems with the change we've made. The resource is then unlocked using GlobalUnlock.

The resource is also locked and unlocked during processing of WM_PAINT to write the text to the display using DrawText. Note the use of a child window scroll bar control rather than a window scroll bar. The child window scroll bar control has an automatic keyboard interface, so no WM_KEYDOWN processing is required in POEPOEM.

POEPOEM also uses three character strings, the IDs of which are defined in the POEPOEM.H header file. For the first instance of the program, the IDS_APPNAME and IDS_CAPTION strings are loaded into global static variables using LoadString:

LoadString (hInstance, IDS_APPNAME, szAppName, sizeof szAppName) ;

LoadString (hInstance, IDS_CAPTION, szCaption, sizeof szCaption) ;

However, for subsequent instances of POEPOEM, the strings are copied from the previous instance:

GetInstanceData (hPrevInstance, szAppName, sizeof szAppName) ;

GetInstanceData (hPrevInstance, szCaption, sizeof szCaption) ;

GetInstanceData is faster than LoadString if the string resource has been discarded from memory. The pointers (szAppName and szCaption) are near pointers to static global variables. Windows uses these pointers in combination with the data segment address of the previous instance and the data segment address of the current instance to copy the contents of the variables.

Now that we've defined all the character strings used in POEPOEM as resources, we've made it easier for translators to convert the program into a foreign-language version. Of course, they'd also have to translate the text of ”Annabel Lee“—which would, I suspect, be a somewhat more difficult task.