538 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C++
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			538 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C++
		
	
	
		
			Executable File
		
	
	
	
	
| //
 | |
| // EX23WIN.CPP
 | |
| //
 | |
| //  C++/Windows Example program for ArchiveLib 2.0
 | |
| //
 | |
| //  Copyright (c) Greenleaf Software, Inc. 1994 - 1996
 | |
| //  All Rights Reserved
 | |
| //
 | |
| // MEMBERS/FUNCTIONS DEMONSTRATED
 | |
| //
 | |
| //   N/A
 | |
| //
 | |
| // DESCRIPTION
 | |
| //
 | |
| //  This program is used to benchmark the performance of our various
 | |
| //  compression engines.  The program looks at the Input Files text
 | |
| //  box for a wildcard specification for a group of files.  The program
 | |
| //  expands the wild card specification and individually compresses and
 | |
| //  decompresses each of the files.  The temporary files created by this
 | |
| //  process are deleted.
 | |
| //
 | |
| //  After the whole batch of files is done, a summary of the compression
 | |
| //  time and statistics is displayed.
 | |
| //
 | |
| //  If the optional Compress Only check box is checked, the
 | |
| //  program runs in "compress-only" mode.  In this case, the decompression
 | |
| //  state is skipped.  You can use this to determine the ratio between
 | |
| //  compression and decompresion times.
 | |
| //
 | |
| //  If the Rude Mode check box is checked, the program doesn't service
 | |
| //  the Windows message loop while it is compression.
 | |
| //
 | |
| //  This example program does one funky thing to try to give us real
 | |
| //  fast performance.  Instead of using the default buffer size of 4096
 | |
| //  bytes, I jack it up to a hefty 28672 bytes.  This gives us a few
 | |
| //  percentage points.  Just change the definition of BIG_BUFFER to
 | |
| //  benchmark at the default size.
 | |
| //
 | |
| //
 | |
| // REVISION HISTORY
 | |
| //
 | |
| //  February 1, 1996  2.0A  : Second release
 | |
| //
 | |
| 
 | |
| #define STRICT
 | |
| #include <windows.h>
 | |
| #include <stdarg.h>
 | |
| #include <stdlib.h>
 | |
| #include <stdio.h>
 | |
| 
 | |
| #include "al.h"
 | |
| #include "ex23win.h"
 | |
| 
 | |
| /*
 | |
|  * I don't have a 32 bit version of CTL3D.DLL for Symantec or Microsoft.
 | |
|  */
 | |
| 
 | |
| #if defined( AL_BORLAND ) || !defined( AL_FLAT_MODEL )
 | |
| #define AL_3D
 | |
| #include "ctl3d.h"
 | |
| #else
 | |
| #define Ctl3dColorChange()
 | |
| #define Ctl3dRegister( a )
 | |
| #define Ctl3dAutoSubclass( a )
 | |
| #define Ctl3dUnregister( a )
 | |
| #endif
 | |
| 
 | |
| #if !defined( BIG_BUFFER )
 | |
| #define BIG_BUFFER 28672
 | |
| #endif
 | |
| 
 | |
| HCURSOR hHourGlassCursor;
 | |
| HINSTANCE hInstance;
 | |
| 
 | |
| //
 | |
| // This storage object is used when compressing or decompressing.  If
 | |
| // the user wants to abort the compression, it just has to set the
 | |
| // error flag for this object.  If the object pointer is set to 0, it means
 | |
| // no compression is currently in progress, so the Abort button does
 | |
| // nothing.
 | |
| //
 | |
| ALStorage *compressed = 0;
 | |
| 
 | |
| extern "C" BOOL AL_EXPORT CALLBACK AboutDialogProc( HWND, UINT, WPARAM, LPARAM );
 | |
| 
 | |
| //
 | |
| // The time class is a very modest convenience.
 | |
| //
 | |
| class TIME {
 | |
|     public :
 | |
|         long milliseconds;
 | |
|         TIME(){ milliseconds = GetTickCount(); }
 | |
|         TIME( long t ){ milliseconds = t; }
 | |
|         TIME operator-(TIME &);
 | |
| };
 | |
| 
 | |
| TIME TIME::operator-( TIME &rhs )
 | |
| {
 | |
|     return TIME( milliseconds - rhs.milliseconds );
 | |
| }
 | |
