Cómo usar DLL de gráficos en nuestros programas

Para este ejemplo desarrollaremos un juego de cartas, y tomaremos prestada la baraja de Microsoft, que está incluida en una DLL de recursos llamada "Cards.dll". Esto nos ahorrará gran parte del trabajo de diseño gráfico.

Generalmente las DLL se usan para agrupar funciones, pero también se pueden usar para agrupar recursos o, como en el caso que nos ocupa, mapas de bits.

Lo primero que tenemos que hacer es obtener un manipulador de instancia de la biblioteca. Para ello usaremos la función LoadLibrary, indicando como parámetro el nombre de la biblioteca. En este caso "cards.dll".

Como en cualquier caso en que se obtiene un recurso para nuestro programa, deberemos recordar liberarlo cuando ya no lo necesitemos. Para ello usaremos otra función del API de Windows: FreeLibrary, indicando en este caso como parámetro el manipulador obtenido previamente.

    static HINSTANCE hLib;
...
    hLib = LoadLibrary("cards.dll");
...
    FreeLibrary(hLib);

Ejemplo de uso de cards.dll

Veamos un ejemplo de aplicación GUI para mostrar el contenido completo de la DLL cards.dll:

#include <windows.h>

/*  Declare Windows procedure  */
LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);

/*  Make the class name into a global variable  */
char szClassName[ ] = "WindowsApp";

int WINAPI WinMain (HINSTANCE hThisInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR lpszArgument,
                     int nFunsterStil)
{
    HWND hwnd;               /* This is the handle for our window */
    MSG messages;            /* Here messages to the application are saved */
    WNDCLASSEX wincl;        /* Data structure for the windowclass */

    /* The Window structure */
    wincl.hInstance = hThisInstance;
    wincl.lpszClassName = szClassName;
    wincl.lpfnWndProc = WindowProcedure;      /* This function is called by windows */
    wincl.style = CS_DBLCLKS;                 /* Catch double-clicks */
    wincl.cbSize = sizeof (WNDCLASSEX);

    /* Use default icon and mouse-pointer */
    wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
    wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
    wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
    wincl.lpszMenuName = NULL;                 /* No menu */
    wincl.cbClsExtra = 0;                      /* No extra bytes after the window class */
    wincl.cbWndExtra = 0;                      /* structure or the window instance */
    /* Use Windows's default color as the background of the window */
    wincl.hbrBackground = GetSysColorBrush(COLOR_BACKGROUND);

    /* Register the window class, and if it fails quit the program */
    if (!RegisterClassEx (&wincl))
        return 0;

    /* The class is registered, let's create the program*/
    hwnd = CreateWindowEx (
           0,                   /* Extended possibilites for variation */
           szClassName,         /* Classname */
           "CARDS.DLL",       /* Title Text */
           WS_OVERLAPPEDWINDOW, /* default window */
           CW_USEDEFAULT,       /* Windows decides the position */
           CW_USEDEFAULT,       /* where the window ends up on the screen */
           544,                 /* The programs width */
           375,                 /* and height in pixels */
           HWND_DESKTOP,        /* The window is a child-window to desktop */
           NULL,                /* No menu */
           hThisInstance,       /* Program Instance handler */
           NULL                 /* No Window Creation data */
           );

    /* Make the window visible on the screen */
    ShowWindow (hwnd, nFunsterStil);

    /* Run the message loop. It will run until GetMessage() returns 0 */
    while (TRUE == GetMessage (&messages, NULL, 0, 0))
    {
        /* Translate virtual-key messages into character messages */
        TranslateMessage(&messages);
        /* Send message to WindowProcedure */
        DispatchMessage(&messages);
    }

    /* The program return-value is 0 - The value that PostQuitMessage() gave */
    return messages.wParam;
}


