💾 Archived View for gemini.spam.works › mirrors › textfiles › internet › FAQ › faqclp.txt captured on 2022-04-29 at 15:11:37.

View Raw

More Information

⬅️ Previous capture (2022-03-01)

-=-=-=-=-=-=-

-Date: 2 Apr 1995 05:20:04 GMT
-From: jon@armory.com (Jon Shemitz)
-Newsgroups: comp.lang.pascal
-Subject: comp.lang.pascal FAQ, ver 0.43


I haven't had the time to do ANYTHING with this since January, when I took
a contract job that left me away from home (and w/out Net access) five days
a week.  While that was happening, we bought a house, so I've been busy
settling in for the past month.  I'll try to start an official release process
on this soon, so it IS on rtfm.mit.edu, and then maybe start collecting
Delphi questions.
_________________________________________________________________

                      COMP.LANG.PASCAL FAQ, VERSION 0.43

Table of Contents

          Preface

    1. Passing Parameters To Programs (Reading The Command Line)
       1.1 How do I read command line parameters?
       1.2 How do I read the whole command line?
    2. Logarithms, Trigonometry, and Other Numerical Calculations
       2.1 How do I calculate X^Y (aka X**Y)?
       2.2 How do I take 'un-natural' logarithms?
       2.3 Why do the trig functions like Sin() and Cos() give the wrong
       answers?
       2.4 How do I calculate ArcSin() or ArcCos()?
       2.5 Can I trap (and recover from) floating point overflow errors?
       2.6 Why do I always get the same random numbers every time I run
       my program?
    3. Strings and Numbers
       3.1 How do I convert a string to a number?
       3.2 How do I convert a number to a string?
    4. Breaking The 64K Limit
       4.1 I have 64K of global data and can't add anymore - what do I
       do?
       4.2 Can I have more than 64K of global data in my Windows program?
       4.3 How can I build an array bigger than 64K?
       4.4 How do I handle .BMP and/or .WAV files with more than 64K of
       data?
    5. Procedural Types
       5.1 Can I make an indirect call to an object's method, using a
       pointer or a procedural type?
    6. Windows Sound Programming
       6.1 Where can I find documentation on Windows sound programming?
    7. Other Pascal Resources


     _________________________________________________________________



Preface



     This is a still-rather-rough draft of a FAQ file for
     comp.lang.pascal. (I'm still not thrilled with the ASCII formatting
     I'm getting from lynx -dump, but the hanging indents and extra blank
     lines seem to be lynx bugs - the HTML Validation Service at
     http://www.hal.com/%7Econnolly/html-test/service/validation-form.htm
     l likes my HTML just fine.) Comments are more than welcome; when
     people are happy with the content (and I'm happy with the
     formatting) I'll submit it as an official news.answers/rtfm.mit.edu
     FAQ.



   I have only included answers to questions that seem to me to be
   genuinely frequently asked. I have no desire to write a Pascal primer,
   nor do I want to produce a intimidatingly large document. On the other
   hand, if this FAQ does get out of hand, it's being written and
   maintained as an HTML file, so you can use Mosaic or Lynx to jump
   straight from the Table Of Contents to the answer to a particular
   question. I'll post the ASCII version periodically to
   comp.lang.pascal; you can always get the latest version via the World
   Wide Web at <http://www.armory.com/~jon/clp-faq.html>.

   Most of the readers of comp.lang.pascal use Turbo/Borland Pascal.
   Certainly most of the FAQ's are about BP. Thus, this FAQ file deals
   primarily with Turbo/Borland Pascal. Some of the questions and answers
   may apply to other Pascal's, but it's up to you to decide if this is
   the case. In any case, this FAQ file is a free public service, and
   comes with no warranty as to accuracy. Use any information you find
   here with discretion and caution.

  CONTRIBUTORS
     * At this point, most of the material in this FAQ is by me, Jon
       Shemitz, aka [jds] or jon@armory.com.
     * Some of the material is borrowed from Professor Timo Salmi's
       (ts@uwasa.fi) Turbo Pascal FAQ file, which may be downloaded
       from ftp://garbo.uwasa.fi/pc/link/tsfaqp.zip. Such material is
       generally 'signed' [ts].
     * Duncan Murdoch, dmurdoch@mast.queensu.ca, contributed the material
       signed [dm].


     _________________________________________________________________

