Blobs and Object-Oriented Database Engines

In today's database parlance, sound and image data are known as "binary large objects," or BLOBs for short. Sam examines one approach to constructing and storing BLOBs in an object-oriented DBMS system.


December 01, 1992
URL:http://www.drdobbs.com/database/blobs-and-object-oriented-database-engin/184408900

Figure 1


Copyright © 1992, Dr. Dobb's Journal

DEC92: BLOBS AND OBJECT-ORIENTED DATABASE ENGINES

BLOBS AND OBJECT-ORIENTED DATABASE ENGINES

Storing image and sound data

This article contains the following executables: DFLT15.ARC D15TX.ARC

Sam Felton

Sam is a software engineer at Raima Corporation and can be contacted at 1605 NW Sammamish Road, #200, Issaquah, WA 98027-5378.


One of the most valuable tools in any system or application designer's kit is a database engine--a library that, when linked into your application, gives your software the power of a database management system without having to write one yourself.

Utilizing object-oriented programming techniques to design a database engine gives you a set of base classes from which you can inherit a DBMS. But how do you use such a tool and what does it give you?

Among other things, this tool gives your objects persistence, meaning that objects "persist" within a database or file, even when the software that created them is not running. Thus, interface objects can be stored in a database as resources, and you can display, modify, and replace them as needed. Retrieval is easy--by key or by set, depending on the database model you choose. You can even create new objects by inheriting from more than one ancestor class and adding specific new functionality yourself.

Suppose, however, you want to store sound and image data in your database. No problem, right? A simple index should allow fast lookup and playback of this data.

Indexing is simple. But what about the data? You have no idea at compile time what size or form that data will take. How do you store an object of varying size when your database expects fixed-length records? And then there is the format--different methods must be developed to handle either the video or the sound. Ideally, you would want to create a single generic method for each activity; that is, Store() to place the item in the database, Retrieve() to get it out, and so on.

Thus, the GUI milieu has provided us with a new challenge--storing image and sound data. A new approach must be found.

The Revenge of the BLOB

The obvious approach to storing images and sound is to find a process of storing generic, variable-length objects in a database. In this article I'll demonstrate exactly that--a method for efficient, generic, large-entity storage.

In modern database parlance, sound and video data fall under the category known as "binary large objects," or BLOB for short. A BLOB is basically a large data stream in any format which is to be stored in the database.

One approach to storing BLOBs is to use an object-oriented database engine in conjunction with an object-oriented language. To illustrate how you can do this, I'll use as an example the Raima Object Manager (ROM), an object-oriented database engine my company produces. ROM is capable of storing records in both indexed and network-model methods. The network model is essential to my approach of storing BLOBs because it easily manages the relationship of items in a 1:n cardinality; more importantly, those items can be accessed with considerable speed. The mechanics are outside the scope of this article; suffice to say, however, that it is faster to traverse a linked list than to look up items in an index, and we need to be able to do both.

In addition to ROM, the other tools on my workbench include Borland's C++ compiler for MS-DOS and Windows (you could also use Microsoft's C/C++ or any other C++ 2.1-compliant compiler or preprocessor) and for interface design, the Zinc Interface Library (ZIL).

Grasping the Amorphous

The two most widely used conceptual models in database design are the relational and the network model. In relational systems, two-dimensional tables consisting of attributes (columns) and instances (rows or tuples) are defined. These tables (called "relations") are linked through indexing on individual columns that serve as keys by which you may locate that table.

The network model, on the other hand, uses entities called "records" (equivalent to rows in the relational model) that contain fields in which data is contained. They roughly resemble the Pascal RECORD or the C struct. These records are linked via the use of sets (typically a linked-list structure). These sets have a cardinality of 1:n--that is, one owner record may have many member records.

In ROM, we use both models to organize our data to build a database of keyed records, each of which will manage a multimember set. Each member of the set will contain tagless fields consisting of a different sized, fixed-length array of bytes. To store the BLOB, we'll use a best-fit slicing algorithm to divide it up into chunks of 500, 2000, and 4000 bytes. (After adding a few bytes for set-connection overhead, these sizes equate to commonly found disk-cluster sizes; thus, we can reduce the amount of external fragmentation created on the disk.)

