Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

Very Dynamic Linking in Windows


SP 94: Very Dynamic Linking in Windows

Craig is a founder and an officer of Enhanced Data Technology (Colorado Springs, CO), developers of the EnhancedView imaging-database tool. He is also the author of Practical Image Processing in C and Practical Ray Tracing in C, both published by John Wiley & Sons. Craig can be contacted at Enhanced Data or on CompuServe at 73552,3375.


No Windows programmer questions the usefulness of dynamically linked libraries (DLLs) in program development. The beauty of DLLs is that they promote modularity of code by segmenting functionality into individual, easily maintained modules. They promote code sharing because different applications can make calls into a DLL without being directly linked to it. Further, DLLs can be used to control run-time memory utilization in a Windows program by loading functionality when it is needed and unloading it when it is not. In fact, many modern application programs consist of a small executable program bundled with many DLLs that provide most of the program's functionality. This segmentation of functionality between EXEs and DLLs makes complex Windows programs easier to develop and maintain.

In this article, I'll present a technique that manages the interface between an application program and one or more DLLs, maximizing power and flexibility in both. This technique, which I call "dyna-linking," is useful whenever there is a one-to-many relationship between the application code and the DLLs necessary to inter-face that code into its run-time environment.

A DLL Backgrounder

Before Windows, if an application program needed to access functions contained in a function library, the code for the library had to be linked directly into the application program. The linker resolved all references made by the application program into the library during the linking process. When the linker's job was completed, the application-program code and the function-libraries code were merged together into a single executable file, the size of which was the combined total of the two bodies of code. In many cases, only a few of the functions in a library might have been needed by the application program, but all of the function-library code was linked with the application code whether or not it was ever used. This was one reason why DOS programs quickly consumed their allotted 640K of memory.