| 
 | |
| //
 | |
| // This function takes two file sizes and returns the compression ratio
 | |
| // in the standard format we expect.
 | |
| //
 | |
| inline int ratio( long plain, long compressed )
 | |
| {
 | |
|     if ( plain == 0 )
 | |
|         return 0;
 | |
|     compressed *= 100;
 | |
|     return (int)( 100 - ( compressed / plain ) );
 | |
| }
 | |
| 
 | |
| //
 | |
| // This function allocates a character buffer, then prints the
 | |
| // time into it.  It is up to the caller to free the returned
 | |
| // string.
 | |
| //
 | |
| char *format_time( const TIME &t )
 | |
| {
 | |
|     long m = t.milliseconds;
 | |
|     int hours;
 | |
|     int minutes;
 | |
|     int seconds;
 | |
|     int hsecs;
 | |
| 
 | |
|     char *s = (char *) malloc( 25 );
 | |
|     if ( s ) {
 | |
|         hours = (int) ( m / ( 1000L * 60L * 60L ));
 | |
|         m -= hours * ( 1000L * 60L * 60L );
 | |
|         minutes = (int) ( m / ( 1000L * 60L ) );
 | |
|         m -= minutes * (1000L * 60L );
 | |
|         seconds = (int)( m / ( 1000L ) );
 | |
|         m -= seconds * 1000L;
 | |
|         hsecs = (int)( m / 10L );
 | |
|         sprintf( s, "%02d:%02d:%02d.%02d", hours, minutes, seconds, hsecs );
 | |
|     }
 | |
|     return s;
 | |
| }
 | |
| 
 | |
| //
 | |
| // This is the first of two possible compression functions.  It is very
 | |
| // much like the main loop in EX23CON.CPP.  It just sets up a wild card
 | |
| // expander object, then starts chunking out file names.  Each time it
 | |
| // gets a new file name, it compresses it, and optionally expands it.
 | |
| // When the whole thing is over, the resulting stats are stuffed into
 | |
| // the dialog, somewhere.
 | |
| //
 | |
| 
 | |
| void NiceCompress( HWND hDlg )
 | |
