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

C/C++

What's in a Name?


Dr. Dobb's Sourcebook March/April 1997: 20/20

Al is a consultant and author based in the Houston area. His most recent book is Developing ActiveX Web Controls (Coriolis Group, 1996). You can find Al on the Web at http://www.al-williams.com/awc/.


Sidebar: About Mapped Files

What's in a name? Shakespeare wrote, "That which we call a rose by any other name would smell as sweet." Today, we live with the opposite effect. A more modern bard might write, "If you've got old coffee grounds, call 'em roses and sell 'em."

These days, many products share similar names, but there is no clear connection between them. Product adjectives are frequently used (or abused) in this way. A few days ago, I saw a sign in the airport: "Free chicken sandwich with purchase of golden fries and ice cold drink."

It probably should have read: "Free chicken sandwich with purchase of overpriced, soggy fries and tepid, watery drink." Of course, that would probably cut down on business. I say probably, since it was in an airport.

Software certainly has its share of poorly defined product categories and adjectives. I doubt that the $5.99 desktop publisher I see in the discount bin at WalMart is the equal of Ventura. How many packages say, "Weak features! Difficult to learn! Almost no online help!"? Not enough of them, that's for certain.

Although Visual C++ has the word "visual" in the title, it isn't as visual as you'd like. Also, to effectively use it, you need a lot of knowledge about the Windows API. Visual Basic is very visual, but has some limitations. The current version interprets code, has limited object-oriented abilities, and doesn't facilitate certain programming techniques. Delphi is more capable and very visual. However, many people don't like Pascal.

When people tell me they want a highly graphical C++ environment, I ask them if they have looked at Powersoft's Optima++. Not to knock the other choices -- I program quite a bit in all of the products I've mentioned -- still, Optima++ is a very visual C++ Rapid Application Development (RAD) tool. Although many people associate Optima++ with Powersoft's flagship PowerBuilder, it really doesn't have much to do with it.

This month I'll examine Optima++ and discuss what it will and won't do. I'll also show you some tricks for working around one of Optima's major limitations. Along the way, we'll see some interesting visual techniques that will probably sneak into some other visual languages sooner or later.

Family Tree

Underneath Optima's graphical exterior is the well-respected Watcom C++ compiler. Watcom was always a leader in generating tight code and tracking C++ changes, so Optima is no slouch in these departments.

What Optima brings to the mix is a reasonably comprehensive Windows class library and a highly integrated toolset that takes advantage of the library. In addition, the environment strives to hide the build process. The net effect is a Visual Basic-like environment that uses C++ and generates outstanding code.

The Downside

Before you throw your current tools in the garbage, you should know a few negatives about Optima. First, the class library is proprietary. True, Microsoft's MFC is proprietary, too. However, MFC's popularity and support from multiple vendors means you can find tools, support, and programmers for it. Optima is not as popular.

Another drawback is that Optima doesn't ship with the usual Windows API support you expect in compilers from Microsoft or Borland. You can use the SDK if you have a copy from another source, but that is a lot of trouble if you only need one or two calls. Though most common tasks are in the Optima class library, there is always something special you need, especially new functions, that aren't in the Optima library yet. However, I'll show you some tricks you can use to circumvent this problem.

Starting an Optima Project

Starting an Optima project is more like starting a Visual Basic program than a Visual C++ project. Optima creates an empty form. Usually, you'll just start dropping components like buttons, edit controls, and so forth, on the form. Each component automatically receives a variable that points to it in the form object. Optima takes care of creating and destroying the objects as well as initializing them to your design-time defaults.

If you want something more complex (for example, a multiple document interface program), you can run Optima's target wizard and select the type of new project you'd like to create. Some components are not immediately visible. For example, a message box component doesn't show up until you activate it. That may seem silly, but it provides you with an easy way to create the message-box object in your code. It also allows you to use drag-and-drop programming with nonvisible objects.

