Introduction
Article shows how to use cards.dll that ships with windows, turn it into a class, and use it in a simple project that draws the reverse side of each of the 16 types of card backing.

Windows 2000 card backs

Windows XP card backs.
Background
In my programming life, I have always fancied writing a card game. Up until recently, this has been a daunting task, but I decided to bite the bullet and have a go. Over the last six months I have been teaching myself C#, so I decided that this would be the language to write the game in.
Presented below is part of the interface to cards.dll. I have mainly used two articles as my reference material that I found on the web, written originally for C. One is an MSDN article from 1996 by Dave Edson, the other comes from a site called catch22 by James Brown. I have tried to amalgamate these two articles to give a clearer overview of the cards.dll, while providing the code for C#.
Cards.dll
Cards.dll exports five functions which you can use to draw card images. Each of these is listed below, with an overview of each function and a detailed breakdown of what the arguments do. You cannot get directly to the bitmaps held within the dll, however in order to draw them, you supply a graphics device context to be drawn upon.
cdtInit
[DllImport("cards.dll")]
private static extern bool cdtInit( ref int width, ref int height );
cdtInit initializes the cards.dll library. As you can see the method takes two arguments which are stored as the width and height of the card in pixels. The return value is either TRUE for when the cards.dll has been initialized, or FALSE if an error has occurred. The constructor for the cardsdll class will throw an exception if there is an error. This is shown in the snippet below.
if( !cdtInit( ref width, ref height ) )
throw new Exception ( "cards.dll did not load" );
Although, you can supply a height and width, I have found that by changing these numbers, the card actually stays the same size.
cdtTerm
[DllImport("cards.dll")]
private static extern void cdtTerm();
cdtTerm is the uninitialization method. It cleans up the card resources. This is called in the Dispose() method of the cardsdll class. It is a one time function which needs to be called just before the application finishes.
cdtDraw
[DllImport("cards.dll")]
private static extern bool cdtDraw( IntPtr hdc, int x, int y,
int card, int mode, long color );
cdtDraw draws a card. This method takes a number of parameters listed below;
IntPtr hdc - Pass in the device context from Graphics.GetHdc().
int x - upper left corner of the card.
int y - upper left corner of the card.
int card - card to draw, (this is dependant upon the mode selected).
int mode - mode
mdFaceUp - draw face up card
mdFaceDown - draw face down card
mdHilite - draw face up card inverted
mdGhost - draw a ghost card, the card parameter is ignored
mdRemove - draw rectangle of background color at x, y
mdDeckX - draw an X
mdDeckO - draw an O
long color - table background color (only required for mdGhost and mdRemove) or when drawing the cross hatch card back.
The return value is TRUE if the draw was successful, and FALSE otherwise.
cdtDrawExt
[DllImport("cards.dll")]
private static extern bool cdtDrawExt( IntPtr hdc, int x, int y,
int dx, int dy, int card, int type, long color );
cdtDrawExt also draws a card. The difference is that by supplying arguments dx and dy, the size of the card can be varied.
cdtAnimate
cdtAnimate animates the backs of certain cards. In windows 2000 and previous the following cards animate.
ROBOT - Meter moves over 4 frames
CASTLE - Bats flapping around castle over 2 frames
ISLAND - Sun sticks tongue out over 4 frames
CARDHAND - Cards running up and down sleeve over 4 frames
Call cdtAnimate every 250 ms for proper animation speed. The return value is TRUE if the animation was successful, and FALSE when the animation has finished.
[DllImport("cards.dll")]
private static extern bool cdtAnimate( IntPtr hdc, int cardback,
int x, int y, int frame );
Bitmap numbering
One final point to note about the dll is the numbering of the bitmaps within it. The lowest card (zero) is the Ace of Clubs, next card is the Ace of Diamonds, etc. What happens is that the 52 playing cards rotate from Clubs, Diamonds, Hearts to Spades and back to Clubs again, starting at the Ace and working up to the King. I have provided 2 enumerations for the suit and rank of card.
public enum eSUIT : int
{
CLUBS = 0,
DIAMOND = 1,
HEARTS = 2,
SPADES = 3
}
public enum eRank : int
{
ACE = 0,
TWO = 1,
THREE = 2,
FOUR = 3,
FIVE = 4,
SIX = 5,
SEVEN = 6,
EIGHT = 7,
NINE = 8,
TEN = 9,
JACK = 10,
QUEEN = 11,
KING = 12
}
A card value can be calculated using the simple formula
card = rank + suit * 4;
For example, the 4 of Diamonds is
card = ( (int)eRank.FOUR + (int)eSUIT.DIAMOND ) * 4;
Giving the value to card of 13.
The 16 card back designs as shown in the images are cards 53 through to 68. Again, I have provided an enumeration for this.
public enum eBACK : int
{
CROSSHATCH = 53,
WEAVE1 = 54,
WEAVE2 = 55,
ROBOT = 56,
FLOWERS = 57,
VINE1 = 58,
VINE2 = 59,
FISH1 = 60,
FISH2 = 61,
SHELLS = 62,
CASTLE = 63,
ISLAND = 64,
CARDHAND = 65,
UNUSED = 66,
THE_X = 67,
THE_O = 68
}
I have not investigated whether the cards in XP animate or not. (Maybe another article!)
cardsdll class
In my code example, each of the interfaces to the dll are private, so in order to use them I have made some public functions. The main one of these I have used is to draw the backs of the cards.
public bool drawCardBack( IntPtr hdc, int x, int y, eBACK back )
Inside the actual function is a call to cdtDraw(. . .) with the last two arguments fixed at mdFaceDown and 0. mdFaceDown is a constant declaration in the class as one of the card types.
Similarly, each way of drawing a card has a public interface.
public bool drawCard( IntPtr hdc, int x, int y, int card,
int mode, long color )
public bool drawCardBack( IntPtr hdc, int x, int y, eBACK back )
public bool drawAnimatedBack( IntPtr hdc, int x, int y,
int card, int frame )
public bool drawInvertedCard( IntPtr hdc, int x, int y, int card )
public bool drawEmptyCard( IntPtr hdc, int x, int y, long color )
public bool drawExtrudedCard( IntPtr hdc, int x, int y, int dx,
int dy, int card, int mode, long color )
Using the code
As an example, in order to draw the bottom right corner card, the following call is made.
cardHandle.drawCardBack( hdc, 250, 310, eBACK.THE_O );
Where cardHandle is an instance of the cardsdll class.
Conclusion
I didn't realize how easy drawing a card could be till I looked at cards.dll. I hope people find this, my first article useful.
History
- 15.09.03 - Initial Publication.
| You must Sign In to use this message board. |
|
|
 |
