Cards.dll direct bitmap access

Accessing the cards.dll bitmaps as resources

The first tutorial I wrote about cards.dll described how to use the exported card-drawing functions (cdtInit, cdtDraw etc). This tutorial will take a different approach to card drawing. Instead of calling the card-functions, I will show you how to directly access the bitmap resources stored inside cards.dll.

Small card pic

This method is about the same complexity as the original one I described, but there is much less work to do when it comes to accessing the bitmaps in the 16bit version of cards.dll.

How to load a Dynamic Link Library (32bits and 16bits)

There will be two different versions of cards.dll, depending on which operating system our program is run under. For Windows 95,98 and ME, there is a 16bit DLL. Under Windows NT, 2000 and XP, there is a 32bit DLL. Because we don't know in advance which DLL we will encounter, we need to dynamically load cards.dll.

Under the Windows NT series, this is very simple. All we need is a single call to LoadLibrary.

HINSTANCE hCardDll;

// Load the dynamic link library
hCardDll = LoadLibrary("cards.dll");

if(hCardDll == 0)
{
    // Error, cannot continue
    return FALSE;
}

// Load the bitmap resources
LoadCardBitmapsFromLibrary(hCardDll);

// Release the dynamic link library
FreeLibrary(hCardDll);

When it comes to the Windows 95 series, there is practically no change. The only difference is we use the undocumented LoadLibrary16 and FreeLibrary16 functions, which are exported from KERNEL32.DLL as ordinals 35 and 36, respectively. These are still 32bit functions, but they enable the caller to load a 16bit DLL. In order to use these functions from our application we need to create a .DEF file with the following content:

LIBRARY KERNEL32

EXPORTS
    LoadLibrary16@4                 @35
    FreeLibrary16@4                 @36

This is only half-way there. In order for our application to correctly link to these functions, we need to create an import library. The LIB.EXE tool that ships with the Platform SDK performs this task, and is located in the PlatformSDK\bin\link directory. Also, LIB.EXE that ships with Visual C++ (in the VC98\bin\link directory) will also do the job. Run LIB.EXE with the DEF file above to create our import library.

lib /machine:IX86 /def:cards16.def

Now that we have our import library, cards16.lib, we can link this with our application and then call LoadLibrary16 and FreeLibrary16 in the exact same way as before.

// Include a library-search record in the object file.
// This causes cards16.dll to be automatically linked in!
#pragma comment( lib, "cards16.lib" )

// Need to prototype the two undocumented functions, because
// they don't appear in windows.h
HINSTANCE WINAPI LoadLibrary16(PSTR);
void      WINAPI FreeLibrary16(HINSTANCE);
HINSTANCE hCardDll;

// Load the dynamic link library
hCardDll = LoadLibrary16("cards.dll");

if(hCardDll == 0)
{
    // Error, cannot continue
    return FALSE;
}

// Load the bitmap resources
LoadCardBitmapsFromLibrary(hCardDll);

// Release the dynamic link library     
FreeLibrary16(hCardDll);

Accessing the bitmap resources

We can load a bitmap from the resource section of any executable (including DLLs) by using the LoadBitmap API call. And by a stroke of luck, LoadBitmap can load bitmaps from 16bit DLLs as well as 32bit DLLs!!! Because we have a valid handle to a DLL (even a 16bit one), LoadBitmap just seems to treat the resource sections the same, which can only be good for us.

Using LoadBitmap is really easy. Just specify the DLL hInstance and a resource ID, and you can load any bitmap you want.

HBITMAP hBitmap = LoadBitmap(hCardDll, MAKEINTRESOURCE(dwIndex));

After a little experimentation, I found that the card bitmaps started with dwIndex=1. Also, the card bitmaps are stored in increasing order, suit by suit. So, first of all there are 13 clubs (Ace-King), then 13 diamonds, then hearts and spades. A simple for loop is enough to load each bitmap in turn.

HBITMAP hBitmap;
BITMAP  bmp;
int     cardwidth, cardheight;

for(i = 0; i < 52; i++)
{
    // work out the correct card to retrieve
    DWORD dwIndex = (i % 4) * 13 + (i/4);

    // Load each bitmap in turn
    hBitmap = LoadBitmap(hCardDll, MAKEINTRESOURCE(dwIndex + 1));

    // find the bitmap dimensions
    GetObject(hBitmap, sizeof(bmp), &bmp);
    cardwidth  = bmp.bmWidth;
    cardheight = bmp.bmHeight;

    // * create a large memory DC now we know the bitmap size (first time only)
    // * copy bitmap into a large memory DC

    // free up the bitmap resource
    DeleteObject(hBitmap);
}