Drag-and-Drop Programming

The ability to drag and drop components on to forms is the bare-minimum element a development tool needs to be visual. Optima goes beyond this with a technique they call drag-and-drop programming. This is what makes Optima distinct from most other Windows environments.

The idea behind drag-and-drop programming is that you can get Optima to write code for you by dragging components into your code window. Suppose you have a form that contains a button and a message-box component. You right-click on the button and create an empty handler for the button click. In most other languages, you'll now have to write some code to bring up the message box. You either know what to type, or you don't.

With Optima, you simply drag the message-box component to the code window. When the cursor is over the event handler, let the component drop. Optima then displays a reference card (see Figure 1). The reference card shows all the things you can do with a message box. You select the action you want and the reference card prompts you for arguments. If you don't know what to put in a slot, you can ask for help. When you are done, the reference card writes all the code for you and puts it in the handler. Now that's visual!

You can display the reference card without dragging components into it. That's useful when you need to work with an object that isn't visible.

This is the closest thing to the famous NeXT Interface Builder (IB) that I have seen for the Windows environment. Every programmer should at least see the IB to know what you're missing. Too bad IB only works on NeXTStep and uses Objective C -- a somewhat obscure dialect of C that incorporates late binding OOP (sort of C meets SmallTalk).

An Example Program

To demonstrate Optima, I wrote a simple file-encryption system. You could encrypt a document before e-mailing it, or encrypt a software upgrade for Internet distribution.

The idea is simple. Provide a user interface to select a file and a password. If you press a command button, the program will encrypt the file, overwriting the current file contents. Another button causes the program to decrypt the file. You can see the end result in Figure 2.

The encryption technique is simple but effective against casual attacks. First, the program XORs each byte in the file with a fixed key phrase. This helps to randomize the input stream. Next, the program XORs again using the key phrase entered in the user interface. The program uses separate steps for encryption and decryption in case you want to use an algorithm that isn't reversible. As it stands now, encrypting a file twice with the same key phrase results in the original file.

Since the program converts the file in place, this is a natural place to use Win32 mapped files (see the accompanying text box entitled "About Mapped Files"). However, Optima has no support for mapped files. I'll show you how to get around that problem later.

Developing the user interface is simple. First, you can drag the visual elements from the component palette to the main form (see Table 1). You'll also place a standard file dialog component and a message-box component on the form. These items won't be visible initially, but you'll need them to complete the interface. Notice that the progress bar near the bottom of Figure 2 is not visible at first, so be sure to set its Visible property to False.

Thanks to the reference card, it is easy to hook up the browse button. Right-click on the button and select its Event entry. Then select Click to install a click handler function. Once you are in the code window, drag the file-open dialog into the code window. From the reference card (see Figure 1), select Parameters and fill in the parameter choices (Figure 3). You can then use the reference card to write the code to pull the file name from the box.

You can easily add the other code and events. You can use the Create User Function item on the File menu to create functions without having to manually alter the header and the CPP file. The difficult part is figuring out how to call the mapped file API calls.

Adding API Calls

The biggest problem with Optima is that it doesn't include the usual Windows API headers and libraries. If you only use the Optima library, that's fine. However, if you need anything out of the ordinary, you'll want to use the API. This is especially true when you want to use some new technology that Optima does not yet support.

If you have the Win32 SDK, you can use it with Optima (the manual explains how, and it is straightforward). However, if you don't have the SDK, you have a problem. In particular, if you only need a few API calls, it is an incredible amount of trouble to acquire and install the entire SDK.

Luckily, Optima allows you to dynamically load DLLs and call their functions. The process is a bit more work than using the Win32 SDK, but it doesn't require any extra software.