Passing Parameters To Programs (Reading The Command Line)



  1.1: HOW DO I READ COMMAND LINE PARAMETERS?

   The standard function ParamCount: word will return the number of
   command line parameters, while function ParamStr(N: word): string will
   return the N-th parameter. (Under DOS 3.0 or greater, the 0-th
   parameter (ie, ParamStr(0)) is the path and file name of the current
   program.) Thus,

        function CommandLine: string;
        var
          Idx:    word;
          Result: string;
        begin
          Result := '';
          for Idx := 1 to ParamCount do
            begin
            if Idx > 1 then Result := Result + ' ';
            Result := Result + ParamStr(Idx);
            end;
          CommandLine := Result;
        end;

   will return the whole command line, with any embedded whitespace
   (spaces or tabs) converted to single spaces. If you care about the
   amount or type of whitespace, or you want commas, semicolons, and
   equal signs to count as parameter separators (as per ancient versions
   of DOS manuals), see the next question:

  1.2: HOW DO I READ THE WHOLE COMMAND LINE?

   ParamCount and ParamStr are for parsed parts of the command line and
   cannot be used to get the command line exactly as it was. If you try
   to capture

        "Hello.   I'm here"

   you'll end up with a false number of blanks. For obtaining the command
   line unaltered use

        type CommandLines = string[127];

        function CommandLine: CommandLines;
        type
          CommandLinePtr = ^CommandLines;
        begin
          CommandLine := CommandLinePtr( Ptr(PrefixSeg, $80) )^;
        end;

   A warning. If you want to get this correct (the same goes for TP's own
   ParamStr and ParamCount) apply them early in your program, before any
   disk I/O takes place.

   For the contents of the Program Segment Prefix (PSP) see a DOS
   Technical Reference Manual (available on the Microsoft DevNet CD) or
   Tischer, Michael (1992), PC Intern System Programming, p. 753.

   - Based on [ts]'s Turbo Pascal FAQ
     _________________________________________________________________