|
 |
I have a problem with showing cards... It want show me 1-10 CLUBS and 1-10 SPADES. All other cards i can display, but when I want to display one of the first eleven, it shows just black. Does anyone had this kind of problem?
I tried with different cards.dll packeges but it doesn't help! Using VS 2008 and c#
Nikola
modified on Thursday, February 26, 2009 3:09 PM
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
 |
The program given with this article works great in XP. However; It's not working on Vista. It throws DllImportException when calling the method "cdtInit(ref int width, ref int height)" . I've also tried to put the cards.dll in the project and set the compability mode of the .exe file to Windows XP(SP2) but still it's not working. What solution can you offer me to make this program work on Vista?
|
| Sign In·View Thread·PermaLink | 5.00/5 |
|
|
|
 |
|
 |
First of all, good job on the new code and explanations.
Secondly, the width and height parameters to cdtInit are not intended to change the width and height of the cards. You correctly note that even if you change these values, they will always be initialized to defaults of 71x97. That's because the function wasn't intended for you to ever change the values, but for the correct values to be returned to you, hence they are byref parameters and should be initialized to 0 or -1 and will contain the correct width and height values after the function call. Not a big deal, just a little more explanation.
Thanks
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
I am new C#:
I found it hard to add the code in this example to a new C# project. What is the correct way to do this? please let me know
C# Student
|
| Sign In·View Thread·PermaLink | 1.50/5 |
|
|
|
 |
|
 |
You will get a runtime error when running this in debug with VS2k5. This is because it picks up subtle errors that 2k3 doesn't.
To correct this, change the declarations of the api's replacing longs with 'int32'. This is because .NET longs are 64bit where as API longs are 32bit.
Hope this helps.
|
| Sign In·View Thread·PermaLink | 5.00/5 |
|
|
|
 |
|
 |
I have used your code using Visual Studio 2003, it works good. But when I have tried to do it in VS 2005 I have got a run time error. Which told that the signature that I have defined doesn't match. How can I get rid of this error message ? can anybody help me ?
Alvi
|
| Sign In·View Thread·PermaLink | 1.00/5 |
|
|
|
 |
|
|
 |
|
|
 |
|
 |
Mike Kitchen wrote: I have completely rewritten the card class. a full sample pp using it can be downloaded from my site at http://www.publicjoe.f9.co.uk/csharp1/code/hilow.zip[^]
Cheers
Mike
Can you repost this code please.
Many thanks.
Matt...
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
 |