Next, we create a set which links the manager object (DISP) with each record (BITSREC 1, 2, and 3); see Figure 1. We can use indexed access (for the DISP manager object, so that we may call up bitmaps by their ID) and the network access (for the BITSREC objects, so that we may reassemble quickly). The schema definition of this database is in Listing One (page 120), written in Raima Data Definition Language (RDDL), a C-like language in respects to its definition of records. Any atomic C data type can be used in a RDDL record--short, long, double, and so on.

In Listing One I've defined "tagless" fields in each record. This signifies to the DDL processor that the record is one large buffer, containing no individual fields. The tagless field definitions have defined a two-dimensional array of char as the storage buffer in the record because the DDL processor assumes that a one-dimensional array of bytes is a string, and C string-handling rules take effect. In this case, we do not want to treat a BLOB as a string!

The DDL processor reads this file and produces two files from it: blob.dbd, the database descriptor file that contains the structures the library objects use to access the database; and blob.h, which contains C structs that are the exact equivalent of the record structures in the database; see Listing Two, page 120. (This file is a C .H file instead of a C++ .HPP file because the Raima Database Manager, a non-object-oriented predecessor to ROM, uses this same file to allow C programmers to access the database.) I'll use multiple inheritance to incorporate these structs into our classes.

The next step is to define C++ classes. ROM provides an OmBitmap class specifically for Microsoft Windows bitmaps. This class is specially designed to manage BLOBs containing Windows bitmaps, and to be able to store and retrieve bitmaps to and from the clipboard, as well as Show() the bitmaps onto a given window handle. The OmBitmap class is actually derived from WinBlob, which derives from OmBlob, which derives from a multimember sethandler class called Polymorph (so named because multimember sets are inherently polymorphic). The OmBitmap class definition is shown in Listing Three, page 120.

From the OmBitmap class, we get all the tools necessary to manage the multiple BLOB chunks, which combined, form the body of the bitmap. Note that the OmBitmap has methods, such as LoadFromClip and CopyToHandle, that make it simple to have a Paste command that can read the Windows clipboard, and paste the bitmap right into our BLOB storage. These members are protected for the obvious reason that outsiders should not, as a rule, be manipulating access to the clipboard.

BLOBing for Bitmaps

The first thing to do is get the data into the database. To demonstrate this, I'll use code taken from examples in the ROM documentation. Since this code is fully documented and described in the toolkit, I'll only show portions here. Let's start with Listing Four, page 120.

Besides cheating on Windows again by using the winio functions, we have subclassed a StoreDb object to form our database, called "BitBlob." The database, once it has been created, can be instantiated inside of the StoreTask class.

The purpose of the StoreTask class is to provide session-by-session control over the databases and to make sure we can manage locking between concurrent tasks. Since this is C++, however, additional management features can be included. For example, if we decide that we needed multiple databases, we can manage their opening and closing here, as well as selecting their open modes, and so on. The task can act as a way of controlling accesses by MDI client windows, as well, if we choose to use that environment.

Next, notice the definition of the Display class coderived from StoreObj and the DDLP-produced struct, Disp. Disp effectively contains our data, as shown in Listing Two. The resultant object instance of this class will amount to displayable bitmaps; notice the Show() method, which displays the name of the key (DispName).

The STOREDIN macro associates an object with a particular database by assigning its private database pointer to the StoreDb-derived class specified. This is a convenient way to associate an object with its database.

The OWNEROF macro generates several member functions: the overloaded operator functions >>, which enable navigation to a given member object via the proper set; and the Members function, which returns the count of members in the set.