Logarithms, Trigonometry, and Other Numerical Calculations

   I often find it convenient to define floating-point functions in terms
   of a type float = {$ifopt N+} double {$else} real {$endif}, rather
   than either explicitly using real or double. This way, the same
   function will automatically use the 'best' argument and result type
   for either the N+ (80x87) or N- state, without any changes and without
   obscuring its logic with lots of {$ifdef}s.

  2.1: HOW DO I CALCULATE X^Y (X**Y)?

   Pascals do not have an inbuilt power function. You have to write one
   yourself. The common, but non-general method is defining

        function POWERFN(number, exponent: float): float;
        begin
          PowerFn := Exp(Exponent*Ln(Number));
        end;

   To make it general use:

        (* Generalized power function by [ts] *)
        {  Some modifications by jds }
        function GenPowFn(Number, Exponent: float): float;
        begin
         if (Exponent = 0.0)
           then GenPowFn := 1.0
           else if Number = 0.0
             then GenPowFn := 0.0
             else if Abs(Exponent*Ln(Abs(Number))) > 87.498
               then RunError(205) {Floating point overflow}
               else if Number > 0.0
                 then GenPowFn := Exp(Exponent*Ln(Number))
                 else if (Number < 0.0) and (Frac(Exponent) = 0.0)
                   then if Odd(Round(Exponent))
                     then GenPowFn := -GenPowFn(-Number, Exponent)
                     else GenPowFn :=  GenPowFn(-Number, Exponent)
                   else RunError(207); {Invalid float-op}
        end;  (* genpowfn *)



   On the lighter side of things, here's an extract from an answer of
   mine [TS] in the comp.lang.pascal UseNet newsgroup:

   > anyone point out why X**Y is not allowed in Turbo Pascal?
          The situation in TP is a left-over from standard Pascal. You'll
          recall that Pascal was originally devised for teaching
          programming, not for something as silly and frivolous as
          actually writing programs. :-)



   The above is a lightly-edited version of the answer from [ts]'s Turbo
   Pascal FAQ. For the common special-case where you're raising a
   floating point number to an integral power (eg, X^7 or Y^3), you can
   use this fast code:

        function RealPower(Base: Float; Power: word): Float;
        begin
          if Odd(Power)
            then if Power = 1
              then RealPower := Base
              else RealPower := Base * RealPower(Base, Power - 1)
          else if Power = 0
            then RealPower := 1
            else RealPower := Sqr(RealPower(Base, Power shr 1));
        end;



  2.2: HOW DO I TAKE 'UN-NATURAL' LOGARITHMS?

   Just define

        function Log(X, Base: float): float;
        begin
          Log := Ln(X) / Ln(Base);
        end;

   This result is based on some elementary math. By definition y = log(x)
   in base B is equivalent to x = B^y (where the ^ indicates an
   exponent). Thus ln(x) = y ln(B) and hence y = ln(x) / ln(B).

   - A lightly-edited version of the answer from [ts]'s Turbo Pascal FAQ

  2.3: WHY DO THE TRIG FUNCTIONS LIKE SIN() AND COS() GIVE THE WRONG ANSWERS?

   While most people express angles in degrees, the trig functions expect
   their arguments to be in radians. For historical reasons, a complete
   rotation is 360 degrees; for more cosmological reasons, the same
   complete rotation is 2 * Pi radians (the circumference of a circle
   with a radius of 1). Thus, to convert degrees to radians, just divide
   by 180 / Pi.

  2.4: HOW DO I CALCULATE ARCSIN() OR ARCCOS()?

   Borland Pascal does not have ArcSin or ArcCos functions. It does have
   an ArcTan function, and the online help for that function gives the
   following conversion formulae:

        ArcSin(x) = ArcTan (x/sqrt (1-sqr (x)))
        ArcCos(x) = ArcTan (sqrt (1-sqr (x)) /x)



   Dr. John Stockton, jrs@merlin.dclf.npl.co.uk, points out that ArcTan
   will always return a value between -Pi / 2 and Pi / 2. Also, there are
   two angles in the range from 0 to 2 pi for any given sine or cosine
   value, even though the formulae above will only give you one of them.

  2.5: CAN I TRAP (AND RECOVER FROM) FLOATING POINT OVERFLOW ERRORS?

   Duncan Murdoch [dm] writes:

     Because the floating point processor operates in parallel to the
     integer processor, this is generally quite tricky. The best approach
     is not to trap the errors, but just to mask them, and at the end of
     a calculation check whether they have occurred by examining the
     coprocessor status word.

     Be aware that Borland's string-conversion routines (used in Str,
     Write and Writeln) clear the FPU's status word. If you do any I/O of
     floating point values in between status checks, you may miss seeing
     signs of errors.

   I would add that it's probably best to leave the floating point
   overflow exception on (unmasked) in the vast majority of your code,
   and that you should only mask it off around the few calculations where
   you expect and can handle a possible overflow. That is, a runtime
   error is probably better than allowing the program to continue with
   unnoticed math errors!

   The following demo code may be helpful:

        function Get87CtrlWord: word; assembler;
        var
          CtrlWord: word;
        asm
                fstcw   [CtrlWord]
                mov     ax,[CtrlWord]
        end;

        procedure Set87CtrlWord(NewCtrlWord: word); assembler;
        asm
                fldcw   [NewCtrlWord]
        end;

        function Get87StatusWord: word; assembler;
        var
          StatusWord: word;
        asm
                fstsw   [StatusWord]
                mov     ax,[StatusWord]
        end;

        const
          InvalidOp     = $01;
          DenormalOp    = $02;
          ZeroDivide    = $04;
          Overflow      = $08;
          Underflow     = $10;
          Precision     = $20;

        var
          Ctrl, Status: word;
          X, Y: double;

        begin
          Ctrl := Get87CtrlWord;
          Set87CtrlWord(Ctrl or Overflow); {Setting a bit masks the exception}
          X := 1e308; Y := X * X;
          Status := Get87StatusWord;
          Set87CtrlWord(Ctrl);
          if (Status and Overflow)  0    {A set bit indicates an exception}
            then WriteLn('Overflow flag set')
            else WriteLn('Overflow flag clear');
        end.

   [dm] adds

        const
          StackFault    =  $40;
          StackOverflow = $200;

   These bits are supported in the 387 and up; together they indicate a
   stack overflow (both set) or stack underflow (just StackFault).

  2.6: WHY DO I ALWAYS GET THE SAME RANDOM NUMBERS EVERY TIME I RUN MY PROGRAM?

   Software random number generators apply a function to a RandSeed which
   cycles the seed through its possible values in a quasi-random way.
   Each call to the random number generator does one iteration of the
   function, and returns a result based on the new seed value.

   When your program loads, this seed will have some default value
   (probably 0). If you do not change the seed, a series of calls to the
   random number generator will yield the same series of "random" numbers
   every time your program is run. (Obviously, this will make it easier
   to track down the bugs!) Typically, the system clock is used to
   provide a value for the random number seed: Even if a given task is
   always run at the same time of day, a difference of a few milliseconds
   is enough to put a good random number generator in an entirely
   different part of its sequence.

   In Borland Pascal, the command to randomize the seed is (surprise!)
   Randomize; in Think Pascal for the Mac, the equivalent command is
   QwertyUiop. Note that you should only call this routine once per
   program, when it first loads, or at the very least at times separated
   by minutes or hours - calling it on every timer tick (say) will just
   reset the 'random' sequence several times a second!
     _________________________________________________________________