Hallo It a nice article , But when I uses Visual Studio 2005 (.NET 2.0) I get this error messages: PInvoke unmanaged signature etc.:(
The Fix to that's problem is, Visual Studio 2005 is very strict with the invoke signature. So the function drawCard have to convert the int parameter from int to short like this:
public bool drawCard( IntPtr hdc, int x, int y, int card, int mode, long color ) { return cdtDraw(hdc, Convert.ToInt16(x), Convert.ToInt16(y), Convert.ToInt16(card), Convert.ToInt16(mode), Convert.ToInt32(color)); }
hestol
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Although, you can supply a height and width, I have found that by changing these numbers, the card actually stays the same size.
This is because the width and height integers are never read by the DLL... it has fixed size cards. It stores values there for YOU to get so your program knows how big the cards will be when they are drawn.
Good programming practice (well to me) would be to use these variables as opposed to hard coding the width and height numbers in the source code anywhere. That way, should MS ever change the card sizes (probably won't happen) your program would be able to, theoretically, scale it's interface to match the new card size.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Card does not equal (Suit + Rank) * 4.
Card = Suit + Rank * 4 or Card = Suit + (Rank * 4)
The author had it right the first time.
|
| Sign In·View Thread·PermaLink | 1.00/5 |
|
|
|
 |
|
 |
Correction he didn't have it right the first time. Close though. You need to remember the order of operations.
Card != Rank + Suit * 4 Card = Suit + Rank * 4
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Using the parentheses makes code reading easier though:
Card = Suit + (Rank * 4); You can calculate the other way too. To calculate the card clicked etc..
Rank = Card / 4; Suit = Card - (Rank * 4);
In business, if two people always agree, one of them is unnecessary.
|
| Sign In·View Thread·PermaLink | 5.00/5 |
|
|
|
 |
|
 |
I am trying to create a card selection screen like the one in Windows Solitaire program. I know how to use the cards.dll (like you have here) but what I am trying to figure out is how to: 1) Make the cards Clickable objects (I currently have Image objects created and was trying to set the card back as the .image property of the object.
2) Make it so that when one is clicked, it is highlighed with a border to indicate it is the one selected.
If you can help, it would be appreciated.
Thanks.
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
|
 |
|
 |
I don't mean to be critical but I notice that you are passing 'long' integers to the cards.dll. Remember that in .NET a long is 64 bit but a LONG in 'c/c++' is 32 bit. You should be passing an int (or Int32). I think you are getting lucky and the Marshaler is dumbing it down for you.
Also, if you are implementing a Dispose() method you should derive from IDisposable as in:
public class cardsdll : IDisposable { }
Thanks for the code.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Yes you are right on both counts.
Along with a micrsoft friend of mine, we are working on a new version of the code, which I will hopefully put up here, when we are finished. It should be a lot better implemented and pass fxCop from microsoft.
Cheers
Mike
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
hi, is it possible to rotate some cards? (for example: 90°) if, yes can u show a simple example of doing it? thank u!
i wanted to draw some cards on the Bitmap, but it seems,it does not work! in onpaint method
....... Bitmap offScreenBmp = new Bitmap(this.Width, this.Height); Graphics offScreenDC = Graphics.FromImage(offScreenBmp); Hdc = offScreenDC.GetHdc(); offScreenDC.ReleaseHdc(Hdc);
DrawCardBack(10, 10); offScreenDC.DrawLine(Pens.Red, 0, 0, 100, 100);
e.Graphics.DrawImage(offScreenBmp, 10, 10); ....
i asked this in the c# forum!!! if it's interesting for u look here
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
The hdc should not be released until after the DrawCardBack function.
For a rotated function add this to the cards class
public void drawRotatedCard( Graphics grfx, int x, int y, int card, int mode ) { Bitmap offScreenBitmap = new Bitmap( 71, 97 ); Graphics offScreenGraphics = Graphics.FromImage( offScreenBitmap );
// Get handle to device context. IntPtr hdc = offScreenGraphics.GetHdc();
cdtDraw( hdc, 0, 0, card, mode, 0x00FFFFFF );
// Release handle to device context. offScreenGraphics.ReleaseHdc(hdc);
Point[] destPoints = { new Point( x, y+71 ), // position of top-left new Point( x, y ), // position of top-right new Point( x+97, y+71 ) }; // position of bottom-left
grfx.DrawImage( offScreenBitmap, destPoints ); }
To use it call
cardHandle.drawRotatedCard( e.Graphics, 10, 10, 43, 0 );
Hope this helps
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Minor Typo. Article says: ======================================================= A card value can be calculated using the simple formula
card = rank + suit * 4;
For example, the 4 of Diamonds is
card = ( (int)eRank.FOUR + (int)eSUIT.DIAMOND ) * 4; Giving the value to card of 13. =======================================================
The example should use paren around the suit and *4, like this:
card = (int)eRank.FOUR + ((int)eSUIT.DIAMOND * 4);
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
 |
I found that this works correctly:
card = (int)eSUIT.DIAMOND + ( (int)eRank.FOUR * 4 );
I do what geeks are supposed to do...
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
This is correct. You multiply the rank by four. Or you can make a single enum to do it.
ACE_CLUBS = 0 ACE_DIAMONDS = 1 ACE_HEARTS = 2 ACE_SPADES = 3 TWO_CLUBS = 4 TWO_DIAMONDS = 5 TWO_HEARTS = 6 TWO_SPADES = 7 THREE_CLUBS = 8 THREE_DIAMONDS = 9 THREE_HEARTS = 10 THREE_SPADES = 11 ...
That's why you multiply the rank by 4, the bitmaps are ordered by rank first and then by suit
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|