Note in the .CPP file that the main portion of the code is located in the RunProgram member of the StoreTask-derived class. RunProgram does a number of things, all from its menu. The s case calls the Save function. After you have copied the bitmap data into the BLOB's buffer area, you perform a UserNew to create corresponding instance records in the database. Then, you call Save to fill those records. The Save function divides BLOBs into their corresponding chunks, and then fills the database records as it goes, until it runs out of data to fill with. In doing so, you pass in a reference to a Display object, so that it knows which record instance is to manage it.

As a result of this operation, we've now stored an object and named it in the key portion of the Disp class, so it can be retrieved. This is done in the f case by getting a name, creating a KeyObj (which is really just a key instance), assigning the character string to search for, and passing the whole thing to Find, the Disp object's member function, which searches for that particular bitmap. Show, the BLOB's member function, displays it.

This illustrates keyed access. But what if we only want to display the BLOB bitmaps in order?

Listings Five (page 121) and Six (page 158) do this. The slide viewer uses ZIL to create a window in which we may display slides. As you can see from the code, we create a special window subclass in which we instantiate the task and objects.

We then associate the objects with actual persistent items by using the overloaded operator [], which has been modified to provide sequential access to instance records, thus associating them with a specific instance record. To display, merely convert the window's coordinates to logical coordinates, pass them to the Show member function, and blast away. The bitmap appears in the window.

The EVT_NEXT and EVT_PREVIOUS events call yet another pair of overloaded operators, the double-plus (++) and double-minus (--) functions. These have been overloaded to cause the memory object to associate itself with the sequentially following or preceding instance record in the database. It is very simple to read the code, and the actions of the operators are fairly intuitive. This makes the code much easier to write and maintain.

AfterBytes

As you can see, there are several ways to utilize BLOB objects. The important thing to remember is that the implementation is crucial. With the method defined by ROM, pieces are reassembled quickly and it's easy to add to or subtract from an existing BLOB's size and reorganize its indexing.


_BLOBS AND OBJECT-ORIENTED DATABASE ENGINES_
by Sam Felton


[LISTING ONE]


/* blob.ddl--contains the schema definition, written in Raima Data
Definition Language */

database BLOB [4048] {
    /* here we tell it into which file to put each object */
    data file "blob.d01" contains Disp;
    data file "blob.d02" contains BitsRec1;
    data file "blob.d03" contains BitsRec2;
    data file "blob.d04" contains BitsRec3;
    key file  "blob.key" contains DispName;

    record Disp
    {
        key char    DispName[21];   /* Name of our bitmap */
    }
    record BitsRec1             /* BLOB chunks... */
    {
        char bit1[1][4000];
    }
    record BitsRec2
    {
        char bit2[1][2000];
    }
    record BitsRec3
    {
        char bit3[1][512];
    }
    set Disp_BitMap
      {
        order last;
            owner Disp;
            member BitsRec1;
            member BitsRec2;
            member BitsRec3;
      }
}






[LISTING TWO]


/* Raima Data Manager Version 3.21A */
/* database blob record/key structure declarations */
struct disp {
   char  dispname[21];
};
struct bitsrec1 {
   char  bit1[1][4000];
};
struct bitsrec2 {
   char  bit2[1][2000];
};
struct bitsrec3 {
   char  bit3[1][512];
};
/* record, field and set table entry definitions */
/* File Id Constants */
/* Record Name Constants */
#define DISP 10000
#define BITSREC1 10001
#define BITSREC2 10002
#define BITSREC3 10003
/* Field Name Constants */
#define DISPNAME 0L
#define BIT1 1000L
#define BIT2 2000L
#define BIT3 3000L
/* Field Sizes */
#define SIZEOF_DISPNAME 21
#define SIZEOF_BIT1 4000
#define SIZEOF_BIT2 2000
#define SIZEOF_BIT3 512
/* Set Name Constants */
#define DISP_BITMAP 20000







[LISTING THREE]


// OMBITMAP.HPP-- C++ header file for Object Manager, an object storage class
//  library/ODBMS -- written by Paul Gallagher 1992 -- for C++ 2.0 or above
//  Copyright (C)1991 Raima Corporation All Rights Reserved
#ifdef WINDOWS
#ifndef OMBITMAP_HPP
#define OMBITMAP_HPP