| {
 | |
|     char input_name[ 128 ];
 | |
|     char buf[ 128 ];
 | |
|     long total_input = 0;
 | |
|     long total_compressed = 0;
 | |
|     int compress_only = IsDlgButtonChecked( hDlg, AL_COMPRESS_ONLY );
 | |
| 
 | |
|     ALWindowsMessage monitor( AL_MONITOR_OBJECTS,
 | |
|                               GetDlgItem( hDlg, AL_PROGRESS_TEXT ),
 | |
|                               AL_SEND_BYTE_COUNT,
 | |
|                               GetDlgItem( hDlg, AL_PROGRESS_NUMBER ) );
 | |
|     monitor.mlObjectStart = 0;
 | |
| 
 | |
|     GetDlgItemText( hDlg, AL_INPUT_FILES, input_name, 128 );
 | |
|     ALWildCardExpander expander( input_name );
 | |
| #if defined( ZIP )
 | |
|     ALPkCompressor compressor;
 | |
|     ALPkDecompressor decompressor;
 | |
| #else
 | |
|     ALGlCompressor compressor;
 | |
|     ALGlDecompressor decompressor;
 | |
| #endif
 | |
| //
 | |
| // This code clears out all the boxes on the dialog that might be left
 | |
| // over from the last run of this program.
 | |
| //
 | |
|     SendDlgItemMessage( hDlg, AL_DATA, LB_RESETCONTENT, 0, 0 );
 | |
|     SetDlgItemText( hDlg, AL_TOTAL_INPUT, "" );
 | |
|     SetDlgItemText( hDlg, AL_TOTAL_COMPRESSED, "" );
 | |
|     SetDlgItemText( hDlg, AL_RATIO, "" );
 | |
|     SetDlgItemText( hDlg, AL_TIME, "" );
 | |
| 
 | |
|     char AL_DLL_FAR *name;
 | |
|     TIME global_start;
 | |
| //
 | |
| // This is the loop where all of the work happens.  All we do here is
 | |
| // get a new file name and use it as the input file.  We then compress
 | |
| // to a second file, and then optionally expand to a third file.
 | |
| //
 | |
|     while ( ( name = expander.GetNextFile() ) != 0 ) {
 | |
|         TIME local_start;
 | |
|         ALFile input( name, BIG_BUFFER );
 | |
|         compressed = new ALFile( "", BIG_BUFFER );
 | |
|         ALFile output( "", BIG_BUFFER );
 | |
|         ALName filename( name );
 | |
|         filename.StripPath();
 | |
|         SetDlgItemText( hDlg, AL_PROGRESS_TEXT, (LPSTR) filename );
 | |
|         input.mpMonitor = &monitor;
 | |
|         compressor.Compress( input, *compressed );
 | |
|         if ( compressed->mStatus < AL_SUCCESS )
 | |
|             break;
 | |
|         if ( !compress_only ) {
 | |
|             input.mpMonitor = 0;
 | |
|             output.mpMonitor = &monitor;
 | |
|             decompressor.Decompress( *compressed, output );
 | |
|             output.Delete();
 | |
|             if ( compressed->mStatus < AL_SUCCESS )
 | |
|                 break;
 | |
|         }
 | |
|         total_input += input.GetSize();
 | |
|         total_compressed += compressed->GetSize();
 | |
|         TIME local_stop;
 | |
|         char *s;
 | |
|         wsprintf( buf,
 | |
|                   "%s\t%ld\t%ld\t%d%%\t%s",
 | |
|                   (LPSTR) filename,
 | |
|                   input.GetSize(),
 | |
|                   compressed->GetSize(),
 | |
|                   ratio( input.GetSize(), compressed->GetSize() ),
 | |
|                   (LPSTR)( s = format_time( local_stop - local_start ) )
 | |
|                 );
 | |
|         if ( s )
 | |
|             free( s );
 | |
| //
 | |
| // I stuff the file stats in the list box, then make sure that the currently
 | |
| // selected position in the list box is on the screen.
 | |
| //
 | |
|         int pos = (int) SendDlgItemMessage( hDlg,
 | |
|                                             AL_DATA,
 | |
|                                             LB_ADDSTRING,
 | |
|                                             0,
 | |
|                                             (LPARAM)(LPSTR) buf );
 | |
|         SendDlgItemMessage( hDlg,
 | |
|                             AL_DATA,
 | |
|                             LB_SETCURSEL,
 | |
|                             pos,
 | |
|                             0 );
 | |
|         SendDlgItemMessage( hDlg,
 | |
|                             AL_DATA,
 | |
|                             LB_SETCURSEL,
 | |
|                             (WPARAM) -1,
 | |
|                             0 );
 | |
|         compressed->Delete();
 | |
|         delete compressed;
 | |
|         compressed = 0;
 | |
|     }
 | |
|     if ( compressed ) {
 | |
|         delete compressed;
 | |
|         compressed = 0;
 | |
|     }
 | |
|     TIME global_stop;
 | |
|     sprintf( buf, "%ld", total_input );
 | |
|     SetDlgItemText( hDlg, AL_TOTAL_INPUT, buf );
 | |
|     sprintf( buf, "%ld", total_compressed );
 | |
|     SetDlgItemText( hDlg, AL_TOTAL_COMPRESSED, buf );
 | |
|     sprintf( buf, "%d%%", ratio( total_input, total_compressed ) );
 | |
|     SetDlgItemText( hDlg, AL_RATIO, buf );
 | |
|     char * s = format_time( global_stop - global_start );
 | |
|     if ( s ) {
 | |
|         sprintf( buf,
 | |
|                  "%s",
 | |
|                  s = format_time( global_stop - global_start ) );
 | |
|         SetDlgItemText( hDlg, AL_TIME, buf );
 | |
|         free( s );
 | |
|     } else
 | |
|         SetDlgItemText( hDlg, AL_TIME, "" );
 | |
| }
 | |
| 
 | |
| //
 | |
| // In order to blow off the Windows message loop, we have to derive a new
 | |
| // file class, which is what we are doing here with ALRudeFile.  This
 | |
| // class only needs a constructor and a new Yield function (the destructor
 | |
| // is here because of DLL problems when we take the default dtor.)
 | |
| // The Yield function does nothing.  This overrides the base class
 | |
| // Yield function, which services the message loop.
 | |
| //
 | |