Remember, the Windows API is just a bunch of DLLs (USER32, KERNEL32, GDI32, and the like). If you know the name of the function you want to call, the arguments it expects, and the DLL it resides in, you can call it. If you have a book or magazine article that discusses a particular call, you can probably figure out the name and the arguments. You will rarely find mention of the DLL location, but you can usually figure it out (see Table 2). At worst, you can try different names until one works. You can also examine the system DLLs using Quick View. You can install Quick View from your original Windows CD. Then, just right-click on a DLL, select Quick View, and you'll find a wealth of information (including exported functions; see Figure 4).

Here are the steps required to call an API function:

  1. Create a function-pointer variable.
  2. Create a WModule variable to represent the DLL that holds the function (or functions).
  3. Load the appropriate DLL using WModule::Load.
  4. Initialize the pointer from step 1 by calling FindProcedure.
  5. Call the function using the pointer.

In standard K&R C, you have to call function pointers with an ugly syntax (*fp)(10);. However, modern compilers (including Optima) accept an alternate syntax that is much neater, as in fp(10);. If you need more than one function from a single DLL, you can repeat steps 1 and 4 as required. Following these steps, it isn't hard to find and use the mapped file calls. You can see the required code in the Form1::Form1 function in Listing One.

Competitors

For now, Optima has one of the best visual interfaces around (using my definition of visual, anyway). However, other products have their advantages, too.

If you've checked out Visual Basic 5, you'll see that it has enhancements that make it a bit easier to write code. For example, when you type an object name and press the period key, VB displays a list of members available for that object. Nice, but not as nice as Optima's reference card. Still, at least VB (and other vendors) are heading in that direction. Of course, VB5 has its own charms (for instance, ActiveX control creation) that Optima lacks.

Optimal Optima?

Should every one rush right out and switch to Optima? No. If you are happy with what you are using, stick with it. Optima is neat, but it doesn't have any overwhelming or compelling advantage over other products. On the other hand, if you are shopping for a development platform or you aren't satisfied with your current tools, you should give Optima a close look. If you are already a C++ programmer, Optima is even more attractive.

Internet Mania

One oddity about writing magazine columns is that you'll read this several months after I write it. As I write this, the holiday season is upon us and I'm contemplating a new year. As January approaches, I find myself ruminating more about the Internet and its place in our lives as developers.

Writing HTML files now feels like taking a time machine back to the days of writing old Fortran 66 code: not much structure or modularity except what you impose on yourself. I finally started running my HTML through a preprocessor so I can include files and use macros.

What are your experiences? Do you consider a tool like Front Page a visual programming tool? If VBScript and JavaScript ran equally well on all browsers, which would you prefer? Drop me a note and let me know. You can find me on the Web (where else?) at http://www.al-williams.com/ awc/. While you are there, take the programmer's web tour and see some of my favorite programming-related sites.

It will be interesting to see what tool is the most "visual" at the beginning of 1998. I'll let you know what I think "next year."

DDJ

Listing One

/*
    form1.cpp


</p>
    NOTE: This file is a generated file.
          Do not modify it by hand!
*/


</p>


</p>
#include "wpch.hpp"
#include "form1.hpp"


</p>
// Code added here will be included at the top of the .CPP file
// Variables for API calls
HANDLE (__stdcall *CreateFile)(char const * lpFileName, DWORD dwDesiredAccess,  
    DWORD dwShareMode,  void * lpSecurityAttributes,    
    DWORD dwCreationDistribution, DWORD dwFlagsAndAttributes,   
    HANDLE hTemplateFile);
HANDLE (__stdcall *CreateFileMapping)(HANDLE hFile,
        void * lpFileMappingAttributes,
        DWORD flProtect,    
        DWORD dwMaximumSizeHigh,    
        DWORD dwMaximumSizeLow, 
        char const * lpName);   
void * (__stdcall *MapViewOfFile)(HANDLE hFileMappingObject,    
       DWORD dwDesiredAccess, DWORD dwFileOffsetHigh,   
       DWORD dwFileOffsetLow, DWORD dwNumberOfBytesToMap);