#include <winblob.hpp>

class OM_EXPORT OmBitmap : public WinBlob
{
protected:
    BITMAP DB_FAR * Bmap;   // stored in Blob header
    int GetHeader(Pvoid p);
    int SetHeader(Pvoid, long len);
    T_F LoadFromClip(HANDLE hdata);
    HANDLE CopyToHandle();       // used to copy to clipboard
public:
    OmBitmap(HWND h,int set): WinBlob(h, set) { Bmap = new BITMAP; }
    ~OmBitmap() { delete Bmap; };
    WORD ClipFormat()   { return CF_BITMAP; }
    void Show(short xStart, short yStart);
};
#endif // OMBITMAP_HPP
#endif // WINDOWS






[LISTING FOUR]


// BITMAP.HPP
//---- Illustrates the creation and use of an OmBitMap class ---
#include <storedb.hpp>    // task/database ObjectManager includes
#include <storeobj.hpp>
#include "blob.h"         // blob database structures from DDLP
#include <keyobj.hpp>
#include <ombitmap.hpp>

extern "C" {
#include "..\winio\iosetup.h"
};
void RunProgram(HWND h);    // called from main
    //----- Database class defintions for blob database -----
class BitBlob : public StoreDb
{
public:
            // CONSTRUCTOR
    BitBlob();
    DEFINE_DB_LOCATOR;
};
    //----- Task class defintions -----
class BitBlobTask : public StoreTask
{    BitBlob    BlobDb;
};
class MyBitmaps;
    //-----Display class--The "owner" of the MyBitmap; contains name of bitmap
class Display : public StoreObj, public Disp
{
private:
    int RecType() { return DISP; }
public:
       // Display() default constructor is Keyed.
    Display() : StoreObj(KeyObj(DISPNAME)) {}
    void Show() { printf("%s\n",DispName); }
    T_F UserNew();
    STOREDIN(BitBlob);
    OWNEROF(MyBitmaps,DISP_BITMAP);
};
    //----- derive BitMap class -----
class MyBitmaps : public OmBitmap
{
private:
public:
    MyBitmaps(HWND h) : OmBitmap(h, DISP_BITMAP) {}
    ~MyBitmaps() {}
    void Show(short xStart, short yStart,char DB_FAR *name );
    STOREDIN(BitBlob);
    MEMBEROF(Display,DISP_BITMAP);
};

// BITMAP.CPP
#include "bitmap.hpp"
//----- Source for WinMain and Run Program -----
DB_INIT(BitBlob);  // For Borland, Initialize database pointer
extern "C" {
int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,
    LPSTR /*lpCmdLine*/, int nCmdShow)
{
    %
    %
    %
    BitBlobTask task;       // Instantiate a Task
    hw=  winio_hwnd();
    if (task.Okay())        // make sure task setup was okay
        RunProgram(hw);
    winio_close();
    return 0;
}
};
// Ok, it's not "pure Windows," but it's easier to understand. Using winio
// instead of real windows, but this could be replaced with a basic message loop....
    void
RunProgram(HWND hw)
{
    char tmp[60];
    Display    display;
    MyBitmaps    bm(hw);
    bm.Init();                      // Post-constructor setup routine
    for (int i=0; i < 10 ; i++)
       printf("\n");
 do
 {
    printf("a) Show all f)find    l) list   c) clip  p) paste\ns) save
            d)display D) DELETE q) quit  : ");
    gets(tmp);
    if (*tmp == 'q')
        break;
    switch (*tmp)
    {
    %
    %
    %
    case 's':       // SAVE THE BITMAP UNDER A NEW NAME
    {
        display.UserNew();           // CREATE A NEW PERSISTENT PART
        printf("Storing\n");
        bm.Save((StoreObj DB_FAR &)display); // SAVE MEMORY PART
                                             // INTO PERSISTENT PART
        break;
    }
    case 'd':       // DISPLAY THE BITMAP IN MEMORY
    {   printf("Display\n");
        bm.Show(0,0,(char DB_FAR *)display.DispName);
        gets(tmp);
        break;
    }
    case 'f':       // FIND A BITMAP BY NAME AND READ IT IN
    {   printf("Enter Bitmap name : ");
        gets(tmp);
        display.Find(KeyObj(DISPNAME,tmp));
        if (display.Okay())
        {
            display >> bm;
            bm.Show(0,0,(char DB_FAR *)display.DispName);
        }
        break;
    }
    }
 } while (1);
}
// ---- Objects Source--shows outputs the name of the bitmap over the Bitmap
    void
  MyBitmaps ::
