From: tomh@wes.on.ca (Tom Haapanen)
Newsgroups: comp.os.ms-windows.announce
Subject: Windows Programmer FAQ: ASCII format, 03/04
Date: 27 Jan 93 16:59:15 GMT
Followup-To: comp.os.ms-windows.misc
Organization: Waterloo Engineering Software

The following is part 03 of 04 of the ASCII text version of the
Microsoft Windows Programmer FAQ.  While some of you may find this the best
format, the more popular format is the WinHelp file.  These are posted
simultaneously with the ASCII version to comp.binaries.ms-windows.

You can obtain a copy of the WinHelp version as "faqprg01.zip" by ftp
from sonygate.sony.com [192.63.138.2], in /pub/comp.binaries.ms-windows.
It will also be placed on ftp.cica.indiana.edu once disk space permits.



BEGIN--------------cut here--------cut here------------cut here----------
===================
  
...........................................................................

5.7.1.  Activating the previous instance
----------------------------------------
  If you want to run only a single instance of your application, and activate
  the first instance when the user tries to run a second one, use the
  following code at the beginning of your WinMain function:
  
      if ( hPrevInstance ) {
          BringWindowToTop( FindWindow( "MyClassName", NULL ));
          return( FALSE );
      }
  Note that the value of hPrevInstance will always be NULL on Windows NT.
  
...........................................................................

5.7.2.  Changing your current directory
---------------------------------------
  The easy way is to use DlgDirList().  You can specify zero for the two ID
  fields.  You can use the current window ID for the dialog handle field.
  
  The standard C library functions chdir() and getcwd() can also be used.
  
...........................................................................

5.7.3.  Detecting idle time
---------------------------
  The 'idle-detecting' message loop may suit your case.  That is, replace the
  standard while( GetMessage() ) ... in WinMain() with the something like the
  following (thanks to Risto Lankinen for the example):
  
  if ( PeekMessage(&msg) ) {
      // The queue contains messages - process them
      // GetMessage() would automatically detect WM_QUIT, but
      // we must explicitly check for it.
      if ( msg.message == WM_QUIT )
          break;
      if ( TranslateAccelerator(hWnd,hAcc,&msg) )
          continue;
  
      TranslateMessage( &msg );
      DispatchMessage( &msg );
  
      // You might want to save the last time a message
      // was processed
      dwLastMsgTime = GetTickCount();
  } else {
      // The queue is empty - user is doing nothing with *this* app
      if ( GetTickCount() < dwLastMsgTime ) {
          // Timer wrapped around -- do something!
      } else if ( GetTickCount() - dwLastMsgTime > MSGTIMEDELTA ) {
          // Do something funky
      }
  
  
...........................................................................

5.7.4.  Enumarating active processes
------------------------------------
  To enumerate the processes currently active in the system (to build a
  process tree, create a task manager or otherwise), you should use
  TaskFirst(), TaskNext() and IsTask().  All of these are included in
  toolhelp.dll, a redistributable component of the Windows 3.1 SDK..
  
...........................................................................

5.7.5.  Extracting icons from a .EXE or .DLL
--------------------------------------------
  In Windows 3.1, it's easy to enumerate the icons in a Windows EXE or DLL
  even if you don't already know their names.  SHELL.DLL exports
       HICON ExtractIcon(hInst, lpszExeName, nIcon)
  This function returns a handle to the specified icon (where 0 is the
  default icon displayed by Program Manager), or the number of icons in the
  file if you pass in an index of -1.
  
  Better yet, SHELL.DLL also exports the function:
       FindExecutable(lpszFile, lpszDir, lpszResult)
  which will give you the executable associated with a given document file.
  You can then extract the appropriate icon from that file.
  
...........................................................................

5.7.6.  Finding the location of the executable
----------------------------------------------
  To locate your executable program directory , use:
  
       GetModuleFileName(hInstance, (LPSTR) szPath, sizeof szPath)
  
...........................................................................

5.7.7.  Finding the number of instances running
-----------------------------------------------
  Use the following code:
  
       nNumInsts = GetModuleUsage(hInstance);
  
  Note that this will always return 1 within Windows NT.
  
...........................................................................

5.7.8.  Getting the instance handle
-----------------------------------
  In general, it's usually best to store the instance handle in a global
  variable, so that it will always be available.  If, however, you don't have
  it handy, you can get it with an easy Windows API call:
  
       GetWindowWord(hWnd, GWW_HINSTANCE)
  
...........................................................................

5.7.9.  GettMultimedia RIFF file format
---------------------------------------
  The IBM/Microsoft RIFF and MCI definition document is available for
  anonymous ftp download from the /vendor/microsoft/multimedia directory on
  ftp.uu.net. If you don't have anonymous ftp access, try CompuServe in the
  WINSDK forum, or Microsoft Online.
  
  The document describes multimedia interfaces (MCI) and data formats (RIFF).
  IBM has committed to include support for these in OS/2.  These interfaces
  are already supported in the Multimedia Extensions for Windows (MME) and
  Windows 3.1.  Included in the RIFF file format is a waveform (.WAV) audio
  definition; this format is the system standard for Windows and OS/2.
  
...........................................................................

5.7.10.  Restarting Windows
---------------------------
  To restart Windows, use:
  
       ExitWindows(EW_RESTARTWINDOWS, 0)
  
...........................................................................

5.7.11.  Rebooting the system
-----------------------------
  To reboot the entire system, use:
  
       ExitWindows(EW_REBOOTSYSTEM, 0)
  
...........................................................................

5.7.12.  Using NetDDE
---------------------
  To use an existing DDE application over the network you simply need to
  create a DDE share for it with a separate utility.  The DDE share you
  create refers to a specific Service|Topic pair, and when a client connects
  to that share, it is as if they had connected to the underlying
  Service|Topic.  (The DDEShare tool that comes with the Windows for
  Workgroups resource kit will do this.)  From the client application you
  simply connect to  \\MachineName\NDDE$|DDEShareName instead of
  Service|Topic.
  
  Thanks to Blake Coverett of Microsoft Canada for this information.
  
...........................................................................

5.7.13.  Waiting for completion of WinExec()
--------------------------------------------
  If you need to wait for the completion of a WinExec()'d process,
  Microsoft's recommended approach is to use the functions in toolhelp.dll (a
  free redistributable part of the Windows 3.1 SDK) to register a
  notification callback routine, which will then get called once the
  WinExec()'d application terminates.
  
