806 lines
36 KiB
Plaintext
806 lines
36 KiB
Plaintext
-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
|