BOOL (__stdcall *UnmapViewOfFile)(void * lpBaseAddress);
BOOL (__stdcall *CloseHandle)( HANDLE hObject);
DWORD (__stdcall *GetFileSize)(HANDLE hFile,DWORD *hiword);


</p>
// Fixed Key
#define FIXEDKEY "Resistance is futile"


</p>
//  Include definitions for resources.
#include "WRes.h"
WForm *_CreateForm1( void )
{
    extern WForm * __MainForm;
    Form1 * __form;


</p>
    __form = new Form1;
    if( __form != NULL ) {
        if( __MainForm == NULL ) {
            __MainForm = __form;
        }
        if( !__form->Initialize() || !__form->Create() ) {
            delete __form;
            __form = NULL;
        }
    }
    return( __form );
}


</p>
void _DeleteForm1( WForm * __item )
{
    delete  __item;
}


</p>
__Form1_Base::__Form1_Base() : WModelessDialog()
    , file_name( NULL )
    , Browse( NULL )
    , key( NULL )
    , FileDialog( NULL )


</p>
    , encrypt( NULL )
    , label_1( NULL )
    , label_2( NULL )
    , progb_1( NULL )
    , msg_1( NULL )
    , decrypt( NULL )
{
    static WEventHandlerList  __BaseHandlers[] = {
        { WCreateEvent, (WEventHandler)&__Form1_Base::__Base_Create },
        { WCloseEvent,  (WEventHandler)&__Form1_Base::__Base_Close },
        { WDestroyEvent,(WEventHandler)&__Form1_Base::__Base_Destroy },
        { WNullEvent,   (WEventHandler)NULL },
    };
    SetEventHandlerList( __BaseHandlers );
}


</p>
__Form1_Base::~__Form1_Base()
{
    Destroy();
}


</p>
WBool __Form1_Base::Create( WWindow * __parent )
{
    WBool  __ok;


</p>
    __ok = WWindow::Create(  __parent, WResourceID("ID_Form1") );
    return( __ok );
}


</p>
WBool __Form1_Base::__Base_Create( WObject *, WCreateEventData * __event )
{
    Center();


</p>
    file_name             = new WTextBox;
    Browse                = new WCommandButton;
    key                   = new WTextBox;
    FileDialog            = new WFileDialog;
    encrypt               = new WCommandButton;
    label_1               = new WLabel;
    label_2               = new WLabel;
    progb_1               = new WProgressBar;
    msg_1                 = new WMessageBox;
    decrypt               = new WCommandButton;


</p>
    file_name->Create( this, ID_file_name );
    Browse->Create( this, ID_Browse );
    key->Create( this, ID_key );
    encrypt->Create( this, ID_encrypt );
    label_1->Create( this, ID_label_1 );
    label_2->Create( this, ID_label_2 );
    progb_1->Create( this, ID_progb_1 );
    decrypt->Create( this, ID_decrypt );


</p>
    SetFont( WFont( "8.MS Sans Serif" ) );


</p>
    SetName( "Form1" );
    file_name->SetName( "file_name" );
    Browse->SetPressed( false );
    Browse->SetName( "Browse" );
    key->SetName( "key" );
    FileDialog->SetOwner( this );
    FileDialog->SetStyle( WFDSHideReadOnly | WFDSNoChangeDir | WFDSEnableHook | WFDSPathMustExist | WFDSFileMustExist | WFDSExplorer );
    FileDialog->SetButtonLabel( "&Open" );
    FileDialog->SetInitialDirectory( WFilePath( "C:\\" ) );
    FileDialog->SetTitle( "Open File" );
    encrypt->SetPressed( false );
    encrypt->SetName( "encrypt" );
    label_1->SetName( "label_1" );
    label_2->SetName( "label_2" );
    progb_1->SetBarStyle( WProgressBarStyleNormal );
    progb_1->SetPosition( 0 );
    progb_1->SetName( "progb_1" );
    decrypt->SetPressed( false );
    decrypt->SetName( "decrypt" );


</p>
    __Base_SetEventHandlers( TRUE );


</p>
    SetVisible( TRUE );
    return FALSE;
}