...........................................................................

5.7.14.  Wsprintf and sprintf
-----------------------------
  wsprintf() can not print floating-point numbers by design.  To print
  floating-point,
  you must use sprintf().  Remember, though, that all strings passed to
  wsprintf() should be cast to FAR!
  
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::


6.  OTHER DEVELOPMENT TOOLS
===========================
  
---------------------------------------------------------------------------

6.1.  Turbo Pascal for Windows
==============================
  
...........................................................................

6.1.1.  Using CTL3D.DLL with Turbo Pascal
-----------------------------------------
  To use the ctl3d.dll (which provides a standardized 3-D look to all your
  dialogs, similar to Microsoft Excel 4.0) with Turbo Pascal, you'll need a
  unit file.  The unit file and example below were contributed by Andreas
  Furrer (s_furrer@ira.uka.de).
  
  CTL3D.PAS
  
     (**************************************************)
     (*   Unit CTL3D                                   *)
     (*                                                *)
     (*   for use with CTL3D.DLL from Microsoft        *)
     (**************************************************)
     
     unit Ctl3D;
     
     interface
     
     uses WinTypes;
     
     const CTL3D_BUTTONS      = $0001;
           CTL3D_LISTBOXES    = $0002;
           CTL3D_EDITS        = $0004;
           CTL3D_COMBOS       = $0008;
           CTL3D_STATICTEXTS  = $0010;
           CTL3D_STATICFRAMES = $0020;
           CTL3D_ALL          = $ffff;
     
     function Ctl3dGetVer : WORD;
     function Ctl3dSubclassDlg(HWindow : HWND; Ctrls : WORD) : bool;
     function Ctl3dSubclassCtl(HWindow : HWND) : bool;
     function Ctl3dCtlColor(DC : HDC; Color : TColorRef) : HBrush;
     {ARCHAIC, use Ctl3dCtlColorEx}
     function Ctl3dEnabled : bool;
     function Ctl3dColorChange : bool;
     function Ctl3dRegister(Instance : THandle) : bool;
     function Ctl3dUnregister(Instance : THandle) : bool;
     function Ctl3dAutoSubclass(Instance : THandle) : bool;
     function Ctl3dCtlColorEx(Message, wParam : WORD; lParam : LONGINT)
                         : HBrush;
     
     
     implementation
     
     function Ctl3dGetVer;       external 'Ctl3d' index 1;
     function Ctl3dSubclassDlg;  external 'Ctl3d' index 2;
     function Ctl3dSubclassCtl;  external 'Ctl3d' index 3;
     function Ctl3dCtlColor;     external 'Ctl3d' index 4;
     function Ctl3dEnabled;      external 'Ctl3d' index 5;
     function Ctl3dColorChange;  external 'Ctl3d' index 6;
     function Ctl3dRegister;     external 'Ctl3d' index 12;
     function Ctl3dUnregister;   external 'Ctl3d' index 13;
     function Ctl3dAutoSubclass; external 'Ctl3d' index 16;
     function Ctl3dCtlColorEx;   external 'Ctl3d' index 18;
     end.
  
  
  Test3D.PAS
  
     (**************************************************)
     (*   Test3D                                       *)
     (*                                                *)
     (*   Copyright (c) 1993 by A. Furrer              *)
     (**************************************************)
     
     program Test3D;
     
     {$R test3d.res}
     
     uses WinTypes, WinProcs, WObjects, Ctl3d;
     
     type
        Applikation =
           object(TApplication)
              procedure InitMainWindow; virtual;
           end;
     
        PMainwindow = ^TMainwindow;
        TMainwindow =
           object(TWindow)
              procedure WMLButtonDown(var Msg : TMessage); virtual
                              wm_first + wm_LButtonDown;
           end;
     
     procedure TMainwindow.WMLButtonDown;
     begin
       Application^.ExecDialog(NEW(PDialog,Init(@Self,'Test3D')));
     end;
     
     procedure Applikation.InitMainWindow;
     begin
       MainWindow := New(PMainwindow, Init(nil, 'Test3D'));
     end;
     
     var Prg : Applikation;
     
     begin
        Ctl3dRegister(HInstance);
        Ctl3dAutoSubclass(HInstance);
     
        Prg.Init('Test3D');
        Prg.Run;
        Prg.Done;
     
        Ctl3dUnregister(HInstance);
     end.
  
  