| class AL_CLASS_TYPE ALRudeFile : public ALFile {
 | |
|     public :
 | |
|         virtual AL_PROTO ~ALRudeFile(){;}
 | |
|         void AL_DLL_FAR * AL_PROTO operator new( size_t size ){ return ::new char[ size ]; }
 | |
|         AL_PROTO ALRudeFile( const char AL_DLL_FAR *name = "" );
 | |
|         virtual void AL_PROTO YieldTime(){;}
 | |
| };
 | |
| 
 | |
| AL_PROTO ALRudeFile::ALRudeFile( const char AL_DLL_FAR *name )
 | |
|  : ALFile( name, BIG_BUFFER, AL_MIXED )
 | |
| {
 | |
| }
 | |
| 
 | |
| //
 | |
| // RudeCompress is just like NiceCompress(), except that it uses rude
 | |
| // files instead of ALFile.  It doesn't bother updating most of
 | |
| // the elements on the screen, because we want to be as rude as
 | |
| // possible.  One kind of not rude thing here is the hourglass cursor
 | |
| // we put up to let you know that you aren't going to be able to do
 | |
| // anything else for a while.
 | |
| //
 | |
| void RudeCompress( HWND hDlg )
 | |
| {
 | |
|     char input_name[ 128 ];
 | |
|     char buf[ 128 ];
 | |
|     long total_input = 0;
 | |
|     long total_compressed = 0;
 | |
|     int compress_only = IsDlgButtonChecked( hDlg, AL_COMPRESS_ONLY );
 | |
| 
 | |
| 
 | |
|     HCURSOR old_cursor = SetCursor( hHourGlassCursor );
 | |
|     GetDlgItemText( hDlg, AL_INPUT_FILES, input_name, 128 );
 | |
|     ALWildCardExpander expander( input_name );
 | |
| #if defined( ZIP )
 | |
|     ALPkCompressor compressor;
 | |
|     ALPkDecompressor decompressor;
 | |
| #else
 | |
|     ALGlCompressor compressor;
 | |
|     ALGlDecompressor decompressor;
 | |
| #endif
 | |
|     SendDlgItemMessage( hDlg, AL_DATA, LB_RESETCONTENT, 0, 0 );
 | |
|     SetDlgItemText( hDlg, AL_TOTAL_INPUT, "" );
 | |
|     SetDlgItemText( hDlg, AL_TOTAL_COMPRESSED, "" );
 | |
|     SetDlgItemText( hDlg, AL_RATIO, "" );
 | |
|     SetDlgItemText( hDlg, AL_TIME, "" );
 | |
|     SetDlgItemText( hDlg, AL_PROGRESS_NUMBER, "" );
 | |
| 
 | |
|     char AL_DLL_FAR *name;
 | |
|     TIME global_start;
 | |
|     while ( ( name = expander.GetNextFile() ) != 0 ) {
 | |
|         TIME local_start;
 | |
|         ALRudeFile input( name );
 | |
|         compressed = new ALRudeFile;
 | |
|         ALRudeFile output;
 | |
|         ALName filename( name );
 | |
|         filename.StripPath();
 | |
|         SetDlgItemText( hDlg, AL_PROGRESS_TEXT, (LPSTR) filename );
 | |
|         compressor.Compress( input, *compressed );
 | |
|         if ( compressed->mStatus < AL_SUCCESS )
 | |
|             break;
 | |
|         if ( !compress_only ) {
 | |
|             decompressor.Decompress( *compressed, output );
 | |
|             output.Delete();
 | |
|             if ( compressed->mStatus < AL_SUCCESS )
 | |
|                 break;
 | |
|         }
 | |
|         total_input += input.GetSize();
 | |
|         total_compressed += compressed->GetSize();
 | |
|         compressed->Delete();
 | |
|         delete compressed;
 | |
|         compressed = 0;
 | |
|     }
 | |
|     if ( compressed ) {
 | |
|         delete compressed;
 | |
|         compressed = 0;
 | |
|     }
 | |
|     TIME global_stop;
 | |
|     sprintf( buf, "%ld", total_input );
 | |
|     SetDlgItemText( hDlg, AL_TOTAL_INPUT, buf );
 | |
|     sprintf( buf, "%ld", total_compressed );
 | |
|     SetDlgItemText( hDlg, AL_TOTAL_COMPRESSED, buf );
 | |
|     sprintf( buf, "%d%%", ratio( total_input, total_compressed ) );
 | |
|     SetDlgItemText( hDlg, AL_RATIO, buf );
 | |
|     char * s = format_time( global_stop - global_start );
 | |
|     if ( s ) {
 | |
|         sprintf( buf,
 | |
|                  "%s",
 | |
|                  s = format_time( global_stop - global_start ) );
 | |
|         SetDlgItemText( hDlg, AL_TIME, buf );
 | |
|         free( s );
 | |
|     } else
 | |
|         SetDlgItemText( hDlg, AL_TIME, "" );
 | |
|     SetCursor( old_cursor );
 | |
| }
 | |