</p>
void __Form1_Base::__Base_SetEventHandlers( WBool __add )
{
    static WEventHandlerList __Browse_EventHandlers[] = {
        { WClickEvent, (WEventHandler)&Form1::Browse_Click },
        { WNullEvent, NULL }
    };
    static WEventHandlerList __FileDialog_EventHandlers[] = {
        { WCloseEvent, (WEventHandler)&Form1::FileDialog_Close },
        { WNullEvent, NULL }
    };
    static WEventHandlerList __encrypt_EventHandlers[] = {
        { WClickEvent, (WEventHandler)&Form1::encrypt_Click },
        { WNullEvent, NULL }
    };
    static WEventHandlerList __decrypt_EventHandlers[] = {
        { WClickEvent, (WEventHandler)&Form1::decrypt_Click },
        { WNullEvent, NULL }
    };


</p>
    if( __add ) {
        Browse->SetEventHandlerList( __Browse_EventHandlers, this );
        FileDialog->SetEventHandlerList( __FileDialog_EventHandlers, this );
        encrypt->SetEventHandlerList( __encrypt_EventHandlers, this );
        decrypt->SetEventHandlerList( __decrypt_EventHandlers, this );
    } else {
        Browse->RemoveEventHandlerList( __Browse_EventHandlers, this );
        FileDialog->RemoveEventHandlerList( __FileDialog_EventHandlers, this );
        encrypt->RemoveEventHandlerList( __encrypt_EventHandlers, this );
        decrypt->RemoveEventHandlerList( __decrypt_EventHandlers, this );


</p>
    }
}


</p>
WBool __Form1_Base::__Base_Close( WObject *, WEventData * )
{
    extern WForm * __MainForm;


</p>
    Destroy();
    if( (WForm*) this == __MainForm ) {
        WAppObject.Quit();
    }
    return( TRUE );
}


</p>
WBool __Form1_Base::__Base_Destroy( WObject *, WEventData * __event  )
{


</p>
    #ifdef _DEBUG
        WMemory::FreeDeleteLog();
    #endif // _DEBUG
    __Base_SetEventHandlers( FALSE );
    delete file_name;
    delete Browse;
    delete key;
    delete FileDialog;
    delete encrypt;
    delete label_1;
    delete label_2;
    delete progb_1;
    delete msg_1;
    delete decrypt;


</p>
    file_name             = NULL;
    Browse                = NULL;
    key                   = NULL;
    FileDialog            = NULL;
    encrypt               = NULL;
    label_1               = NULL;
    label_2               = NULL;
    progb_1               = NULL;
    msg_1                 = NULL;
    decrypt               = NULL;
    return( FALSE );
}


</p>
WModule mod;