---------------------------------------------------------------------------

6.2.  Visual Basic
==================
  
...........................................................................

6.2.1.  Allowing user interaction in tight loops
------------------------------------------------
  If your application uses a tight loop to perform calculations (or do ther
  things), you should call DoEvents() from within your loop to allow the user
  to activate other applications -- or to press the Cancel  button in your
  application.
  
...........................................................................

6.2.2.  Animating the icon
--------------------------
  To animate the icons, use a timer control (which is disabled when the form
  is not minimized), and change the value of the form's icon property as
  required.
  
...........................................................................

6.2.3.  Disabling automatic variables
-------------------------------------
  In the tradition of Basic, Visual Basic allows you to use variables which
  have not been explicitly declared.  Unfortunately there is no option to
  disable this "feature".  You can alleviate some of its effects by including
  a line such as
       DefStr A-Z
  in all your modules.  This means that Visual Basic will assume all your
  variables are strings unless explicitly declared, catching at least some
  typos in your code.
  
  Hopefully Visual Basic 3.0 will add an option for disabling this feature.
  
...........................................................................

6.2.4.  Passing a structure back to Visual Basic from a DLL
-----------------------------------------------------------
  The following description is courtesy of Todd Ogasawara, 1991 (reachable at
  todd@pinhead.pegasus.com).  The code fragments were developed and tested
  using Borland C++ 2.0 and Microsoft Visual Basic.
  
  * Define a type that is a pointer to a structure.
      typedef struct {
      long    fsize;          // file size in bytes
      char    ftime[25];      // last file access time string
      } * fileStruct;
  
  * Sample function prototype declaration.
      int FAR pascal FileInfo(char filename[], fileStruct);
  
  * Sample DLL C function that receives a filename in a char array from
     Visual Basic and passes back file size (long) and file modification date
     (char array) in a structure.
      // Get file info (access time & size)
      int FAR pascal
      FileInfo(char filename[], fileStruct far fileinfo)
      {
      struct stat statbuf;
      FILE   *stream;
      if (!(stream = fopen(filename, "r"))) {
           return(-1); // ERROR: cannot find named file
      } else {
           fstat(fileno(stream), &statbuf);
           fclose(stream);
      }
      /* file size */
      fileinfo->fsize = statbuf.st_size;
      /* access time */
      strcpy(fileinfo->ftime,ctime(&statbuf.st_ctime));
      return(0);
      }
  
  * Declare a Visual BASIC "user-defined type" (i.e., a "structure") that
     matches the structure declared in the DLL C code. See pages 260-261 of
     the Microsoft Visual BASIC Programmer's Guide for more information about
     user-defined types.
      ' type (structure) definition in GLOBAL.BAS
      Type FileStruct
      Fsize As Long
      Ftime As String * 25
      End Type
  
  * Declare the function in your GLOBAL.BAS (or whatever you named the file
     you keep global information in). In this example a function is declared
     since the DLL C function returns a -1 to indicate an error and a 0 to
     indicate success. Note that the filename is passed from Visual BASIC to
     the DLL C function by value (ByVal) while the data in the DLL C structure
     is passed to Visual BASIC by reference (As). See pages 379-387 of the
     Microsoft Visual BASIC Programmer's Guide for more information about
     declaring and calling DLL routines.
      ' declaration in GLOBAL.BAS
      Declare Function FileInfo Lib "dosdll.dll" (ByVal FileName$,
      FileInf As FileStruct) As Integer
  
  * Example of using the DLL function 'FileInfo' in Visual BASIC.
      If (Myfile.Filename = "") Then
      Exit Sub
      Else
      ThisFileName$ = UCase$(Myfile.Filename)
      End If
      FileStatus% = FileInfo(ThisFileName$, FileStat)
      ThisFileSize$ = Format$(FileStat.Fsize, "###,###,###") + "
      bytes" ThisFileStat$ = Left$(FileStat.Ftime, 24)
  