| 
 | |
| //
 | |
| // This text message used to be in the RC file, but for some reason
 | |
| // Microsoft's RC.EXE decided it was too long to compile.
 | |
| //
 | |
| char *help = "This program is used to run benchmarks on ArchiveLib's "
 | |
|              "compression performance.  Simply enter a wild-card file "
 | |
|              "specification in the Input Files text box, then press the "
 | |
|              "Compress button.  When the program completes, it will "
 | |
|              "print statistics regarding compression speed and ratio.  The "
 | |
|              "Compress Only check box can be used to disable decompression.  "
 | |
|              "When the Rude Mode check box is selected the compression "
 | |
|              "routines will not service the Windows message loop.";
 | |
| //
 | |
| // This is the dialog procedure for our main dialog.  In this program, all
 | |
| // the important work gets done here.  Most of it gets down by the
 | |
| // handler for WM_COMMAND, which processes the button presses from the
 | |
| // dialog.
 | |
| //
 | |
| 
 | |
| BOOL AL_EXPORT CALLBACK MainDialogProc( HWND hDlg,
 | |
|                                         UINT message,
 | |
|                                         WPARAM wParam,
 | |
|                                         LPARAM )
 | |
| {
 | |
|     short int tab_stops[] = { 65, 120, 175, 195  };
 | |
|     switch ( message ) {
 | |
| //
 | |
| // We respond to the init message by positioning the dialog on the screen,
 | |
| // initializing the windows title, the setting up the initial values of
 | |
| // all the text boxes.
 | |
| //
 | |
|         case WM_INITDIALOG :
 | |
|             RECT rc;
 | |
|             SetDlgItemText( hDlg, AL_HELP, help );
 | |
|             SendDlgItemMessage( hDlg, AL_DATA, LB_SETTABSTOPS, 4, (LPARAM) (LPINT) tab_stops );
 | |
| /* I use this line of code to help me line up the columns...
 | |
|             SendDlgItemMessage( hDlg,
 | |
|                                 AL_DATA,
 | |
|                                 LB_ADDSTRING,
 | |
|                                 0,
 | |
|                                 (LPARAM)(LPSTR) "A\tB\tC\tD\tE" );
 | |
| */
 | |
|             GetWindowRect( hDlg, &rc );
 | |
|             SetWindowPos( hDlg,
 | |
|                           NULL,
 | |
|                           ((GetSystemMetrics(SM_CXSCREEN) - (rc.right - rc.left)) / 2),
 | |
|                           ((GetSystemMetrics(SM_CYSCREEN) - (rc.bottom - rc.top)) / 2),
 | |
|                           0,
 | |
|                           0,
 | |
|                           SWP_NOSIZE | SWP_NOACTIVATE );
 | |
|             SetWindowText( hDlg, "Windows example 23" );
 | |
|             SetDlgItemText( hDlg, AL_INPUT_FILES, "C:\\DOS\\COMMAND.COM" );
 | |
|             SetDlgItemText( hDlg, AL_PROGRESS_TEXT, "" );
 | |
|             SetDlgItemText( hDlg, AL_PROGRESS_NUMBER, "" );
 | |
|             return( TRUE );
 | |
| //
 | |
| // Have to support this message in order to make the CTL3D stuff work.
 | |
| //
 | |
|         case WM_SYSCOLORCHANGE :
 | |
|            Ctl3dColorChange();
 | |
|            break;
 | |
| //
 | |
| // WM_COMMAND is for all the button presses.
 | |
| //
 | |
|         case WM_COMMAND :
 | |
|             switch ( wParam ) {
 | |
| //
 | |
| // If the user wants to compress, we do so, but only if no compression
 | |
| // is already in progress.
 | |
| //
 | |
|                 case AL_COMPRESS :
 | |
|                     if ( compressed == 0 )
 | |
|                         if ( IsDlgButtonChecked( hDlg, AL_RUDE ) )
 | |
|                             RudeCompress( hDlg );
 | |
|                         else
 | |
|                             NiceCompress( hDlg );
 | |
|                     return TRUE;
 | |
| //
 | |
| // If the user wants to quit, I have to set one of the file objects to an
 | |
| // error status first.  Otherwise, the compression code will keep right on
 | |
| // going even after we have killed our dialog window.  Code like this is used
 | |
| // all over the place in all of the example programs.
 | |
| //
 | |
|                 case AL_EXIT :
 | |
|                 case WM_QUIT :
 | |
|                 case WM_DESTROY :
 | |
|                     if ( compressed != 0 )
 | |
|                         compressed->mStatus.SetError(
 | |
|                                         AL_USER_ABORT,
 | |
|                                         "User pressed abort key" );
 | |
|                     else
 | |
|                         EndDialog( hDlg, TRUE );
 | |
|                     return TRUE;
 | |
| //
 | |
| // We abort a compression in progress the same way, by setting the
 | |
| // error code for the storage object.  Likewise, code that looks
 | |
| // just like this is used all throughout our example programs.
 | |
| //
 | |
|                 case AL_ABORT :
 | |
|                     if ( compressed != 0 )
 | |
|                         compressed->mStatus.SetError(
 | |
|                                         AL_USER_ABORT,
 | |
|                                         "User pressed abort key" );
 | |
|                     return TRUE;
 | |
| 
 | |
|                 case AL_ABOUT :
 | |
|                     DialogBox( hInstance, "ALAboutDialog", hDlg, AboutDialogProc );
 | |
|                     return TRUE;
 | |
|         }
 | |
|         break;
 | |
|     }
 | |
|     return FALSE;
 | |
| }
 | |