Strings and Numbers

  3.1: HOW DO I CONVERT A STRING TO A NUMBER?

   In Turbo/Borland Pascal, you can use the standard Val() procedure. The
   first argument is a string, and the second argument is a numeric
   variable which will be set to the numeric value represented by the
   string. The result variable can be any of the numeric types - from
   byte or ShortInt to comp or extended - the compiler will automagically
   pass the variable's type to the Val() procedure.

   Val() is a procedure, not a function, which means that it does not
   return any sort of error code to indicate that it couldn't set the
   result variable because eg the string was 'Four score and seven' not
   '87', or because the string was '3.14159' and the result variable was
   a word, or even because '12261958' is too big for a word variable.
   Obviously, you do want to be able to find out whether Val() was able
   to decode the string. This is where the third parameter - an integer
   (or word) variable which will receive a result code - comes in. If the
   result code is 0, then the string represents a number which could be
   (and was) placed in the result variable; a nonzero result code means
   that the string does not represent a compatible number: the result
   variable has not been changed, and the error code is the index of the
   first illegal character in the string.

   Note that Val() and ReadLn() can handle hexadecimal numbers, using the
   $1234 format.

  3.2: HOW DO I CONVERT A NUMBER TO A STRING?

   In Turbo/Borland Pascal, you can use the standard Str() procedure. The
   first argument is a number, and the second is a string variable which
   will be set to the formatted value. Just as with Write() and
   WriteLn(), the number may (but does not have to) be followed by a
   colon and a width specifier and (for float point numbers) a second
   colon and a number of digits after the decimal point.

   Str() is a procedure, not a function, which makes it a lot harder to
   use than, say, WriteLn(). Obviously, it's pretty trivial to put a
   'wrapper' around it so that you can write string expressions like
   Fmt(Month,2) + DateSep + Fmt(Day,2) + DateSep + Fmt(Year,2):

        type NString = string[20];

        function Fmt(N: LongInt; Width: word): NString;
        var Result: NString;
        begin
          Str(N: Width, Result); {A width of 0 is the same as no width at all}
          Fmt := Result;
        end;



   While Val() can read hex strings, there's no way to force Str() to
   produce a hex string. For that, you can use

        type HexString = string[8];

        function HexFmt(Int: LongInt; Width: integer): _HexFmtResult_;
        const Hex: array[0..$000F] of char = '0123456789ABCDEF';
        begin
          if (Width <= 1) and (Int and $FFFFFFF0 = 0)
            then HexFmt := Hex[Int]
            else HexFmt := HexFmt(Int shr 4, Width-1) + Hex[Int and $000F];
        end;


     _________________________________________________________________