Show(short xStart, short yStart, char DB_FAR *name )
{
    OmBitmap:: Show(xStart,yStart);  // Call Base class to show bitmap
    HDC hDC;                         // Get a window DC
    hDC = GetDC(hwind);
    TextOut(hDC,xStart,yStart,name,_fstrlen(name)); // Print the name
    ReleaseDC(hwind,hDC);
}
    T_F
  Display ::
UserNew()
{
    printf("Enter Display Name > ");
    gets (DispName);
    NewObj();
    return True;
}
 // Database BitBlob Constructor-must be in directory with the blob database
BitBlob :: BitBlob() : StoreDb("blob",PDB_LOCATOR)
{    if (Open() != True)
        printf("Error Opening Tutorial Database, Has it been initialized?\n");
}





[LISTING FIVE]


// tech.hpp - class definitions for slide player
// Revision history:  -- 1.00  SPF

#ifndef  TECH_HPP

#define  TECH_HPP
#define  EVT_QUIT       10000    // our event mapping
#define  EVT_NEXT       10001
#define  EVT_PREVIOUS   10002
#define  EVT_FIRST      10003

#include <storedb.hpp>    // task/database ObjectManager includes
#include <storeobj.hpp>
#include "blob.h"         // blob database structures from DDLP
#include <keyobj.hpp>
#include <ombitmap.hpp>

//// These should look familiar - they are essentially stolen from the
//// bitmap.hpp file, with slight exceptions
    // Database class defintions for blob database
class BitBlob : public StoreDb
{
public:
            // CONSTRUCTOR
    BitBlob();
    DEFINE_DB_LOCATOR;
};
    //---- Task class defintions ----
class BitBlobTask : public StoreTask
{    BitBlob    BlobDb;
};
class MyBitmaps;
    //---- Display class--"owner" of MyBitmap ----
class Display : public StoreObj, public disp
{
private:
    int RecType() { return DISP; }
public:
       // Display() default constructor is Keyed.
    Display() : StoreObj(KeyObj(DISPNAME)) {}
    STOREDIN(BitBlob);
    OWNEROF(MyBitmaps,DISP_BITMAP);
};
    //---- derive BitMap class ----
class MyBitmaps : public OmBitmap
{
private:
public:
    MyBitmaps(HWND h) : OmBitmap(h, DISP_BITMAP) {}
    ~MyBitmaps() {}
    STOREDIN(BitBlob);
    MEMBEROF(Display,DISP_BITMAP);
};
// Subclass of UIW_WINDOW for my own purposes
//  Welcome to ZIL, folks!
class EXPORT MY_WINDOW : public UIW_WINDOW
{
public:
   MY_WINDOW();
   virtual ~MY_WINDOW() {}
    EVENT_TYPE Event(const UI_EVENT &event); // our local event processor
private:
   static EVENT_TYPE FirstEvent(UI_WINDOW_OBJECT *object, UI_EVENT &event,
                                EVENT_TYPE ccode);
   static EVENT_TYPE QuitEvent(UI_WINDOW_OBJECT *object, UI_EVENT &event,
                               EVENT_TYPE ccode);
   static EVENT_TYPE NextEvent(UI_WINDOW_OBJECT *object, UI_EVENT &event,
                               EVENT_TYPE ccode);
   static EVENT_TYPE PreviousEvent(UI_WINDOW_OBJECT *object, UI_EVENT &event,
                                   EVENT_TYPE ccode);
   POINT display_origin;   // a place to start the bitmap display
   BitBlobTask task;    // our StoreTask subclass
   Display     display; // our display manager object
   MyBitmaps   bm;      // our OmBitmap subclass (bitmap data)
};
#endif //TECH_HPP