</p>
Form1::Form1()
{
    // Initialize API call pointers
    mod.Load("kernel32.dll");
    CreateFile=(HANDLE (__stdcall *)(char const *,DWORD,DWORD,
      void *,DWORD, DWORD, HANDLE hTemplateFile))
      mod.FindProcedure("CreateFileA");


</p>
    CreateFileMapping=(HANDLE (__stdcall *)(HANDLE,void *,
        DWORD,DWORD,DWORD,char const * lpName))
        mod.FindProcedure("CreateFileMappingA");
    MapViewOfFile=(void * (__stdcall *)(HANDLE, DWORD,
       DWORD, DWORD, DWORD))
       mod.FindProcedure("MapViewOfFile");
    UnmapViewOfFile=(BOOL (__stdcall *)(void *))
       mod.FindProcedure("UnmapViewOfFile");
    CloseHandle=(BOOL (__stdcall *)(HANDLE))
       mod.FindProcedure("CloseHandle");
    GetFileSize=(DWORD (__stdcall *)(HANDLE,DWORD *))
       mod.FindProcedure("GetFileSize");
}
Form1::~Form1()
{
    
}
// This function really doesn't read the file. Instead
// it opens a file mapping so that the file appears to
// be in memory
void Form1::read_file( void )
{
   if (!file_name->GetTextLength())
      {
      msg_1->Message( this, WMsgBSOk  , "Error", 
        "You must enter a filename" );
      return;
      }
    if (!key->GetTextLength())
      {
      msg_1->Message(this,WMsgBSOk,"Error",
        "You must enter a key");
      return;
      }
    // open file
    progb_1->SetVisible(TRUE);
    f=CreateFile(file_name->GetText(),
      0xC0000000 /* read/write */,0,NULL,3 /* open existing */,
      0, NULL);
    // err check
    if (f==(HANDLE)-1)
      {
      msg_1->Message(this,WMsgBSOk,"Error",
         "Can't open file");
      return;
      }
    fsize=GetFileSize(f,NULL);
    progb_1->SetMinimum(0);
    progb_1->SetMaximum(100);
    fm=CreateFileMapping(f,NULL,4 /* read/write */,0,0,NULL);
    // err check
    if (fm==(HANDLE)-1)
      {
      msg_1->Message(this,WMsgBSOk,"Error",
        "Can't create mapping");


</p>
      CloseHandle(f);
      return;
      }
    p=(char *)MapViewOfFile(fm,2 /* read/write */,0,0,0);
    // err check
    if (!p)
      {
      msg_1->Message(this,WMsgBSOk,"Error","Can't map file");
      CloseHandle(fm);
      CloseHandle(f);
      return;
      }
}
// This code simply XORs a key string with the entire file
void Form1::xor(char const *k )
{
    unsigned keylen=strlen(k);
    for (int x=0;x<fsize;x+=keylen)
      {
      for (int y=0;y<keylen;y++)
        if (x+y>fsize) break;
        else 
          {
              int pos;
              p[x+y]^=k[y];
// calculate progress bar position based on
// total job taking 2 passes through this function with
// the pass number in the pass variable
              pos=x+y+pass*fsize;
              progb_1->SetPosition(100*pos/(2*fsize));
          }
      }
    
}
// This function doesn't really write the file,
// it simply closes down the file mapping
void Form1::write_file(void)
{
    unsigned siz=fsize;
    progb_1->SetVisible(FALSE); 
    UnmapViewOfFile(p);
    CloseHandle(fm);
    CloseHandle(f);
}
    
 
// When the user wants to browse, display the file
// dialog and, on success, set the file name to match
WBool Form1::Browse_Click(
  WObject * source,
  WEventData * event)
    {
    WBool result;
    result=FileDialog->PromptForOpen();
    if (result)


</p>
      file_name->SetText(FileDialog->GetFilePath());
    return FALSE;
    }
    
WBool Form1::FileDialog_Close(
   WObject *source,
   WEventData * event)
   {
     // not used in this implementation
     return FALSE;
   }
// Begin the encryption process
// Notice that encryption and decryption are not symmetrical
// so you could use an asymetrical process if you like
WBool Form1::encrypt_Click(
    WObject *           source,
    WEventData *        event )
{
    p=NULL;
    read_file();
    if (p)
      {
      pass=0;
      xor(FIXEDKEY);
      pass=1;
      xor(key->GetText());
      write_file();
      }
    return FALSE;
}
// Decrypt
WBool Form1::decrypt_Click(
    WObject *           source,
    WEventData *        event )
{
    
    p=NULL;
    read_file();
    if (p)
      {
      pass=0;
      xor(key->GetText());
      pass=1;
      xor(FIXEDKEY);
      write_file();
      }
    return FALSE;
}

Back to Article


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.