| 
 | |
| //
 | |
| // The about procedure just displays the about box.
 | |
| //
 | |
| BOOL AL_EXPORT CALLBACK AboutDialogProc( HWND hDlg,
 | |
|                                          UINT message,
 | |
|                                          WPARAM wParam,
 | |
|                                          LPARAM )
 | |
| {
 | |
|     switch ( message ) {
 | |
|         case WM_INITDIALOG :
 | |
|             RECT rc;
 | |
|             GetWindowRect( hDlg, &rc );
 | |
|             SetWindowPos( hDlg,
 | |
|                           NULL,
 | |
|                           ((GetSystemMetrics(SM_CXSCREEN) - (rc.right - rc.left)) / 2),
 | |
|                           ((GetSystemMetrics(SM_CYSCREEN) - (rc.bottom - rc.top)) / 2),
 | |
|                           0, 0, SWP_NOSIZE | SWP_NOACTIVATE);
 | |
|             break;
 | |
|         case WM_QUIT :
 | |
|         case WM_DESTROY :
 | |
|             EndDialog( hDlg, TRUE );
 | |
|             return TRUE;
 | |
|         case WM_COMMAND :
 | |
|             switch ( wParam ) {
 | |
|                 case IDOK :
 | |
|                 case AL_EXIT :
 | |
|                     EndDialog( hDlg, TRUE );
 | |
|                     return TRUE;
 | |
|             }
 | |
|             break;
 | |
|     }
 | |
|     return FALSE;
 | |
| }
 | |
| 
 | |
| //
 | |
| // WinMain just has to dispatch the dialog box.  It also sets up the CTL3d
 | |
| // stuff.
 | |
| //
 | |
| int PASCAL WinMain( HINSTANCE instance,
 | |
|                     HINSTANCE,
 | |
|                     LPSTR,
 | |
|                     int )
 | |
| {
 | |
|     hInstance = instance;
 | |
|     hHourGlassCursor = LoadCursor( NULL, IDC_WAIT );
 | |
|     Ctl3dRegister( instance );
 | |
|     Ctl3dAutoSubclass( instance );
 | |
|     DialogBox( instance, "ALMainDialog", 0, MainDialogProc );
 | |
|     Ctl3dUnregister( instance );
 | |
|     DestroyCursor( hHourGlassCursor );
 | |
|     return 0;
 | |
| }
 |