[LISTING SIX]


// tech.cpp -  methods for slide viewer program. This is a Bitmap slideshow
//  presentation program.It uses Raima Object Manager and the Zinc Interface
//  Library (ZIL) Revision history: 1.0   SPF

#define USE_MAIN_WIN TRUE
#define USE_RAW_KEYS

// tell compiler to use pre-compiled headers...
#pragma  hdrfile
#include <dos.h>
#include <stdlib.h>
#include <errno.h>
#include <iostream.h>
#include <ui_win.hpp>
#include <ui_evt.hpp>
#include <stdio.h>
#include <string.h>
#include <process.h>
#include <dir.h>
#include "dlg.hpp"
#include "tech.hpp"
#pragma  hdrstop

DB_INIT(BitBlob); // For Borland and many UNIX compilers/preprocessors, we must
                // initialize static pointers via a prototype before they can
                // be referenced anywhere (even in a constructor). Here, we
                // initialize the pointer to the database in the StoreTask item
                // This function is automated via a macro in Object Manager.
    // Database BitBlob Constructor
    // must be in the directory with the blob database
BitBlob :: BitBlob() : StoreDb("blob",PDB_LOCATOR)
{    if (Open() != True)
        printf("Error Opening Tutorial Database, Has it been initialized?\n");
}
// Constructor for our slide display window, Zinc style.
MY_WINDOW::MY_WINDOW()
          :UIW_WINDOW("DLG.DAT~MAIN_WIN"),
           bm(this->screenID) // screenID is the HWND in the object
{
    // Set the user functions to the pop-up items.
    UIW_POP_UP_ITEM *popup;
   unsigned short the_item;
   // Change point of display origin for bitmap from Client Coordinates to
   // Screen Coordinates for use by the Show() command
   display_origin.x = 5;
   display_origin.y = 10;
   ClientToScreen(this->screenID,&display_origin);

   // I know these look repetitive, but they are only done once,
   // so why waste the call overhead?
   the_item = QUIT_ITEM;
   popup = (UIW_POP_UP_ITEM *)this->Information(GET_NUMBERID_OBJECT,&the_item);
   popup->userFunction = MY_WINDOW::QuitEvent;
   popup->HotKey('Q');
   the_item = NEXT_SLIDE;
   popup = (UIW_POP_UP_ITEM *)this->Information(GET_NUMBERID_OBJECT,&the_item);
   popup->userFunction = MY_WINDOW::NextEvent;
   popup->HotKey('N');
   the_item = PREVIOUS_SLIDE;
   popup = (UIW_POP_UP_ITEM *)this->Information(GET_NUMBERID_OBJECT,&the_item);
   popup->userFunction = MY_WINDOW::PreviousEvent;
   popup->HotKey('P');
   the_item = FIRST_SLIDE;
   popup = (UIW_POP_UP_ITEM *)this->Information(GET_NUMBERID_OBJECT,&the_item);
   popup->userFunction = MY_WINDOW::FirstEvent;
   popup->HotKey('F');
   bm.Init();        //here, we initialize the BLOB instance - this must
                     // be done AFTER instantiation is complete
}
////////////////////////////////////EVENTS//////////////////////////////////
// First slide displayed
#pragma argsused
EVENT_TYPE MY_WINDOW::FirstEvent (UI_WINDOW_OBJECT *object, UI_EVENT &event,
                                  EVENT_TYPE ccode)
{
   UI_EVENT tEvent;
   tEvent = event;
   if (ccode != L_SELECT)
      return ccode;
   tEvent.type = EVT_FIRST;
   object->eventManager->Put(tEvent);
   return ccode;
}
// Next slide displayed
#pragma argsused
EVENT_TYPE MY_WINDOW::NextEvent (UI_WINDOW_OBJECT *object, UI_EVENT &event,
                                 EVENT_TYPE ccode)
{
   UI_EVENT tEvent;
   tEvent = event;
   if (ccode != L_SELECT)
      return ccode;
   tEvent.type = EVT_NEXT;
   object->eventManager->Put(tEvent);
   return ccode;
}
//Previous slide displayed
#pragma argsused
EVENT_TYPE MY_WINDOW::PreviousEvent (UI_WINDOW_OBJECT *object, UI_EVENT &event,
                                     EVENT_TYPE ccode)
{
   UI_EVENT tEvent;
   tEvent = event;
   if (ccode != L_SELECT)
      return ccode;
   tEvent.type = EVT_PREVIOUS;
   object->eventManager->Put(tEvent);
   return ccode;
}
// QuitEvent is cancel - bail out immediately
#pragma argsused
EVENT_TYPE MY_WINDOW::QuitEvent(UI_WINDOW_OBJECT *object, UI_EVENT &event,
                                EVENT_TYPE ccode)
{
    UI_EVENT tEvent;
    tEvent = event;
    if (ccode != L_SELECT)
        return ccode;
   tEvent = EVT_QUIT;
   object->eventManager->Put(tEvent);
   return ccode;
}
// This is our window's event switch...
EVENT_TYPE MY_WINDOW::Event(const UI_EVENT &event)
{
   EVENT_TYPE ccode = event.type;
   const UI_EVENT exit_event(L_EXIT);
   const UI_EVENT redisp(S_REDISPLAY);
    switch(ccode)
    {
   case EVT_QUIT:
      eventManager->Put(exit_event);
      break;
   case EVT_FIRST:
      display[FIRST];      // get our first bitmap
      if(display.Okay())
      {
         this->Event(redisp);
         display >> bm;    // navigate to blob
         // display it at these coords.
         bm.Show(display_origin.x, display_origin.y);
      }
      break;
   case EVT_NEXT:
      display++;           // next bitmap... etc...
      if(display.Okay())
      {
         this->Event(redisp);
         display >> bm;
         bm.Show(display_origin.x, display_origin.y);
      }
      break;
    case EVT_PREVIOUS:
      display--;           // previous bitmap... etc...
      if(display.Okay())
      {
         this->Event(redisp);
         display >> bm;
         bm.Show(display_origin.x, display_origin.y);
      }
      break;
   default:
        ccode = UIW_WINDOW::Event(event);
    };
    return ccode;
}
// WinMain, mostly Zinc stuff...
#pragma argsused
int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,
                   LPSTR lpCmdLine, int nCmdShow)
{
   // Initialize the display
   UI_DISPLAY *display = new UI_MSWINDOWS_DISPLAY(hInstance,
                                                      hPrevInstance, nCmdShow);
   // Create the event manager and add devices.
   UI_EVENT_MANAGER *eventManager = new UI_EVENT_MANAGER(display);
   *eventManager
      + new UID_KEYBOARD
      + new UID_MOUSE
      + new UID_CURSOR;
   // Create the window manager.
   UI_WINDOW_MANAGER *windowManager =
      new UI_WINDOW_MANAGER(display, eventManager);
   // Initialize error system, attach to UI_WINDOW_OBJECT static member
   UI_WINDOW_OBJECT::errorSystem = new UI_ERROR_SYSTEM;
   // Create our main window
   *windowManager + new MY_WINDOW;
   // Wait for user response.
   EVENT_TYPE ccode;
   do
   {
      UI_EVENT event;
      eventManager->Get(event, Q_NORMAL);
      ccode = windowManager->Event(event);
   } while (ccode != L_EXIT && ccode != S_NO_OBJECT);
   // Clean up.
   delete UI_WINDOW_OBJECT::errorSystem;
   UI_WINDOW_OBJECT::errorSystem = NULL;
   delete windowManager;
   delete eventManager;
   delete display;
   return (0);
}














Copyright © 1992, Dr. Dobb's Journal

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.