Using cards.dll API

How to access the cards.dll API

The aim of this article is to describe exactly how to use cards.dll, the dynamic link library that has shipped with all versions of windows since version 3.0. This library contains all of the playing card bitmapped images that games such as Windows Solitare use to display their graphics.

There are actually two versions of cards.dll. The Windows 95 series of operating systems (this includes Windows 98 and Windows ME) use the original 16bit version which shipped way back with Windows 3.0. Obviously there are going to be problems using this 16bit DLL from your 32bit application, but I will come on to that later. The second version is a 32bit DLL, and ships with the Windows NT series (Windows NT, Windows 2000 etc). I will explain how to use this version first. 

Exported functions from cards.dll

Cards.dll exports five functions which your program can use to draw card images. These functions are the the only interface that cards.dll provides to draw cards. Note that these functions never let you have access to the bitmaps; rather, you supply a device context and the card functions draw the card bitmaps for you. Here is a description of each function, and its prototype.

cdtInit

BOOL WINAPI cdtInit (int *width, int *height)

Initializes the cards.dll library for your application. You must supply the addresses of two variables, in which cards.dll stores the width and height (in pixels) of a card.

cdtDraw

BOOL WINAPI cdtDraw (HDC hdc, int x, int y, int card, int type, DWORD color)

Draws a card at position x, y on the device context hdc. The type parameter controls whether the front, the back, or the inverted front of the card is drawn.

The card parameter controls which card is drawn. This parameter is dependent on the value of type. If a card face is to be drawn (type is 0 or 2), then card must be a value from 0 through 51 to represent each card. If type specifies that a card back is to be drawn (type is 1), then card must be a value from 53 to 68 (inclusive), to represent one of the 16 possible card backs.

The card faces are organised in increasing order. That is, the aces come first, then the two's and so on. In each group, the cards are ordered by suit. The order is clubs, diamons, hearts, spades. This pattern is repeated as the card values increase.

You can use the following values to represent each card suit.

#define ecsCLUBS    0
#define ecsDIAMONDS 1
#define ecsHEARTS   2
#define ecsSPADES   3

To calculate the value of card given a suit (0-3) and a face value (0-13, 0 being aces, 13 being kings), you can use this simple formula:

  card = suit + face * 4

Here is a list of the 16 possible card backgrounds that you can use in your code.

#define ecbCROSSHATCH  53
#define ecbWEAVE1      54
#define ecbWEAVE2      55
#define ecbROBOT       56
#define ecbFLOWERS     57
#define ecbVINE1       58
#define ecbVINE2       59
#define ecbFISH1       60
#define ecbFISH2       61
#define ecbSHELLS      62
#define ecbCASTLE      63
#define ecbISLAND      64
#define ecbCARDHAND    65
#define ecbUNUSED      66
#define ecbTHE_X       67
#define ecbTHE_O       68

Lastly, the color parameter sets the background color for the ecbCrossHatch card back, which uses a pattern drawn with lines. All the other backs and fronts are bitmaps, so color has no effect.

cdtDrawExt

BOOL WINAPI cdtDrawExt (HDC hdc, int x, int y, int dx, int dy, 
    int card, int suit, DWORD color)

This procedure is the same as cdtDraw except that you specify the dx and dy parameters to indicate the size of the card. The card bitmaps are stretched or compressed to the specified size.

cdtAnimate

BOOL WINAPI cdtAnimate (HDC hdc, int cardback, int x, int y, int frame)

This function animates the backs of cards by overlaying part of the card back with an alternative bitmap. It creates effects: blinking lights on the robot, the sun donning sunglasses, bats flying across the castle, and a card sliding out of a sleeve. The function works only for cards of normal size drawn with cdtDraw. To draw each state, start with frame set to 0 and increment through until cdtAnimate returns 0.

cdtTerm

void WINAPI cdtTerm (void)

This function cleans up the card resources from your program. It takes no parameters, and returns no values. It is a good idea to call this function just before your card game exits.

How to load a Dynamic Link Library

The best way to access the cards.dll libary is to use the LoadLibrary API call, and then use GetProcAddress to access the individual functions that cards.dll exports.

HMODULE hCardDll;
...

BOOL InitCardsDll(void)
{
    hCardDll = LoadLibrary("cards.dll");

    if(hCardDll == 0)
        return FALSE;

    cdtInit   = (pfcdtInit)   GetProcAddress(hCardDll, "cdtInit");
    cdtDraw   = (pfcdtDraw)   GetProcAddress(hCardDll, "cdtDraw");
    cdtDrawEx = (pfcdtDrawEx) GetProcAddress(hCardDll, "cdtDrawExt");
    cdtTerm   = (pfcdtTerm)   GetProcAddress(hCardDll, "cdtTerm");

    return TRUE;
}

You should use the following typedef's to define each function prototype:

typedef BOOL (WINAPI *pfcdtInit)(int *, int *);
typedef BOOL (WINAPI *pfcdtDraw)(HDC, int x, int y, int card, int type, DWORD color);
typedef BOOL (WINAPI *pfcdtDrawEx)(HDC, int x, int y, int dx, int dy, int card, int type, DWORD color);
typedef BOOL (WINAPI *pfcdtAnimate)(HDC hdc, int cardback, int x, int y, int state);
typedef void (WINAPI *pfcdtTerm) (void);

Note that all parameters to the 32bit library are themselves 32bits in size.

That's really all there is to it. Don't forget to release the library with FreeLibrary before your program exits.

Using the 16bit cards.dll library

If you want your card game to run on Windows 95 then you have a problem, because 16bit DLLs are not directly compatible with a 32bit application. You have two options to choose from. The first is to distribute the 32bit version of cards.dll with your game. This is a bad idea, because firstly it's probably illegal to do this unless you have permission from Microsoft, and secondly, cards.dll is around 150Kb, which is a bit on the hefty side. Your second option is to access the 16bit dll from your 32bit code using a technique called Flat Thunking.

Flat Thunking is documented (I won't say fully) in MSDN. It involves writing two additional thunking DLLs; a 32bit DLL which thunks down to 16bit mode, and a 16bit DLL which calls the functions inside the "target" 16bit DLL.

Whilst this is OK in itself, I've always thought it was a little messy to have to distribute two additional DLLs. Fortunately there is an undocumented method to perform a Flat Thunk directly from your win32 application.

There is an undocumented function in KERNEL32.DLL called QT_Thunk. This function is used by a flat-thunk DLL pair to perform a thunk from 32 to 16bits. Using a little assembly language we can use this function directly from our own application without any additional DLLs. I won't describe here exactly how to perform this trick as it is fully documented in the source code.

Assuming that we can now access the 16bit version of cards.dll, there are a couple of points to mention. The first is that we are dealing with a 16bit executable module. This means that the default size of an integer is 16bits. The consequence of this is that we need to make sure that we only pass 16bit integers to cards.dll. DWORD values can stay as 32bits. The source-code handles this correctly.

The demonstration program uses the same 32bit function prototypes to access cards.dll regardless of which version of Windows you are using. The flat thunking is hidden behind a set of 32bit wrapper functions, so the test program does not need to know which version of Windows it is running under when it calls the cards.dll functions.

You may want to check out these two links for additional information on this flat thunking trick.

Original article by Matt Pietrek

Codeguru article on the same subject