Take a close look at the first statement in the for-loop. This formula is necessary, because the arrangement of the card-bitmap resources is different than that used by the original cdtDraw functions. The resources are stored Ace-to-King, suit-by-suit, whilst cdtDraw enables access to the cards by a face-value grouping arrangement. That is, the aces come first, then all the twos, then the threes etc. Therefore a small bit of maths is required to convert from one arrangement to the other. The result will be that our card bitmaps are in the same order as we are used to.

Obviously its no good just loading the bitmaps and then freeing them again. We will create a memory device-context large enough to store all the bitmaps in, one after the other. This will make it convenient when we want to draw the card bitmaps.

HDC     hMemDC = 0;
HBITMAP hMemBitmap = 0;
HDC     hCardDC;
...

HDC hdc = GetDC(0);

// Create a memory DC for the big bitmap store
hMemDC      = CreateCompatibleDC(hdc);
hMemBitmap  = CreateCompatibleBitmap(hdc, cardwidth * NUMCARDBITMAPS, cardheight);
SelectObject(hMemDC, hMemBitmap);

// Create a temporary DC to store each card bitmap in
hCardDC     = CreateCompatibleDC(hdc);

ReleaseDC(0, hdc);        

Now we can save off each bitmap as they are loaded.

HANDLE  hOld;
...
hOld = SelectObject(hdcCard, hBitmap);
BitBlt(hMemDC, i * cardwidth, 0, cardwidth, cardheight, hdcCard, 0, 0, SRCCOPY);
SelectObject(hdcCard, hOld);

Fixing the card bitmaps

Red card picWhen you have all the card bitmaps loaded, you will find that whilst all the "black" cards (clubs, spades) have a black single-line border, all of the "red" cards (hearts, diamonds) have a red single-line border. I prefer it when all cards have a black border (like the cdtDraw function draws them as), so it is necessary to modify each card as it is stored to give them a black border.

The code below draws a single black border around a specific card, once it is stored in the large memory DC.

int xpos = i * width;
MoveToEx(hMemDC, xpos+2,         0, 0);
LineTo  (hMemDC, xpos+width - 3, 0);
LineTo  (hMemDC, xpos+width - 1, 2);
LineTo  (hMemDC, xpos+width - 1, height - 3);   //vertical
LineTo  (hMemDC, xpos+width - 3, height - 1);
LineTo  (hMemDC, xpos+2,         height - 1);
LineTo  (hMemDC, xpos+0,         height - 3);
LineTo  (hMemDC, xpos+0,         2);
LineTo  (hMemDC, xpos+2,         0);

Drawing a card bitmap

After the loop has finished we can clean up, and then get ready to draw card bitmaps! We will define a function, CardBlt, which will draw a card bitmap into a specified DC, just like the original cdtDraw function. Because each card has slightly rounded edges, it would be a nice idea to draw each card with transparent corners. And because each card is the same shape we can forgoe the hassle of getting a transparent blitter working. Instead, I have opted to draw a card in strips - one main strip, and two shorter strips each side. The result is faster than doing a "proper" transparent blit, and is simpler too. Note that this technique assumes that the cards are all of a particular shape. Should the card bitmaps change shape radically in future versions of windows, then this method would fail to draw the cards properly.

void CardBlt(HDC hdc, int x, int y, int nCardNum)
{
    int sx = nCardNum * cardwidth;
    int sy = 0;
    int width  = cardwidth;
    int height = cardheight;

    //draw main center band
    BitBlt(hdc, x+2, y, width - 4, height, hMemDC, sx+2, sy+0, SRCCOPY);

    //draw the two bits to the left
    BitBlt(hdc, x,   y+2, 1, height - 4, hMemDC, sx+0, sy+2, SRCCOPY);
    BitBlt(hdc, x+1, y+1, 1, height - 2, hMemDC, sx+1, sy+1, SRCCOPY);

    //draw the two bits to the right
    BitBlt(hdc, x+width-2, y+1, 1, height - 2, hMemDC, sx+width-2, sy+1, SRCCOPY);
    BitBlt(hdc, x+width-1, y+2, 1, height - 4, hMemDC, sx+width-1, sy+2, SRCCOPY);
}

Conclusion

We're now ready to draw card bitmaps to our heart's content. The sample download implements a complete card bitmap loader, and demonstrates drawing the cards.

This tutorial took a different approach to accessing the card bitmaps in cards.dll. Instead of using a set of well defined functions to draw the bitmaps, we accessed them directly by looking into the resource section of cards.dll. The complexity of accessing the 16bit version of cards.dll has also reduced because the flat thunking was not necessary to extract the bitmap resources.

Look out for Part 3 of this card-drawing series, which I will be releasing shortly. In this tutorial I will be presenting the card-game engine I developed whilst programming Shed (the card-game which you can find on this site). This C++ library provides powerful card handling and drawing support.