Windows designers understood this problem and took steps to correct it. They reasoned that not all functionality inherent in an application program had to be in memory simultaneously as long as it could be loaded and executed quickly when it was needed. Thus, DLLs were born. They are called "dynamically linked" because the resolution of function and data addresses previously performed by the linker at program-build time could now happen at run time. Therefore, the application program could find out where in the DLL certain data items or functions resided and call them as needed. The first reference to data or functions in a DLL causes the DLL to be loaded into memory (if it isn't in memory already). If references are never made, the DLL is never loaded. In short, DLLs led to smaller application programs that load and execute faster, increased modularity of code, and easier code maintainability.

As an aside, DLLs go by many names in the Windows environment. Usually they have a DLL file extension, but they can have other extensions, as well, including EXE, DRV, DS, and so on. They can contain executable code, ASCII character strings, icons, fonts, and just about anything else you can think of.

How Application Programs use DLLs

Static DLL linking is accomplished by running the import librarian program on a DLL to produce a "LIB" file, which is then linked into the Windows application using the linker in your development environment. This small file gives the application program information about the entry points within the DLL that can be used at run time to load and execute functions. From Windows' point of view, programs linked in this manner are a single entity. If you attempt to execute a statically linked program and Windows cannot find a DLL the application is linked with, you will receive a message stating that some component of the application program is missing. An application program can be statically linked to as many DLLs as it needs. Each DLL requires a separate LIB file to be linked into the application-program's executable.

An application program can directly manage the loading and unloading of component DLLs at run time using LoadLibrary and FreeLibrary function calls. Once a DLL is loaded successfully into memory, the application program uses the GetProcAddress function to get the addresses of all functions it needs to call within the DLL. The application program calls the functions within the DLL by using far pointers to the functions. When it is finished with a particular DLL, it calls FreeLibrary to unload it from memory. Note that the DLL will only be unloaded from memory if all applications using the DLL are finished with it. Windows keeps track of which DLLs are in use and will only unload a DLL when it is no longer needed.

Dynalinking

Dynalinking is an extension of the DLL direct-management technique. While you can think of dynalinking as a C++ class encapsulation of DLL direct management, it is actually much more. Dynalinking can be used to manage the interface between an application program and a single DLL, but it is much more powerful when used to adapt an application program to its environment using multiple DLLs. To illustrate this, consider an application program that:

  • Supports status/error messages in multiple languages.
  • Supports spell checking in multiple languages.
  • Interfaces to n specific hardware-interface boards (video digitizers, network-interface cards, and so on).
  • Is a word processor which needs to export its text into formats compatible with n different word processors.
  • Operates in 16-, 256-, or true-color graphic modes.
Each of these presents a scenario which could be satisfied using a single application program calling upon multiple DLLs to perform specific tasks. Dynalinking provides this service.

Good software design would place all common portions of the interface code in the application program, and device-specific code segmented into DLLs (the "drivers"). The major advantages of using dynalinking are that:

  • A single, well-defined API exists between the application program code and all dynalinked DLLs.
  • Only one version of the application program is needed for any configuration of the application. This results in less time spent in program builds and reduced maintenance costs.
  • Although the interface between the application program and all DLLs must be exactly the same, the functionality provided by the DLLs can be as similar or as different as required by the application.
  • The application program can dynamically switch between DLLs at run time with very little overhead.

The Demonstration Program

To illustrate dynalinking, I'm providing a demo program for providing program error/status messages in three different languages--English, German, and Japanese. Dynalinking eliminates the need for a different version of the application program for each language. Instead, all messages for each language are contained in separate DLLs. The DLL used for messages determines which language the application program supports. Thus, only one version of the application program is required to operate in three different languages. In the demo program, the language DLL is selected via a menu. In an actual application, the selection could be made by reading an entry in an INI file or reading an option bit programmed into a software lock device.

The demo program is comprised of one application program and three DLLs, one each for English, German, and Japanese. When the demo program is executed, the language to display a word in and the word to be displayed are selected by the user. The selected word is then displayed in a window in the selected language. Every time the language is changed, the old language DLL is unloaded and the newly selected language DLL is loaded--all with two lines of application code similar to Example 1. In this case, the current language DLL is unloaded and the German DLL is loaded.

How Dynalinking Works

The best way to understand how dynalinking works is to examine the listings. Listing One shows an important class-definition include file; Listing Two gives the English version of the DLL code; Listing Three shows the dynalink code; and the demo application program is in Listing Four . All of the files required to produce the DLLs and the demo application are available electronically; see "Availability," page 3.

The files message.hpp (Listing One) and engdll.cpp (Listing Two) define a very basic Windows DLL with a minimum of functionality. Notice that they contain the code for the MessageLookup C++ class in addition to the required LibMain function and optional WEP procedure. This code is compiled as a DLL with explicit functions exported in the large memory model using Borland C++ Version 3.1.

You'll notice that the constructor and destructor of the MessageLookup class pop up a message box just to prove that they were executed. In a real application, of course, these functions would probably do something useful. LookupMessage provides the only real functionality by returning a long pointer to a message, given a message ID. A message identifier of GOODMORNING-MSGID returns a pointer to the string "Good Morning" for example. The German and Japanese versions of this DLL are identical except for the strings they return. In the Japanese DLL, the message identifier GOODMORNINGMSGID returns a pointer to the string "Ohayo---gozaimasu." The German version returns "Guten Morgen."

The implementation of the dynalink technique itself takes place in dyna-link.cpp (Listing Three), which is always made a part of the application program. In this code, a class identical to MessageLookup (the interface class of the DLLs) is defined. This class, DLMessage-Lookup, has a one-for-one function mapping for each function defined within the DLL. The constructor for the DLMessageLookup class does most of the work. The parameter passed to the constructor of the DLMessageLookup class is the identifier of the DLL to be used. The identifier determines the filename of the DLL to be loaded into memory. Prior to loading the DLL, a variable LibError is defined and set True, indicating a loading error occurred. A problem loading the DLL is assumed until proven otherwise. Next, an attempt is made to load the DLL into memory using LoadLibrary. A return code of 33 or greater indicates a successful load. A load error causes the constructor to pop up a message box and subsequently terminate.

Next, some magic occurs. Most everyone who knows something about C++ knows that all methods (functions) carry around a hidden this parameter that points to the data for a specific class instance (or object). This is how objects keep track of their personal data while executing shared code. The this pointer is seldom explicitly referred to in software development, but it is there nevertheless. Since the dynalink code is interfacing with C++ objects in a DLL, the data for the DLL object must be allocated somewhere, and a pointer to this data must be passed to the DLL C++ functions for them to work correctly. In order to satisfy this requirement, I allocate a block of memory the size of the object (in this case, the size of MessageLookup), which will be used for the object's data. Locking this memory block produces a pointer that will become the this pointer for each of the DLL functions.

Next, function pointers are retrieved for each function within the DLL. The GetProcAddress function is repeatedly called with the entry-point number of a specific function within the DLL. The address returned is that of the function within the DLL. As shown in the listing, entry-point addresses are gotten for the constructor, the destructor, and the LookupMessage functions. If all three addresses are nonzero, indicating success, the constructor for the MessageLookup class is called via its function pointer, passing the synthesized this pointer. If all went well, the LibError flag is set False.

My LookupMessage has the same interface as the built-in LookupMessage function within the DLL we are trying to call. It accepts a message ID number as a parameter and returns a long pointer to the message string. When it is executed, a check is first made to verify that LibError is False. A call into a DLL that did not load correctly is destined to failure and will probably cause your machine to crash and burn. If there is no LibError, a call is made via the function pointer, and the LookupMessage function within the DLL is executed.

The destructor for the DLMessageLookup class gets executed when the DLMessageLookup object is deleted or goes out of scope. Again LibError is checked, and if False, the destructor within the DLL is executed to allow cleanup within the DLL. After that, the memory allocated for the object's data is unlocked and freed. Finally, the DLL is unloaded from memory without a trace.

All of the code surrounded by DEBUGDL statements in Listing Three is part of a serial-port debugger built into the dynalinked code. A debugger of this sort is probably not necessary for a DLL interface with so few functions, but it becomes a godsend when 30 to 40 DLL entry points must be monitored.

Extensions

The basic dynalink technique can incorporate a serial-port debugger to check the parameters and return values for each dyna-linked DLL function. This provides a unique way to unintrusively monitor the operation of the DLL functions at run time. To utilize the serial-debugging feature, the application program needs to be recompiled with DEBUGDL defined, which causes the serial code to be included in the application. An RS-232 terminal connected to the COM2 serial port will then display all calls made to the DLL. The serial parameters for the remote terminal are 9600 baud, 8 bits, no parity, and 1 stop bit. These parameters can, of course, be altered by changing the values hardcoded in the source code. Remote serial debugging is a very convenient way of debugging the application-program/DLL interface. Remember, however, if your application program also uses the COM2 port, there may be contention for this port, and neither your application nor the serial-port debugger will work correctly.

Building a dynalink interface to C instead of C++ code in a DLL is even easier than the method shown. This is because the calls to GetProcAddress can specify the ASCII names of the C functions within the DLL instead of their entry-point numbers, as is required for the C++ version. Entry-point numbers must be specified in the C++ version because of C++ name mangling. Also, the memory allocated for the MessageLookup object is not required for a C-code version and should be eliminated. Finally, all references to the this pointer must be eliminated.

Caveats

The dynalinking technique presented here has only been tested with Borland C++ 3.1. It may or may not work with other compilers.

Care must be taken when adding functions to dynalinked DLLs because it is possible to change the order of entry-point numbers hardcoded into the GetProcAddress calls. If you always add the code for new functions at the end of the listings, you'll be okay. If you insert the new code in the middle of the listings, the entry-point numbers will change. Use Borland's TDUMP, or a similar program, to determine the entry-point numbers of your DLL functions. Finally, for a dynalinked C++ class within a DLL, you must provide a constructor without parameters.

Closing Thoughts

I've been using dynalinking for over a year in our EnhancedView image-database software with no problems. In fact, the utilization of this technique has helped us maintain our sanity because it allows one version of our application program to interface to five different DLLs (one for each different image-digitizer board) under control of a bit encoded in a software lock. If this technique were not used, we would need five different versions of our application and five different sets of installation disks.

Example 1: Code that loads a newly selected DLL.

delete pMessageLookupClass;
pMessageLookupClass = new
   DLMessageLookup(GERMAN);

Listing One


/*********************************************************/
/***                  "message.hpp"                    ***/
/***          Message Class Interface File             ***/
/***                  DynaLink Demo                    ***/
/***                 Craig A. Lindley                  ***/
/***      Revision: 1.0     Last Update: 02/20/94      ***/
/*********************************************************/
// Check to see if this file already included
#ifndef MESSAGE_HPP
#define MESSAGE_HPP
#include <windows.h>
// These define the various message IDs
#define GOODMORNINGMSGID 100
#define GOODBYEMSGID     101
#define PLEASEMSGID      102
#define THANKYOUMSGID    103
#define CHEERSMSGID      104
/* The following is required when using a C++ class as an interface for a DLL.
It is required because this file will be included in both the application 
compilation and DLL compilation. All members (functions and data) are far!*/
#ifdef __DLL__
#  define EXPORT _export
#else
#  define EXPORT huge
#endif
class EXPORT MessageLookup {
  private:
  public:
    FAR  MessageLookup(void);     // Class constructor/destructor
    FAR ~MessageLookup(void);
    LPSTR FAR LookupMessage(WORD MessageID);// Lookup the message
};
#endif


Listing Two


/*********************************************************/
/***                  "engdll.cpp"                     ***/
/***        English Language Message DLL Code          ***/
/***                  DynaLink Demo                    ***/
/***                 Craig A. Lindley                  ***/
/***      Revision: 1.0     Last Update: 02/20/94      ***/
/*********************************************************/
#include "message.hpp"
MessageLookup::MessageLookup(void) {
  MessageBox(NULL, "English DLL Constructor Executed", "Status Message", 
                                                       MB_OK | MB_TASKMODAL);
}
MessageLookup::~MessageLookup(void) {
  MessageBox(NULL, "English DLL Destructor Executed", "Status Message", 
                                                        MB_OK | MB_TASKMODAL);
}
LPSTR MessageLookup::LookupMessage(WORD MessageID) {
  // Use MessageID value to find appropriate message
  switch(MessageID) {
    case GOODMORNINGMSGID:
      return "Good Morning";
    case GOODBYEMSGID:
      return "Goodbye";
    case PLEASEMSGID:
      return "Please";
    case THANKYOUMSGID:
      return "Thank You";
    case CHEERSMSGID:
      return "Cheers";
  }
  return "Unknown English Message ID";
}
// Library entry point for initialization
int CALLBACK LibMain (HANDLE, WORD, WORD wHeapSize,
                      LPSTR)  {
  if (wHeapSize > 0)
    UnlockData (0);
  return 1;
}
// Window's exit procedure for this DLL
int CALLBACK WEP(int)  {
  return(1);
}


Listing Three


/*********************************************************/
/***                  "dynalink.cpp"                   ***/
/***   This interface class provides for the dynamic   ***/
/***     linking of DLLs to application programs.      ***/
/***                 Craig A. Lindley                  ***/
/***      Revision: 1.0     Last Update: 02/20/94      ***/
/*********************************************************/
#include <windows.h>
#include "dynalink.hpp"
// The following code is used for debugging and is only compiled
// when DEBUGDL is defined.
#ifdef DEBUGDL
// Global variables for serial support
static int  DevID    = -1;
static BOOL DevError = FALSE;
// Initialize the COM2 port for serial output. Parameters are
// 9600 baud, 8 data bits, no parity and 1 stop bit.
BOOL InitCOM2(void) {
  int Error;
  DCB dcb;
  DevID = OpenComm("COM2", 256, 256);
  if (DevID < 0) {
    DevError = TRUE;
    return FALSE;
  }
  Error = BuildCommDCB("COM2:9600,n,8,1", &dcb);
  if (Error < 0) {
    DevError = TRUE;
    return FALSE;
  }
  // Force the use of XOFF/XON flow control
  dcb.fOutX = 1;
  dcb.fInX  = 1;
  Error = SetCommState(&dcb);
  if (Error < 0) {
    DevError = TRUE;
    return FALSE;
  }
  return TRUE;
}
// Write a string to the current serial port device
BOOL WriteString(LPSTR TheString) {
  COMSTAT CommState;
  int Offset, NumWritten;
  if (DevError) return FALSE;
  FlushComm(DevID, 0);            // Flush transmission queue
  Offset = NumWritten = 0;
  while (TRUE) {                  // Loop until complete
    NumWritten = WriteComm(DevID,  (LPSTR) &TheString[Offset],
               lstrlen((LPSTR) &TheString[Offset]));
    if (NumWritten < 0) {
      // Error writing data
      GetCommEventMask(DevID, 0xFFFF); // Clear the event mask
      return FALSE;               // return an error indication
    } else {
      Offset += NumWritten;
      // Wait until all characters are sent
      do {
        GetCommError(DevID, &CommState);
      } while(CommState.cbOutQue);
      if (Offset >= lstrlen(TheString)) {
        GetCommEventMask(DevID, 0xFFFF);
        return TRUE;
      }
    }
  }
}
// Write a formated string to the serial port
void WriteFormattedString(char *szFormat, ...)  {
  char szBuffer[256];
  char *pArguments;
  pArguments = (char *) &szFormat + sizeof(szFormat);
  wvsprintf(szBuffer, szFormat, pArguments);
  lstrcat(szBuffer,"\n\r");
  WriteString((LPSTR) szBuffer);
}
#endif

DLMessageLookup::DLMessageLookup(LanguageDLLType Language)  {
  char Buffer[20];
  DWORD FirstEntry;
#ifdef DEBUGDL
  InitCOM2();                     // Initialize the COM2 port
#endif
  LibError = TRUE;                // Asssume lib cannot be loaded
  FirstEntry = 2;                 // Normal first entry number in DLL
                                  // Lookup the name of DLL from identifer
  switch(Language)  {
    case ENGLISH:
      lstrcpy(Buffer, "ENGDLL.DLL");
      break;
        
    case GERMAN:
      lstrcpy(Buffer, "GERDLL.DLL");
      break; 
    case JAPANESE:
      lstrcpy(Buffer, "JAPDLL.DLL");
      break;
        
    default:
      MessageBox(GetFocus(), "Unknown language library file, 
                                               cannot continue!",NULL, MB_OK);
      return;
  }
#ifdef DEBUGDL
  WriteFormattedString("\n\r%s version of language DLL", Buffer);
#endif
  // Attempt to load the appropriate imaging DLL
  hLib = LoadLibrary((LPSTR) Buffer);
  if ((UINT) hLib <= 32)  {
    MessageBox(GetFocus(), "Error loading library file!", NULL, MB_OK);
    return;
  }
  // When we get here, the appropriate library DLL has been loaded.
  // Alloc a chunk of memory for the MessageLookup class. Ptr to memory
  // will be passed as this pointer to all class functions.
  hMessageLookupClass = GlobalAlloc(GHND, sizeof(MessageLookup));
  if (!hMessageLookupClass) {
    MessageBox(GetFocus(), "Not enough memory for MessageLookup class!", 
                                                                 NULL, MB_OK);
    return;
  }
  // Get pointer for data to be used as this pointer.
  pMessageLookupClass = (MessageLookup *) GlobalLock(hMessageLookupClass);
  // Get all of the function pointers. NOTE: because this are fetched using
  // ordinal numbers, these number will change as new functions are added
  // to the class or rearranged.
  (FARCPROC) lpConstructorFn   = (FARCPROC) GetProcAddress(hLib, 
                                                         (LPSTR) FirstEntry+2);
  (FARCPROC) lpDestructorFn    = (FARCPROC) GetProcAddress(hLib, 
                                                         (LPSTR) FirstEntry+1);
  (FARCPROC) lpLookupMessageFn = (FARCPROC) GetProcAddress(hLib, 
                                                         (LPSTR) FirstEntry);
  // Make sure all entry points to the DLL were resolved
  if (lpConstructorFn && lpDestructorFn && lpLookupMessageFn) {
    (*lpConstructorFn)(pMessageLookupClass);// Execute the constructor
    LibError = FALSE;                       // Indicate no error
  } else
    MessageBox(GetFocus(), "Error accessing DLL functions!", NULL, MB_OK);
}
// Destructor for the DLMessageLookup class
void DLMessageLookup::~DLMessageLookup(void)  {
#ifdef DEBUGDL
  WriteFormattedString("MessageLookup DLL unloading");
  CloseComm(DevID);
#endif
  if (!LibError)                  // Run destructor only if constructor
    (*lpDestructorFn)(pMessageLookupClass);  // ran ok.
  if (hMessageLookupClass)  {     // Free class data memory
    GlobalUnlock(hMessageLookupClass);
    GlobalFree(hMessageLookupClass);
  }
  // Free the DLL library
  if ((UINT) hLib > 32)
    FreeLibrary(hLib);
}
LPSTR DLMessageLookup::LookupMessage(WORD MessageID) {
  if (LibError)              // If error detected
    return 0;
#ifdef DEBUGDL
  WriteFormattedString("LookupMessage: message id %d", MessageID);
#endif
  return ((*lpLookupMessageFn)(pMessageLookupClass, MessageID));
}


Listing Four


/*********************************************************/
/***                   "app.cpp"                       ***/
/***       A simple demo program to illustrate         ***/
/***           the concept of dynalinking.             ***/
/***                 Craig A. Lindley                  ***/
/***      Revision: 1.0     Last Update: 02/20/94      ***/
/*********************************************************/
/* This demo program demonstrates the dynalink technique described in the
accompanying article. The demo application is written in Borland's OWL.
It allows the user to select the language to be used for program
messages and then a word to display in the selected language.*/
#include <owl.h>
#include "app.h"
#include "dynalink.hpp"
class TSampleDynalinkApp : public TApplication {
  public:
    TSampleDynalinkApp(LPSTR AName, HINSTANCE hInstance,
                       HINSTANCE hPrevInstance,
                       LPSTR lpCmdLine, int nCmdShow)
    : TApplication(AName, hInstance, hPrevInstance,
                   lpCmdLine, nCmdShow) {};
    virtual void InitMainWindow();
};
_CLASSDEF(TAppWindow)
class TAppWindow : public TWindow {
  private:
    // Private functions
    // Language select menu item functions
    virtual void CMEnglish(RTMessage Msg)     = [CM_FIRST + CM_ENGLISH];
    virtual void CMGerman(RTMessage Msg)      = [CM_FIRST + CM_GERMAN];
    virtual void CMJapanese(RTMessage Msg)    = [CM_FIRST + CM_JAPANESE];
    // Word select menu item functions
    virtual void CMGoodMorning(RTMessage Msg) = [CM_FIRST + CM_GOODMORNING];
    virtual void CMGoodbye(RTMessage Msg)     = [CM_FIRST + CM_GOODBYE];
    virtual void CMPlease(RTMessage Msg)      = [CM_FIRST + CM_PLEASE];
    virtual void CMThankyou(RTMessage Msg)    = [CM_FIRST + CM_THANKYOU];
    virtual void CMCheers(RTMessage Msg)      = [CM_FIRST + CM_CHEERS];
    // The about function
    virtual void CMAbout(RTMessage Msg)       = [CM_FIRST + CM_ABOUT];
    // The paint function
    virtual void Paint(HDC PaintDC, PAINTSTRUCT _FAR & PaintInfo);
    // Private data
    DLMessageLookup *pMessageLookupClass;   // Pointer to DL class
    int CurrentMessageID;                   // ID of selected message
  public:
    TAppWindow(PTWindowsObject AParent, LPSTR ATitle);
   ~TAppWindow();
};
// Window Class Constructor
TAppWindow::TAppWindow(PTWindowsObject AParent, LPSTR ATitle)
             : TWindow(AParent, ATitle) {
 
  // First assign the command menu to the window
  AssignMenu("CmdMenu");          
  // Next, instantiate the English message DLL by default
  pMessageLookupClass = new DLMessageLookup(ENGLISH);
  CurrentMessageID = -1;          // No message yet selected
}
// Window Class Destructor
TAppWindow::~TAppWindow() {
  delete pMessageLookupClass;     // Delete the MessageLookup class object
}
// Window paint function. Displays the selected word in the center of
// the window, whenever window is repainted.
void TAppWindow::Paint(HDC PaintDC, PAINTSTRUCT _FAR & PaintInfo) {
  if (CurrentMessageID != -1) {   // Display only if word has been selected
    DrawText(PaintDC, pMessageLookupClass->LookupMessage(CurrentMessageID),
             -1, &PaintInfo.rcPaint,
             DT_SINGLELINE | DT_CENTER | DT_VCENTER);
  }
}
// This function is called when English language is selected.
void TAppWindow::CMEnglish(RTMessage) {
  // First check this menu selection and uncheck others
  HMENU hMenu = GetMenu(HWindow);
  CheckMenuItem(hMenu, CM_ENGLISH,  MF_CHECKED);
  CheckMenuItem(hMenu, CM_GERMAN,   MF_UNCHECKED);
  CheckMenuItem(hMenu, CM_JAPANESE, MF_UNCHECKED);
  // Delete current message DLL and then instantiate the new one
  delete pMessageLookupClass;
  pMessageLookupClass = new DLMessageLookup(ENGLISH);
  // Invalidate window to cause a repaint
  InvalidateRect(HWindow, NULL, TRUE);
}
// This function is called when German language is selected.
void TAppWindow::CMGerman(RTMessage) {
  // First check this menu selection and uncheck others
  HMENU hMenu = GetMenu(HWindow);
  CheckMenuItem(hMenu, CM_GERMAN,   MF_CHECKED);
  CheckMenuItem(hMenu, CM_ENGLISH,  MF_UNCHECKED);
  CheckMenuItem(hMenu, CM_JAPANESE, MF_UNCHECKED);
  // Delete current message DLL and then instantiate the new one
  delete pMessageLookupClass;
  pMessageLookupClass = new DLMessageLookup(GERMAN);
  // Invalidate window to cause a repaint
  InvalidateRect(HWindow, NULL, TRUE);
}
// This function is called when Japanese language is selected.
void TAppWindow::CMJapanese(RTMessage) {
  // First check this menu selection and uncheck others
  HMENU hMenu = GetMenu(HWindow);
  CheckMenuItem(hMenu, CM_JAPANESE, MF_CHECKED);
  CheckMenuItem(hMenu, CM_ENGLISH,  MF_UNCHECKED);
  CheckMenuItem(hMenu, CM_GERMAN,   MF_UNCHECKED);
  // Delete current message DLL and then instantiate the new one
  delete pMessageLookupClass;
  pMessageLookupClass = new DLMessageLookup(JAPANESE);
  // Invalidate window to cause a repaint
  InvalidateRect(HWindow, NULL, TRUE);
}
// The following functions select the word to display
// This function is called Good Morning is clicked.
void TAppWindow::CMGoodMorning(RTMessage) {
  // First check this menu selection and uncheck others
  HMENU hMenu = GetMenu(HWindow);
  CheckMenuItem(hMenu, CM_GOODMORNING, MF_CHECKED);
  CheckMenuItem(hMenu, CM_GOODBYE,  MF_UNCHECKED);
  CheckMenuItem(hMenu, CM_PLEASE,   MF_UNCHECKED);
  CheckMenuItem(hMenu, CM_THANKYOU, MF_UNCHECKED);
  CheckMenuItem(hMenu, CM_CHEERS,   MF_UNCHECKED);
  // Set the new message ID
  CurrentMessageID = GOODMORNINGMSGID;
  // Invalidate window to cause a repaint
  InvalidateRect(HWindow, NULL, TRUE);
}
// This function is called when Goodbye is clicked.
void TAppWindow::CMGoodbye(RTMessage) {
  // First check this menu selection and uncheck others
  HMENU hMenu = GetMenu(HWindow);
  CheckMenuItem(hMenu, CM_GOODBYE,  MF_CHECKED);
  CheckMenuItem(hMenu, CM_GOODMORNING, MF_UNCHECKED);
  CheckMenuItem(hMenu, CM_PLEASE,   MF_UNCHECKED);
  CheckMenuItem(hMenu, CM_THANKYOU, MF_UNCHECKED);
  CheckMenuItem(hMenu, CM_CHEERS,   MF_UNCHECKED);
  // Set the new message ID
  CurrentMessageID = GOODBYEMSGID;
  // Invalidate window to cause a repaint
  InvalidateRect(HWindow, NULL, TRUE);
}
// This function is called when Please is clicked.
void TAppWindow::CMPlease(RTMessage) {
  // First check this menu selection and uncheck others
  HMENU hMenu = GetMenu(HWindow);
  CheckMenuItem(hMenu, CM_PLEASE,   MF_CHECKED);
  CheckMenuItem(hMenu, CM_GOODMORNING, MF_UNCHECKED);
  CheckMenuItem(hMenu, CM_GOODBYE,  MF_UNCHECKED);
  CheckMenuItem(hMenu, CM_THANKYOU, MF_UNCHECKED);
  CheckMenuItem(hMenu, CM_CHEERS,   MF_UNCHECKED);
  // Set the new message ID
  CurrentMessageID = PLEASEMSGID;
  // Invalidate window to cause a repaint
  InvalidateRect(HWindow, NULL, TRUE);
}
// This function is called when Thankyou is clicked.
void TAppWindow::CMThankyou(RTMessage) {
  // First check this menu selection and uncheck others
  HMENU hMenu = GetMenu(HWindow);
  CheckMenuItem(hMenu, CM_THANKYOU, MF_CHECKED);
  CheckMenuItem(hMenu, CM_GOODMORNING, MF_UNCHECKED);
  CheckMenuItem(hMenu, CM_GOODBYE,  MF_UNCHECKED);
  CheckMenuItem(hMenu, CM_PLEASE,   MF_UNCHECKED);
  CheckMenuItem(hMenu, CM_CHEERS,   MF_UNCHECKED);
  // Set the new message ID
  CurrentMessageID = THANKYOUMSGID;
  // Invalidate window to cause a repaint
  InvalidateRect(HWindow, NULL, TRUE);
}
// This function is called when Cheers is clicked.
void TAppWindow::CMCheers(RTMessage) {
  // First check this menu selection and uncheck others
  HMENU hMenu = GetMenu(HWindow);
  CheckMenuItem(hMenu, CM_CHEERS,   MF_CHECKED);
  CheckMenuItem(hMenu, CM_GOODMORNING, MF_UNCHECKED);
  CheckMenuItem(hMenu, CM_GOODBYE,  MF_UNCHECKED);
  CheckMenuItem(hMenu, CM_PLEASE,   MF_UNCHECKED);
  CheckMenuItem(hMenu, CM_THANKYOU, MF_UNCHECKED);
  // Set the new message ID
  CurrentMessageID = CHEERSMSGID;
  // Invalidate window to cause a repaint
  InvalidateRect(HWindow, NULL, TRUE);
}
// This function is called when the About menu item is clicked
void TAppWindow::CMAbout(RTMessage) {
  MessageBox(HWindow, "by Craig A. Lindley",
                      "Demo Dynalink Application", MB_OK);
}
void TSampleDynalinkApp::InitMainWindow() {
  MainWindow = new TAppWindow(NULL, Name);
}
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine, int nCmdShow) {
  TSampleDynalinkApp MyApp("Demo Dynalink Application Program",
                           hInstance, hPrevInstance,
                           lpCmdLine, nCmdShow);
  MyApp.nCmdShow = SW_SHOWMAXIMIZED;
  MyApp.Run();
  return MyApp.Status;
}


Copyright © 1994, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.