/*  This function is called by the Windows function DispatchMessage()  */
LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    static HINSTANCE hLib;
    HBITMAP hBitmap;
    PAINTSTRUCT ps;
    BITMAP bmp;
    static int w, h;
    int i, j;
    HDC memDC;
    HDC hDC;

    switch (message)                  /* handle the messages */
    {
        case WM_CREATE:
            hLib = LoadLibrary("cards.dll");
            hBitmap = LoadBitmap(hLib, MAKEINTRESOURCE(1));
            GetObject(hBitmap, sizeof(bmp), &bmp);
            w  = bmp.bmWidth;
            h = bmp.bmHeight;
            MoveWindow(hwnd, 30, 30, 20+w*13, 40+h*6, FALSE);
            return false;
        case WM_PAINT:
            hDC = BeginPaint(hwnd, &ps);
            memDC = CreateCompatibleD(hDC);
            for(j = 0; j < 5; j++)
               for(i = 0; i < 13; i++) {
                  hBitmap = LoadBitmap(hLib, MAKEINTRESOURCE(1+i+13*j));
                  SelectObject(memDC, hBitmap);
                  BitBlt(hDC, w*i, h*j, w, h, memDC, 0, 0, SRCCOPY);
                  DeleteObject(hBitmap);
               }
            for(i = 0; i < 2; i++) {
               hBitmap = LoadBitmap(hLib, MAKEINTRESOURCE(i+67));
               SelectObject(memDC, hBitmap);
               BitBlt(hDC, w*i, h*5, w, h, memDC, 0, 0, SRCCOPY);
               DeleteObject(hBitmap);
            }
            EndPaint(hwnd, &amp;ps);
            break;
        case WM_DESTROY:
            FreeLibrary(hLib);
            PostQuitMessage (0);       /* send a WM_QUIT to the message queue */
            break;
        default:                      /* for messages that we don't deal with */
            return DefWindowProc (hwnd, message, wParam, lParam);
    }

    return 0;
}

En este programa hemos usado varios bucles para obtener los mapas de bits de cada carta de la baraja. Para las cartas se usan los identificadores del 1 al 52, 13 cartas por cada palo: 1 a 10 y las tres figuras. Existen otros 13 mapas de bits, que generalmente se usan para los dorsos de las cartas y, los dos últimos, (67 y 68) se usan para marcar zonas del tapete de juego.

Hemos usado la macro MAKEINTRESOURCE para obtener el recurso a partir de un valor entero con una llamada a LoadBitmap. Esto nos da un manipulador de mapa de bits que podemos usar con las funciones del GDI de forma normal.

Afinando el aspecto gráfico

Rey de diamantes
Rey

Si nos fijamos en los gráficos de los naipes de la biblioteca veremos que los bordes están redondeados. Pero si usamos la función BitBlt para dibujar las cartas y resultan estar sobre otras cartas o en general, siempre que el fondo no sea blanco, aparecerán unas feas esquinas blancas. Para evitar esto usaremos otra función que nos permite dibujar bitmaps usando una máscara.

Esquina de carta
Esquina de carta

Podremos usar las funciones PlgBlt o MaskBlt. Pero la segunda no nos sirve, ya que la parte que no cubre la máscara se pinta con el pincel seleccionado para el fondo. Nosotros queremos que la zona no enmascarada conserve el contenido previo, fuera cual fuese. Por lo tanto, en este caso usaremos la primera de ellas.

Nota:Estas funciones se explican en el capítulo 23 del curso del Win API.

Primero necesitaremos un mapa de bits para hacer una máscara, para ello usaremos un programa de edición de gráficos, como el Paint, para crear un fichero bmp de 71x96 pixels, monocromo, es decir, un bit por pixel. Dejaremos todo el mapa de bits en blanco, salvo los tres pixels de cada esquina, que los pintaremos de negro.

Este mapa de bits se usará como máscara cada vez que tengamos que visualizar una carta, y lo incluiremos en el fichero de recursos del proyecto del juego.

Para dibujar las cartas usaremos esta estructura:

      hMascara = LoadBitmap(hInstance, "Mascara");
      POINT punto[3];
...
      punto[0].x = x;
      punto[0].y = y;
      punto[1].x = punto[0].x+w;
      punto[1].y = punto[0].y;
      punto[2].x = punto[0].x;
      punto[2].y = punto[0].y+h;
...
      hBitmap = LoadBitmap(hLib, MAKEINTRESOURCE(21));
      SelectObject(memDC, hBitmap);
      PlgBlt(hDC, punto, memDC, 0, 0, w, h, (HBITMAP)hMascara, 0, 0);
      DeleteObject(hBitmap);
...
      SelectObject(hDC, anterior);
      DeleteDC(memDC);
...
      DeleteObject(hMascara);

Sí, es más complicado, pero el resultado es algo mejor.

Otra ventaja de esta función, (aunque no para este juego), es que nos permite dibujar la carta en otras posiciones, además de la que ya está definida. Podemos dibujar la carta horizontal o inclinada, e incluso en perspectiva.