...........................................................................

6.2.5.  Right mouse clicks on command buttons
---------------------------------------------
  Visual Basic command buttons do not process right mouse clicks, nor mouse
  up or down events.  To use the right mouse button with a command button,
  replace the command button with a picture control (use two bitmaps, one for
  "up" and the other for "down"), which does process these events.
  
...........................................................................

6.2.6.  Using Visual Basic strings in a DLL
-------------------------------------------
  To access Visual Basic strings in a DLL, you need to use the functions
  provided in the Control Development Kit (CDK).  You can purchase the CDK
  separately, but it is also included as part of Visual Basic 2.0
  Professional Edition.
  
  As an alternative, passing a Visual Basic string to a DLL as ByVal will
  automatically convert it to the usual null-terminated ASCII format.
  However, this will limit the length of the string to 255 bytes.
  
...........................................................................

6.2.7.  Visual Basic and Fortran
--------------------------------
  If you want to use Visual Basic to build a slick interface for your old
  text-based Fortran code, the approach to take is to build the Fortran code
  into a DLL, and call it from Visual Basic.  You may either pass the
  parameters as arguments, or you may want to construct a temporary file for
  more extensive input.
  
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::


7.  INTERFACING TO THE OUTSIDE WORLD
====================================
  
---------------------------------------------------------------------------

7.1.  Communicating with DOS applications
=========================================
  
...........................................................................

7.1.1.  Determining whether a task is a DOS task
------------------------------------------------
  To determine whether a particular task is a DOS session, you can use the
  following function, as contributed by Blake Coverett
  (blakeco@microsoft.com).
  
  BOOL
  IsDosTask(HTASK hTask)
  {
      TASKENTRY te = {sizeof(TASKENTRY)};
  
      if (!IsTask(hTask))
          return FALSE;
  
      TaskFindHandle(&te, hTask);
      return(!lstrcmp(te.szModule, "WINOLDAP"));
  }
  
  
...........................................................................

7.1.2.  Passing commandline parameters to DOS applications
----------------------------------------------------------
  The standard WinExec() function will not pass any commandline parameters to
  the application you are starting.  In order to pass parameters, you need to
  use the ShellExecute() function contained in shell.dll, which is a part of
  the Windows 3.1 SDK.
  
...........................................................................