Breaking The 64K Limit



  4.1: I HAVE 64K OF GLOBAL DATA AND CAN'T ADD ANYMORE - WHAT DO I DO?

   What you have to do is to find the largest data structures. A good
   way to do this is to look at a .map file with the addresses of
   "public" names, and look for large jumps between names. Often, a big
   jump flags a big variable. However, the .map file will not show any
   variables that are private to a unit, so a big jump may just indicate
   a lot of private variables within a unit. In that case, you'll have to
   look at the unit's source code to see what's taking up so much space.

   When you find your largest data structures, look at how they're used.
   (The cross-reference tool in the BP7 IDE is an excellent way to do
   this.) If they're only used in a single function or procedure, you can
   move them into that procedure's local variables section, which will
   get them out of the global data segment and onto the stack.

   Of course, this may now cause the stack to be too big. If so, or if
   the data are used in more than one place, you'll have to move the data
   structure onto the heap. To do this, you'll have to do three things:
    1. Replace a declaration like
var BigVar: BigType;
   with
var SmallVar: ^BigType;
    2. Change all references to BigVar to SmallVar^. Because BP is so
       fast, it's usually quite practical to just keep compiling until
       you get no more 'BigVar unknown' or 'need a ^' errors.
    3. Somewhere in your program initialization, before you ever execute
       any code that refers to SmallVar^, do a New(SmallVar); to allocate
       space on the heap.

       It's a good idea to get in the habit of using Dispose() whenever
       you no longer need the space you've allocated, but it's not
       strictly necessary, here: neither DOS nor Windows will 'lose' any
       memory that you allocate but don't free when your program
       terminates.



   One thing to keep in mind is that reading or writing (dereferencing) a
   pointer is slower than reading or writing a global or local variable,
   especially in protected mode. Generally, you shouldn't worry about
   this sort of thing except in bottleneck code but, where efficiency
   does matter, you should definitely replace a series of dereferences
   of the same pointer with a single with statement. (BP7 is
   significantly better at "peephole" optimization than its predecessors,
   but even in BP7 there are cases where a with PtrVar^ statement
   produces better code than the equivalent series of 'raw' pointer
   statements. Certainly replacing multiple instances of Row[Idx]^ or
   even RecordPtr^.RecordField with a single with will result in smaller
   and faster code.)

   Finally, a word of admonition: You should use global data very
   sparingly. If you are pushing the 64K limit, and it's not all in a
   handful of large buffers, you're almost certainly using too many
   global variables. Obviously, global variables that are only used in
   one place waste space; less obviously, using global variables tends to
   produce bugs. A seemingly innocuous change to a variable here can have
   effects way over there. You can fight this sort of potentially
   crippling interdepence by explicitly passing parameters to a function,
   and explicitly returning results. It can be impossible to totally
   avoid writing code that has side-effects, but such code should be
   avoided as much as possible -- and always documented.

  4.2: CAN I HAVE MORE THAN 64K OF GLOBAL DATA IN MY WINDOWS PROGRAM?

   Yes and no. Your .exe module can only have a single 64K
   data-and-stack segment, but each .dll has its own, up-to-64K data
   segment. Thus, if you run out of room in your global data segment, you
   can move some units and their global data into a DLL.

   There are two things to keep in mind if you do this:
    1. A call to a DLL is more expensive than a call within an EXE. Each
       call into a DLL swaps data segments on the way in and on the way
       out.
    2. If a user is running two or more copies of your program at the
       same time, each copy shares the code, but has its own copy of the
       .exe's global data. However, there will only be one copy of the
       .dll's global data, no matter how many copies of your application
       (or how many different applications) are using it.

       That is, any global variables in your .exe are a task resource;
       any global variables in a .dll are a system resource.



  4.3: HOW CAN I BUILD AN ARRAY BIGGER THAN 64K?

   Break the array into two (types of) pieces: an array of row ptrs, and
   a set of rows on the heap. That is, replace

        type BigArrayType = array[0..Rows, 0..Cols] of DataType;
        var  BigArray: BigArrayType;

   with

        type BigArrayRow  = array[0..Cols] of DataType;
             BigArrayType = array[0..Rows] of ^ BigArrayRow;

        var  BigArray: BigArrayType

   and replace any references to BigArray[Row, Col] with references to
   BigArray[Row]^[Col]. (As per the answer to 4.1, don't forget to
   allocate memory for each row!)

          If performance isn't a major issue, you might well want to
          encapsulate the array behavior in an object. That is, instead
          of replacing BigArray[Row, Col] with BigArray[Row]^[Col], you
          would replace it with calls to BigArray.Get(Row, Col) or
          BigArray.Set(Row, Col, NewVal). While this is a bit slower than
          direct array references, the object (compiled) code is smaller
          and the source code is both easier to read and easier to
          change. If you need to add virtual memory (swapping to and from
          disk, EMS, or XMS) or to move your code to a 32-bit compiler,
          you would only have to change the object definitions, not every
          reference to your big array.

          (One way to maintain both modifiability and good performance is
          to replace the array of row pointers with a function that
          returns a row pointer. Since you only need to call this once,
          no matter how many operations you're doing on the row, using
          your huge array might not be much slower than a normal array.
          Since the function can do anything from simply looking up a
          pointer in an array to swapping out the Least Recently Used row
          and swapping in the one you need, you maintain flexibility.)



    If the array has a lot of rows, and the rows are not some multiple of
   8 (or 16 bytes) long, you might end up wasting a lot of space at the
   end of each row. If so, and if you're just barely running out of room,
   you might want to New() a "multi-row" type, that consists of an array
   of 8 (or 16) individual rows. This will eliminate the wasted pad
   bytes, and will only complicate your setup and teardown code: since
   the row pointer array will still point to the start of each individual
   row, access to any individual array element will be no different than
   if each row is its own heap block.

   Of course, decomposing an array in this way only works if the array
   has two (or more) dimensions. See the next question if you have to
   deal with a one-dimensional data stream that's larger than 64K.

  4.4: HOW DO I HANDLE .BMP AND/OR .WAV FILES WITH MORE THAN 64K OF DATA?

   There are two issues here: allocating a single heap block that's
   bigger than 64K, and accessing it. Both are significantly different in
   real mode than in protected mode.

   Real mode:
          In real mode, the primary issue is allocating the space: BP's
          heap manager won't let you allocate blocks bigger than 63 and a
          bit K-bytes. One approach is to simply rely on the current
          implementation's behavior and break your large heap request
          into multiple subrequests. If previous allocation and
          deallocation hasn't left the heap fragmented, back-to-back
          allocations will all be contiguous: The last byte of one will
          be just below the first byte of the next. This behavior is not
          guaranteed by Borland, though, and may change in future
          releases; similarly, if the heap is fragmented, back-to-back
          allocations will probably not be contiguous. A better approach
          is to restrict the size of the heap, and to use DOS services to
          allocate memory outside of your .exe's memory map.

          However you allocate it, you will still have to access it. In
          real mode, this is simply a matter of understanding that the
          segment part of a pointer is multiplied by 16 and added to the
          offset part to obtain a linear address within the first meg of
          memory. You can thus use a function like


        function RealPtrTo(Base: pointer; Offset: LongInt): pointer;
        {Note:  This function has NOT been pulled from existing code
         and has NOT been tested.  JDS, 30 September 1994}
        var Normal: record Segment, Offset: word; end;
        begin
          Normal.Segment := Seg(Base^) + Ofs(Base^) shr 4;
          Normal.Offset  := Ofs(Base^) and $000F;
          {Normalise the Base pointer}
          Inc(Offset, Normal.Offset); {# bytes from start of Base seg}
          RealPtrTo := Ptr(Normal.Segment + Offset shr 4,
                           Normal.Offset + Offset and $0000000F);
        end;

   to generate a pointer to any byte within your huge data structure.
          This pointer can then be used just as you use any other 16-bit
          pointer, to address an up-to-64K window within your huge data.

   Protected mode:
          In protected mode, allocation is easy, and access is hard. Just
          use GlobalAlloc() or GlobalAllocPtr() to allocate however much
          space you need. (Well, if your program is running in Windows
          standard mode, this had better be less than or equal to one
          meg.)

          Once you've allocated it, life continues to be easy - if you're
          using 386 Enhanced Mode and a 32-bit compiler or assembler. The
          selector you get from GlobalAllocPtr can be used with any
          32-bit offset within the declared size of the heap block. If
          you're using Standard Mode, or 16-bit compilers like TPW or
          BP7, though, you'll have to work a bit.

          To start with, you can't do segment arithmetic: segments have
          been replaced with selectors, which are essentially indices
          into a system-global table of allocated segments. Doing
          arithmetic with selectors will either result in value that
          points to the wrong selector or (more likely) produce an
          invalid selector. In either case, using the resulting pointer
          will probably produce a General Protection Fault.

          This is a complicated subject that I've already covered
          elsewhere (see http://www.armory.com/~jon/pubs/huge-model.html
          for my PC Techniques article on huge model programming): All
          I'll say here is that

         1. You have to use SelectorInc to 'step' the selector from one
            64K window to the next, and
         2. You can't make a single reference that starts in one 64K
            window and ends in the next.


     _________________________________________________________________

Procedural Types

  5.1: CAN I MAKE AN INDIRECT CALL TO AN OBJECT'S METHOD, USING A POINTER OR A
  PROCEDURAL TYPE?

     Yes, but you'll need to make aggressive use of casting, and to have
     a bit of background on just what a method call is. While method
     calls look and act very differently than normal calls -- the call
     looks like a reference to one of the object's fields, and there's
     the implicit with Self do that lets us refer to the object's fields
     as if they were global variables -- at the level of words on the
     stack they're not all that different from a normal procedure or
     function call. All methods have an `invisible', or implicit,
     parameter, var Self, after any regular, or explicit, parameters;
     constructors and destructors also add an implicit word parameter
     (the 16-bit VMT pointer) between the explicit parameters and Self.
     Also, while constructors act as if they return a boolean, they
     actually return a pointer which contains @ Self if Fail was not
     called, and Nil if it was.

     The implicit parameters and the special handling of constructor
     results are the only differences between method calls and normal
     calls: there's no magic involved. If we simply define a procedural
     type, ProcType, that explicitly declares the method's implicit
     parameters after any normal parameters, we can then use ProcType to
     cast any pointer variable to a procedural variable. Once it's cast,
     the pointer acts just like a normal procedural variable; we can
     assign it to another procedural variable or use it to call a
     procedure. Just as with a normal procedural type, the only
     difference between a direct and indirect call lies in the way we
     make the call: The parameters are pushed and popped in the same way;
     the called code operates just the same; and indirectly called
     methods have the same full access to their object's fields (through
     the Self pointer) as directly called methods do.

     Thus, if we have a method with no arguments and no results, we would
     simply make the declaration type Niladic = procedure (var Self);. To
     use it, we remember that we can only cast pointer variables, not
     pointer expressions, and so do something like PtrVar := @
     ObjectType.Method; Niladic(PtrVar)(Self); Now, while there is
     something strange looking about a cast (in parentheses) followed by
     an argument list (in parentheses), indirect method calls are
     typically rare and concentrated in a few key routines, even in
     programs that rely heavily on them. (My typical uses for indirect
     method calls involve things like executing a list of object/method
     pairs on every timer tick, or calling a window object's message
     handler after DMT lookup reveals that it does have a handler.)
     What's more, the strange look of an indirect method call does not
     translate into strange object code: Using a cast to a procedural
     type generates the exact same code as using an normal procedural
     type, and that's both a little smaller and only slightly slower than
     a normal, direct procedure or function call.

     Methods that require parameters or that return results are only
     slightly different than our Niladic example above. We simply have to
     remember to put any explicit parameters before the implicit
     parameter(s). Thus, we might use type SimplePredicate = function
     (var Self): boolean; for a method that takes no arguments and
     returns a boolean, and type UntypedDyadic = procedure (var A, B; var
     Self); for a method that requires two untyped memory references.

     Just as with a normal procedure call, the compiler will not let us
     make an indirect method call with the wrong number or type of
     arguments. This is obviously desirable behavior, but it's tempered
     with a bit of a caveat: When we make a cast, we are effectively
     telling the compiler that we know exactly what we are doing. If we
     accidentally use a pointer to a UntypedDyadic method as a Niladic,
     the compiler will neither require nor accept the two var parameters
     to the UntypedDyadic method but the procedure will probably use them
     and the result will not be pretty! Similarly, the compiler will not
     complain if you cast a data pointer or the address of a near routine
     into a procedural type: it will blithely generate code that will (at
     best!) crash your computer.

   This answer is based on my [jds] article Three Myths About Procedural
   Variables which originally appeared in the Dec/Jan 1993 issue of PC
   Techniques.
     _________________________________________________________________

Windows Sound Programming



  6.1: WHERE CAN I FIND DOCUMENTATION ON WINDOWS SOUND PROGRAMMING?



   For a start, try MMSYSTEM.HLP, in your \bp\bin directory.
          (The BP7 install program creates an icon for this in the
          Borland Pascal program group, but many people simply copy the
          BPW icon to a working group, and zap the BP group. Then, when
          they want to do some sound programming, they find there is no
          mention of it in the online help file, and they don't know
          where to turn.)

   Unfortunately, MMSYSTEM.HLP only contains part of material in the
   SDK's Multimedia Programmer's Reference. Piecing together the sequence
   of steps to properly open a MIDI or WAV device from the help file can
   be tough! I strongly recommend the SDK - or, better, the DevNet CD.
     _________________________________________________________________

Other Pascal Resources

     * Professor Timo Salmi (ts@uwasa.fi) of the University of Vaasa in
       Finland maintains a Turbo Pascal archives, which includes both
       source code and a Turbo Pascal FAQ file. You can find these by
       ftp-ing to garbo.uwasa.fi and looking in the /pc/ts directory.
     * I have written a number of articles on Turbo Pascal esoterica. An
       online bibliography is available via the World Wide Web at
       http://www.armory.com/~jon/Publications.html. Some of the actual
       articles are also available online and I can post most of the
       others on request.
     * I heartily recommend Microsoft's DevNet CD. It's not exactly a
       Pascal resource, but many of the questions on comp.lang.pascal
       these days have to do with Windows programming. The CD includes
       not only the reference material in the .HLP files but also
       electronic copies of the SDK's programmer's guides; Petzold's
       Programming Windows 3.1; DOS and hardware references; and much,
       much more. Best of all, it's extensively indexed, so it's very
       easy to find the answers to your questions. If you're at all
       serious about Windows programming, get a CD-ROM drive and this CD.
       (For somewhat more info, you can see the on-line version of the
       review I wrote for PC Techniques at
       http://www.armory.com/~jon/pubs/DevNet-CD.html).


     _________________________________________________________________

    Jon Shemitz - jon@armory.com - 30-Sep-94..7-Jan-95
    HTML 2.0 Checked!
--

http://www.armory.com/~jon                      Personal and Technical Pages
http://www.armory.com/~jon/hs/HomeSchool.html   Home School Resource Pages