7.1.3.  Passing a pointer to a DOS application or TSR
-----------------------------------------------------
  In order to pass a pointer to a DOS application (to share memory), you can
  not just pass a Windows pointer.  GlobalLock() returns a segment selector
  table entry, not a physical address.  Thus, the simple code below will give
  you an incorrect address:
  
     lpBuffer    = GlobalLock(hBuffer);
     InRegs.x.di = FP_OFF(lpBuffer);
     SegRegs.es  = FP_SEG(lpBuffer);
     int86x(0x7f,&InRegs,&OutRegs,&SegRegs);
  
  The problem is that the TSR or DOS application runs in real mode, while
  Windows applications running in Standard or 386 Enhanced mode use selectors
  [LDT] and not pointers[SEG:OFF] to access memory.
  
  The following gives an outline of what needs to be done, courtesy of Glenn
  Boozer (glenn@imagen.com):
  
  To send a pointer to DOS [a Segment:Offset address, not a protected mode
  selector]
  * DosAllocate a memory buffer  [This will be a buffer in the first 640K of
     address space.  This buffer is locked and will not move.  [Not recomended
     by Microsoft]
  * Copy the data from the buffer that is in "Windows Application space" into
     the DOS Buffer
  * Get the Segment and Offset of the DOS buffer and pass that to the TSR.
  * Release the DOS Buffer
  
  To use a pointer [Segment:offset] you got from a DOS application:
  * Allocate a Selector [Not recomended by Microsoft]
  * Set the selector base and length with the data returned from the TSR.
     [Not recomended by Microsoft]
  * Use the data
  * Release the selector.  [Not recomended by Microsoft]
  
  Selected code fragments follow.
  
  // Windows kernel calls not in WINDOWS.H
  WORD  FAR PASCAL SetSelectorBase(HANDLE hSelector, DWORD dwBase);
  WORD  FAR PASCAL SetSelectorLimit(HANDLE hSelector, DWORD dwLimit);
  DWORD FAR PASCAL GlobalDosAlloc(DWORD);
  WORD  FAR PASCAL GlobalDosFree(WORD);
  
  HANDLE FAR PASCAL
  GetPhysicalMemoryHandle()
  {
      HANDLE hSel;
      HANDLE hSel2;
  
      /*. create a selector for use by MakePhysicalMemoryPtr() */
      /* The how of this is taken from an SR response. */
      if ((hSel2 = GlobalAlloc(GMEM_FIXED,(long) 64)) != NULL) {
          hSel = AllocSelector(hSel2);
          GlobalFree(hSel2);
      } else {
          hSel = (HANDLE)NULL;
      }
      return hSel;
  }
  
  LPSTR NEAR PASCAL
  MakePhysicalMemoryPtr(WORD wMemHandle, WORD wSegment, WORD wOffset)
  {
      /*. set selector base from wSegment parameter */
      SetSelectorBase(wMemHandle, (((LONG)wSegment)<<4) + wOffset );
      /*. set limit for 4K bytes accessable */
      SetSelectorLimit(wMemHandle,(long) 0x0FFF);
      /*. make and return a long pointer using passed wMemHandle */
      return (LPSTR)MAKELONG(0, wMemHandle);
  }
  
  void FAR PASCAL
  FreePhysicalMemoryPtr(LPSTR lpMemPtr)
  {
      FreeSelector(HIWORD(lpMemPtr));
  }
  
  
  Main code fragment:
  
  // Assuming protected mode
  if (!( hPhysMemHandle = GetPhysicalMemoryHandle())) {
      MessageBoxOKHand((LPSTR) "Error: Could not get "
                               "physical memory for TSR");
      return;
  }
  
  // [call tsr-calling routine n times]
  
  // Return the handle we allocated
  FreePhysicalMemoryPtr(lpPhysPtr);
  
  
  TSR-calling routine:
  
  static FPTR   near fp;
  
  lpsDosParagraphSelector.d =
                GlobalDosAlloc((DWORD)max( APImsg.len, 4));
  if(APImsg.buffer) {
      lmemcpy( (LPSTR)MAKELONG(0, lpsDosParagraphSelector.w.sel),
                               APImsg.buffer, max( APImsg.len, 4));
      fp.w.sel = lpsDosParagraphSelector.w.par;
      fp.w.off = 0;
  } else {
      /* Null Pointer */
      fp.p = 0L;
  }
  dx = fp.w.sel;
  bx = fp.w.off;
  
  // Call the TSR
  rc = int2f(ax, cx, si, di, dx, bx, (unsigned int far *)&di,
            (unsigned int far *)&si, (unsigned int far *)&cx,
            (unsigned int far *)&dx, (unsigned int far *)&bx);
  
  (void) GlobalDosFree(lpsDosParagraphSelector.w.sel);
  lpPhysPtr = MakePhysicalMemoryPtr(hPhysMemHandle, dx, bx);
  fp.p = lpPhysPtr;
  
...........................................................................

7.1.4.  Starting a Windows application from a DOS session
---------------------------------------------------------
  This is really quite difficult, and you may be happier using an existing
  implementation (such as wx and wxserver, which comes with the Windows 3.1
  SDK), because in 386Enhanced mode the DOS application and the Windows world
  are in separate virtual machines; the only context they have in common is
  the underlying DOS.  The basic idea is to use a TSR that
  talks to both the DOS app and a Windows "wrapper" app that does the
  WinExec() for you. Thanks for the explanation are due to Ed Schwalenberg
  (ed@odi.com).
  
  Create a TSR that gets loaded before Windows is started.  Its services will
  be available to both DOS apps and Windows apps.  When Windows is started,
  your wrapper program can call the TSR with an INT 2F, giving it the address
  of some GlobalDOSAlloc()'ed memory which will be used to pass information
  back and forth between the TSR and protected-mode Windows.  While
  processing this INT 2F, you issue one of your own, with AX=1683h, which
  will return in BX the magic number of the Windows virtual machine (VxD),
  which is currently 1 but may change.
  
  The DOS application issues an INT 2F, passing the name of the desired
  application to the TSR.  The TSR copies the information into a private data
  buffer in the TSR's address space, NOT to the GlobalDOSAlloc()'ed memory
  (which only exists in the Windows virtual machine).
  
  Now for the hard part.  You need to call back to Windows when the Windows
  VM is scheduled.  To do that, use INT 2F, AX=1685h, BX=Windows VM number
  which you saved from the initialization step, CX=flags, DS:SI= priority
  boost and ES:DI=CS:IP of a procedure to call.  When the Windows VM is
  scheduled, your procedure will be called.  That procedure can copy the name
  of the application into the GlobalDOSAlloc()'ed memory, issue an INT 2F to
  the windows wrapper program, and IRET.  The windows wrapper program can use
  the data in the GlobalDOSAlloc()'ed memory to WinExec() the desired
  program.
  
---------------------------------------------------------------------------

7.2.  Multimedia
================
  
...........................................................................

7.2.1.  MIDI file format
------------------------
  To find out how to get a copy of the latest MIDI file standard as well as
  other MIDI-related documentation, send an email message to mail-
  server@cs.ruu.nl, with the following contents:
       
       BEGIN
       PATH <insert-your-internet-address-here>
       HELP
       send MIDI/INDEX
       END
  
...........................................................................

7.2.2.  Playing sounds from Visual Basic
----------------------------------------
  First, you can execute MCI commands by calling the Windows API:
  
       Declare Function mciExecute Lib "MMSystem" (ByVal xstr$)
               As Integer
       
       Sub Form_Click ()
           x% = mciExecute("Play c:\windows\ding.wav")
       End Sub
  
  If all you want to do is play sounds, there is an alternative:
  
  Declare Function sndPlaySound Lib "MMSYSTEM" (ByVal snd$, ByVal f%)
          As Integer
  
  Sub Form_Click ()
      x% = sndPlaySound("chime.wav", 0)
  End Sub
  
  The second argument determines whether the system waits until the sound is
  finished before returning from the call (0) or returns immediately while
  the sound is playing (1).
  
...........................................................................

7.2.3.  RIFF ( DIB, MIDI, RTF and WAV) file formats
---------------------------------------------------
  The sound file formats are described in the document sound10.txt, available
  by ftp from oak.oakland.edu, directory /pub/misc/sound.
  
  Microsoft's original Resource Interchange File Format (RIFF) descriptions
  (including WAV, MIDI, DIB and RTF) are available by ftp from ftp.uu.net,
  directory /vendor/microsoft/multimedia, under filenames riffmcir.zip
  (original spec, RTF format), riffmcit.zip (original spec, text format) and
  riffnew.zip (additions to the spec, RTF format).  They are also available
  on Compuserve's WINSDK forum, using truncated 6-character filenames.
  
  They are also available, free of charge, (in paper form) from Microsoft's
  Developer Services by phoning 800-227-4679, x11771.
  
...........................................................................

7.2.4.  Using an accurate timer
-------------------------------
  The standard Windows (and PC) timer is only accurate to a disappointing
  18ms.  If you need a more accurate timer, you should investigate the
  Windows 3.1 multimedia API, and specifically look at the functions
  timeBeginPeriod, timeEndPeriod and timeSetEvent.
  
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::


8.  PUTTING IT ALL TOGETHER
===========================
  
---------------------------------------------------------------------------

8.1.  Compiling and linking
===========================
  
...........................................................................

8.1.1.  Emulator vs. alternate floating-point math
--------------------------------------------------
  The alternate math package is faster on non-x87 machines, but slower on
  those equipped with a math chip.  Depending on your application, you might
  want to ship either, or both.  If you need accuracy in floating-point
  calculations, though, stay away from the alternate math package.
  
  Borland C++ does not support the alternate math package, but it does have a
  "fast floats" option, which is roughly equivalent.
  
...........................................................................

8.1.2.  Emulator floating-point: corrupted code segments
--------------------------------------------------------
  Compiling a Windows application with emulator floating-point causes
  corrupted code segments when running on a non-8087 equipped system in
  Windows 2.x and Windows 3.0 Real mode.
  
  The emulated floating point tries to used the coprocessor. When it does not
  find one on startup, it patches the code to use the software floating
  point. Patching does not, however, recalculate the code-segment checksum,
  thus the Windows debugging kernel chokes when it finds that something
  terrible must have happened to the code.  (This problem does not affect
  Windows 3.x in Standard or 386 Enhanced modes.)
  
  You can get Windows to ignore the checksum errors by setting
  EnableSegmentChecksum=0 in the [debug] section of win.ini; the problem only
  affects debugging versions of Windows 3.0.
  
...........................................................................

8.1.3.  Exporting CDECL functions
---------------------------------
  If you specify functions to be exported in the .def file, the linker will
  assume that they use the Windows standard Pascal calling convention.  If
  you didn't declare some of them as PASCAL, you will need to specify those
  functions in the .def file as _funcname instead of funcname, since C
  compilers always add an underscore to the front of a name.
  
...........................................................................

8.1.4.  Large memory model: why or why not?
-------------------------------------------
  Yes, you can do it.  There are several problems with using large model,
  though:
  * Your program's data memory will be fixed in real mode.  Effectively, your
     application will cripple any real-mode Windows system.  (Of course, this
     problem doesn't exist with Windows 3.1!)
  * Your application will run more slowly, since all your data must be
     accessed through far pointers.
  * You may only be able to have one instance of your application active at
     any one time. This restriction is imposed by Windows on applications with
     multiple static data segements: in large model, that means most
     applications generated with C or C++.  Borland C++ 3.0+ and Microsoft
     C/C++ 7.0 will attempt to keep your static and global data in a single
     segment (although Microsof C5.1 an C6.0 will not), so as long as that
     data does not exceed 64K, you could run multiple instances of a large-
     model application created using those compilers.
  You should consider very carefully before you decide that large model is
  the only way to go; the preferred method is to use medium model, and to
  allocate far data as required.
  
  Another alternative is to use a compiler such as Watcom C/386 or Zortech
  C++ for development; this will let you use a single 4GB segment, and 32-bit
  registers, increasing your applications performace substantially (but
  limiting it to running in 386 enhanced mode).
  
  Finally, developing for Win32 (which encompasses both the upcoming Windows
  NT and Win32s) will allow you to use the flat 32-bit memory model without
  the restrictions and performance penalties associated with using a 32-bit
  environment on Windows 3.x.
  
...........................................................................

8.1.5.  Using STRICT with windows.h
-----------------------------------
  The Windows 3.1 windows.h file implements optional strict typechecking.
  You can turn this on by adding the statement
       #define STRICT
  before including windows.h.  Using strict typechecking will cause the
  compiler to issue warnings if you try to assign a window handle to a
  variable typed as HDC, for example, as it defines all handles as distinct
  datatypes.  Using strict is probably a good idea to avoid problems when
  moving to Windows NT and other future versions of Windows later.
  
---------------------------------------------------------------------------

8.2.  Debugging
===============
  
...........................................................................

8.2.1.  Turbo Debugger video configuration
------------------------------------------
  Borland's TDW debugger uses its own video drivers for single-screen Windows
  debugging.  As a result, you must make sure that the debugger can find the
  correct drivers.  As an example, if you are using an ATI Graphics Ultra (or
  Graphics Vantage), you need to make sure the following lines are in your
  tdw.ini file:
  
       [Debugger]
       VideoDLL=C:\TPW15\ULTRA.DLL
       
       [VideoOptions]
       DebugFile=C:\TDW.LOG
  
...........................................................................

8.2.2.  Turbo Debugger and Windows 3.1
--------------------------------------
  TDW 2.5 and TDW 3.0 need a new version of windebug.dll; otherwise the
  following error is being displayed while loading a .EXE file in the
  debugger:
       
       Cannot load WINDEBUG.DLL
  
  The new windebug.dll can be found at ftp.cica.indiana.edu in the directory
  /pub/pc/win3/programr/tp as "tpwin31.zip".  TDW 2.5 still has a few
  problems with Windows 3.1 (it sometimes generates exception 13 errors while
  stepping trough code), but this is a workable solution.
  
  TDW 2.51 (shipped with the Turbo Pascal for Windows 1.0 maintenance
  release) does not function correctly with Windows 3.1.
  
  TDW 3.1 (shipped with TPW 1.5 and BC++ 3.1) has been written for Windows
  3.1 and works correctly under both Windows 3.0 and 3.1.
  
  
...........................................................................

8.2.3.  Dr. Watson log files
----------------------------
  The Dr. Watson log shows the contents of the registers when you application
  crashed.  Even if you can't use it to determine contents of the variables,
  you can pinpoint the location of the crash.
  
  Make sure you keep a copy of the .map file generated by the linker for the
  version shipped to your customers; you can then look up the crash location
  manually from this file when you receive a Dr. Watson log.  If you linked a
  version with /CO /LI, the .map file will also contain line number
  information, allowing you to pinpoint the line in your program.
  
...........................................................................

8.2.4.  Programmer's WorkBench and tab characters
-------------------------------------------------
  If you want real tab characters inserted into your source files whenever
  you press Tab, specify RealTabs:Yes and Graphic:Tab in your tools.ini file.
  You can also quote a single tab character by preceing it with Ctrl+P.
  
---------------------------------------------------------------------------

8.3.  Resources and resource tools
==================================
  
...........................................................................

8.3.1.  Borland C++ Windows tools and Windows 3.1
-------------------------------------------------
  WinSight shipped with BC++ 3.0 does not work under Windows 3.1.  There is
  an update file available at ftp.cica.indiana.edu in the directory
  /pub/pc/win3/programr/bcpp as "wsupd1.zip".
  
  Dr. Frank does not work with Windows 3.1.  This program has been
  reincarnated as an official Borland tool and is shipped with BC++ 3.1 as
  "WinSpector".
  
...........................................................................

8.3.2.  Building a DLL with Zortech C++
---------------------------------------
  The key to succesfully building a DLL using Zortech C++ is to make sure
  that there is a LibMain function; it appears that Borland and Microsoft
  compilers include it automatically.  Simply insert the following fragment
  into your code:
     
     int FAR PASCAL LibMain(HANDLE hInst,   WORD wDataSeg,
                            WORD wHeapSize, LPSTR lpszCmdLine)
     {
     }
  
...........................................................................

8.3.3.  Extracting resources from an .EXE file
----------------------------------------------
  There are at least two alternatives: Borland's Resource Workshop (included
  with most of their Windows tools) allows you to extract and edit individual
  resources out of executable files.  Alternately you can get a program
  called rx (Resource eXtractor) by ftp from
  monu6.cc.monash.edu.au , in directory /pub/win3/programr.
  
  
...........................................................................

8.3.4.  Linking fonts into a .FON file
--------------------------------------
  The linker provided with the Windows 3.0 SDK will produce the following
  error when linking fonts:
      Link Error L2049: no segments defined
  
  The above LINK error is a bug in link. The fix is to run exehdr /r on the
  .exe file, and then run rc on it. The Windows 3.0 SDK linker incorrectly
  detects an error, and marks the resulting .exe file with some kind of error
  bit, even though the rest of the exe file is ok. Exehdr /r will reset this
  "error bit", after which rc will work just fine.
  
  An alternate fix is to use link4 from Windows 2.x SDK.
  
...........................................................................

8.3.5.  Running out of resources in Visual Basic
------------------------------------------------
  Visual Basic is limited to 255 controls per form, but the real limit
  depends on Windows system resources, which in turn depends on what kind of
  controls they are and what else you have going on in your system at the
  time.  Here's a simplified explanation:
  
  Windows does indeed give you access to many megabytes of memory (16Mb in
  Windows 3.0, 256Mb in 3.1).  However, some of that memory is "special"
  because Windows reserves several 64K segments for its own purposes.  The
  most significant of these segments for this discussion are the USER heap
  and the GDI heap.  This is where Windows keeps track of many of its
  internal data structures -- things like window handles, menus, (USER) and
  handles to bitmap, clipping regions, and fonts (GDI).  (Note that these are
  handles and related data structures, not the actual menus, bitmaps, or
  fonts themselves) Collectively,
  these two  segments are called "Windows resources" and whichever one of
  the two that is closest to being exhausted is reported as a percentage  (of
  64K) in the Help / About dialog in Program Manager.
  
  It should make sense then that as you approach 0% free in one of these two
  segments "strange things begin to happen" because Windows can't allocate
  space for new Windows or bitmaps or whatever.
  
  It is unfortunate that there is this 64K limitation: it arises because of
  Windows' roots in real mode.  Future versions (both NT and perhaps a future
  version of Windows for MS-DOS) will
  elminate this.  Windows 3.1 was an improvement over 3.0 because it split
  the single USER heap into several 64K heaps.
  
  Now, getting back to Visual Basic: every form is a window.  Every control
  is a window.  Every window consumes some of the USER heap (some more than
  others).  Every bitmap consumes some of the GDI heap; every picture box or
  form for which you've set AutoRedraw = True also consumes additional space
  in the GDI heap.  I'm sure you can see where this is leading: eventually,
  as you add controls, you run out of system resources.  The number at which
  you run out depends on what the controls are (picture controls chew up
  resources faster than labels), how many other forms and controls are
  currently loaded in your application, and how much of your system resources
  are being consumed by other
  applications you may be running at that time.
  
  How to work around this?  Only load the forms you need.  Unloaded forms
  (and the controls on them) don't use system resources.  Try to reduce the
  number of picture boxes.  Don't set AutoRedraw to True. If you're trying to
  display a lot of bitmaps or a grid of data, consider using a custom control
  designed to do that.  The grid control included in the Professional Toolkit
  allows you to display lots of information without using up the system
  resources you would if you placed it all in text boxes or labels.
  
  If you are running Windows 3.1 (and you really should be) you can call this
  function:
  
       Declare Function GetFreeSystemResources Lib "User" (ByVal
                        fuSysResource As Integer) As Integer
       
       Global Const GFSR_SYSTEMRESOURCES = &H0
       Global Const GFSR_GDIRESOURCES = &H1
       Global Const GFSR_USERRESOURCES = &H2
  
  This returns the percentage of free system resources For example:
  
       Print GetFreeSystemResources(GFSR_SYSTEMRESOURCES)
  
  This displays the lower of the GDI or USER heaps (this is exactly what is
  displayed in those about boxes).  If you want to find out how much space is
  left in USER, use  GFSR_USERRESOURCES; if you want to find out how much is
  left in GDI, use GFSR_GDIRESOURCES.  Using this function, you can determine
END--cut here--cut here