|
Data types
25.55 becomes to 24.5499999 in a field of a calculated paradox-field
Array of const
Create 'dynamic' array
Static Variables
Dynamic Components Solution
Class/Static/Shared Variables
Creating many instances
Difference of TMemoFields
Using integer pointers
MS Binary Format / IEEE conversion
Array of the Image Object
Pass a record problem
Function pointer
Converting Real to a fraction of two integers
Resizing (Dynamic) Arrays
Use Free with records
Create large arrays
Pointer to a function
Pass a function as a parameter
Array of TPoints
Function return type
25.55 becomes to 24.5499999 in a field of a calculated paradox-field
Question
Why 25.55 become 24.499999?
I have a paradox table that contains currency values. I am trying to
calculate totals for some of the fields programmatically with the
following code:
Table1.Open;
Table1.first;
VATTotal := 0;
SalesIncVAT := 0;
While not Table1.EOF do
begin
VATTotal := VATTotal + Table1VAT.value;
SalesIncVAT := SalesIncVAT + Table1GrandTotal.value;
Table1.next;
end;
Label2.caption := FloatToStr(SalesIncVAT);
Label3.caption := FloatToStr(VATTotal);
Label1.caption := FloatToStr(SalesIncVAT - VATTotal);
Answer
A:
The label captions all show the same type of answer as the subject
line. Can someone explain what I've done wrong.
You're not to blame for the calculation error!
I've found the same type of problems in an accounting package I'm writing
(=serious!).
Seems to me that Borland has some calculating coding to do.
You can get around the problem using the round function.
SalesIncVAT:=round(SalesIncVAT*100)/100; {gives you two decimal places}
A:
that's nothing extraodrinary, this is a basic property of floating point
math, which is precise only to a given number of decimal digits. More
specifically float is precise for intermediate range integers and for
fractions which are a sum of components which are powers of 2, any other
number is rounded at the float accuracy (7 digits for single precision, 15
for double, 20 for extended). One has to use round or the str procedure:
var
s : string;
begin
str(SalesIncVat:10:2,s); {10 characters altohether (with point) and 2
decimal digits}
Label1.Caption:=s;
A:
In the help it is written that FloatToStr formats the string with 15 decimal
digits - that is why your number is displayed in a fancy fashion, try this
function with a number like 25.5, 25.25, 25.125 or so i.e. one that has a
finite representation in binary notation and you should get it back.
Or use the function FloatToStrF which takes also specification of total
width and number of decimal digits as its arguments.
Array of const
Question
InsertRecord() takes an array of const. How do I build an array of const
at runtime?
Answer
A:
procedure foo(a : array of const);
implementation
var
var1 : longint;
var2 : pointer;
var3 : integer;
begin
var1 := 12345678;
var2 := @var1;
var3 := 1234;
foo([var1, var2, var3]);
Actually an array of const is more correctly called array of tvariant.
Tvariant is a multiple choice kind of variable that can take an number of
types. It has its heritage in Visual Basic. Delphi allows to use either name.
A:
Define a type such as:
TYPE
NAME1 = Array[1..4,1..10] of Integer;
Then, in your CONST section:
NAME2 : NAME1 = ((1,2,3,4,5,6,7,8,9,10),
(1,2,3,4,5,6,7,8,9,10),
(1,2,3,4,5,6,7,8,9,10),
(1,2,3,4,5,6,7,8,9,10));
Create 'dynamic' array
Question
How do I create a 'dynamic' array? I need to vary the number of elements
in the array at run-time.
Answer
A:
Assuming you want to store "GIZMOS" in your array, try the following:
CONST
MaxGIZMOS = $FFFF Div (SizeOf(GIZMOS)) { Or whatever the maximum number of
GIZMOS is going to be...}
TYPE
pGIZMOArray = ^GIZMOArray;
GIZMOArray = Array[1..MaxGIZMOS] of GIZMOS;
VAR
TheGIZMOS: pGIZMOArray;
GIZMOcount: integer;
BEGIN
GetMem(TheGIZMOS,(GIZMOcount+1)*SizeOf(GIZMO)); {Need 1 extra as GetMem
array is zero-based...}
TheGIZMOS^[index] := Whatever;
etc...
A:
TList is such a dynamic array. Look for details in the Help section. If you
want to do it yourself you have to use GetMem to allocate a pointer to dynamic
memory and later FreeMem to release the space of the dynamic array. Tlist does
all that for you in a painless way.
Static Variables
Question
Does DELPHI allow static variables? Static, in the C sense, a variable declared
in a procedure that does not disappear when the procedure is exited, and is
available when the procedure is next entered.
Answer
A:
Yes, it does. You have declare the variable in the const section,
for example.
procedure P;
const
MyVariable : Integer = 0;
begin
Inc(MyVariable);
end;
In this example MyVariable holds the P calls number.
However, often it is a better to use the field of the object instead
(if possible).
Dynamic Components Solution
Question
How to dynamic component creation?
Answer
A:
Warning to Newbies:
If you simply want to create a component whos type you know at compile time
then you should read the delphi\doc\VB2Delph.wri file to see how this is done,
it also explains control arrays and is just darn good reading anyway. This post
explains how to utilise the RTTI facilities in Delphi.
The first thing that your application needs to do is register all the classes
that you know you are going to need create at some stage in your program. This
can be done using either the RegisterClass(), RegisterClasses() or
RegisterClassAlias() functions.
eg.
procedure TForm1.FormCreate(Sender: TObject);
begin
RegisterClasses([TButton, TEdit, TMemo, TLabel]);
end;
This may seem like a limitation but then Delphi is a static language. If you
want true dynamic creation of objects in a weakly typed late bound environment
then use a dynamic language like Smalltalk. I have a feeling that Delphi uses
this registration mechanism to register all the components in the DCL when it
starts up, which allows it to create any component at design time.
Creating the components. Use the FindClass() function to return a class
reference to the component that you want to create and call its Create method.
Easy, isn't it? In the example I have typecast SomeComponent to a TControl so
that I can set its parent property (I can do this because I know that all the
classes that I registered were descendants of TControl). You need to set the
parent property of a control to make it appear on the form.
eg.
procedure TForm1.CreateClick(Sender: TObject);
begin
SomeComponent:= TComponentClass(FindClass(ClassName.Text)).Create(Self);
(SomeComponent as TControl).Parent := Self;
end;
Now that you have the component, how do you set it's properties without using
the biggest case statement in the universe? Use the GetPropInfo() function to
get the run-time type information (RTTI) structure for the property, and then
use the SetXXXXProp() set of functions to set it's value. (Note: These
functions are not documented in the Delphi help files. OO programming means
reading other peoples code and building on it, not reinventing the proverbial
wheel.) Each SetXXXXProp() function also has an equivalent GetXXXXProp()
function so you can inspect an objects property values.
eg.
procedure TForm1.SetPropertyClick(Sender: TObject);
var
PropType: PTypeInfo;
PropInfo: PPropInfo;
begin
PropInfo := GetPropInfo(SomeComponent.ClassInfo, PropertyName.Text);
PropType := PropInfo^.PropType;
case PropType^.Kind of
tkInteger:
SetOrdProp(SomeComponent, PropInfo,
StrToInt(PropertyValue.Text));
tkChar:
SetOrdProp(SomeComponent, PropInfo, Ord(PropertyValue.Text[1]));
tkEnumeration:
SetOrdProp(SomeComponent, PropInfo, GetEnumValue(PropType,
PropertyValue.Text));
tkFloat:
SetFloatProp(SomeComponent, PropInfo,
StrToFloat(PropertyValue.Text));
tkString:
SetStrProp(SomeComponent, PropInfo, PropertyValue.Text);
end;
end;
You can also set the values of Set, Class and Method properties but this can be
a little bit more complicated. I might post how to do that a little bit later.
And that's it. It's pretty amazing what you can find out by reading the VCL
source code.
This is tempting feature, but has potential for mass abuse. There is no
substitute for a good understanding of the other ways of achieving the same
things in Delphi and choosing the technique most appropriate to your design.
Class/Static/Shared Variables
Question
I have been generally pleased with Delphi's level of Object Orientation,
but it does seem to be missing Class Variables. It would be nice to be
able to have a class/static/shared variable that gets updated every time
the class is instantiated - like so:
type
TNode = class
public
NodeCount : Integer = 0; {THIS IS NOT ALLOWED}
constructor Create;
{
other stuff
}
end;
TNode.Create;
begin
inherited Create;
Inc(NodeCount);
end;
It seems the compiler won't allow the typed constant within a class type
declaration. So the NodeCount variable gets reinitialized for every
instantiation of the TNode class. It will work by putting the class
variable OUTSIDE of the type statement as a plain-old typed constant.
But then rather than having OOP looking code like:
SampleNode := TNode.Create;
x := SampleNode.NodeCount;
You have to resort to semi OOP/procedural code like:
SampleNode := TNode.Create;
x := NodeCount;
Answer
A:
It's not a big deal to get equivalent
functionallity by using a class method. You simply declare NodeCount as
a regular typed constant in the implementation section of your file.
type
TNode = class
public
NodeCount : Integer = 0; {THIS IS NOT ALLOWED}
constructor Create;
Class Function GetNodeCount : word;
{
other stuff
}
end;
implementation
const
NodeCount : word = 0;
TNode.Create;
begin
inherited Create;
Inc(NodeCount);
end;
Function TNode.GetNodeCount : word;
begin
result := NodeCount;
end;
So your code will look just as you want:
SampleNode := TNode.Create;
x := SampleNode.GetNodeCount;
the follwing form is also legal:
x := TNode.GetNodeCount;
Creating many instances
Question
How do I create many intances of an object. Sometimes I need to create
10 or so instances and sometime I need 1000 instances. How should I
do it if I have an object named TSSObject.
TSSObject = class(TObject)
n1 :Single;
n2 :Single;
end;
Answer
A:
list:=Tlist.create;
For i:= 1 to 1000 do
begin
SSObject:=TSSObject.create;
{put the newly created object somewhere - eg on a Tlist}
list.add(SSObject);
end;
Difference of TMemoFields
Question
While I'm on the subject can someone please explain to me why this doesn't
work! I'm afraid I just don't understand pointers all the time.
procedure TForm1.Button1Click(Sender: TObject);
var
Buffer: Pointer;
MyStrings: TStringList;
begin
MyStrings := TStringList.Create;
GetMem(Buffer, Table1Notes.DataSize);
{After this call Pointer's Addr becomes nil?}
Table1Notes.GetData(Buffer); {Of course this doesn't work, Pointer is nil }
FreeMem(Buffer, Table1Notes.DataSize);
MyStrings.Free;
end;
Answer
A:
First, if the size argument to GetMem is zero, GetMem will set the pointer
to nil (not leave it the way it was, but actually assign nil to it). Might
want to check the value of DataSize (or getTextLen) in your debugger before
the call.
(Ignore this paragraph if Table1Notes is not a memo.)
Second, if Table1Notes is a memo field, you probably want to use
Table1Notes.getTextLen, not DataSize, since DataSize gives you the length of
the record buffer segment (0-254) while getTextLen gives you the actual size
of the memo. (For a string field, DataSize will work, but then it's curious
as to why it's zero.) Also, might want to consider getTextBuf rather than
getData, don't exactly know why, but I played around with them awhile ago
and getTextBuf seemed to work while getData didn't.
A:
Since wordwrapping is your application, you can just replace the #10 (line
feed) and #13 (carriage return) characters with spaces, eg.
cursor: pchar;
cursor := your buffer;
while cursor^ <> #0 do
if (cursor^ = #13) or (cursor^ = #10) then cursor^ := ' ';
This is easy because we don't have to move any text around, although it does
mean there'll be two blanks at the end of each line, which shouldn't matter
since you're word wrapping anyway. Alternatively, you can change them to
some special character that your word wrapper recognizes as a word break but
otherwise discards (eg. #8). If you need to get rid of them, use two
cursors as follows (OTTOMH, not tested):
out, in: pchar;
out := your buffer;
in := out;
while in^ <> #0 do begin
if (in^ <> #10) and (in^ <> #13) then begin
out^ := in^;
inc (out);
end;
inc (in);
end;
out^ := #0;
A:
If you instead want to replace every CR-LF pair or a single CR or LF with a
single space you could use this:
out, inn: PChar;
out := your byffer;
inn := out;
while in^ <> #0 do begin
if (in^ = #10) then begin
end
else if (in^ = #13) then begin
if (in+1)^
A:
If you instead want to replace every CR-LF pair or a single CR or LF with a
single space you could use this:
out, inn: PChar;
out := buf;
inn := out;
while inn^ <> #0 do begin
if (inn^ = #10) or ((inn^ = #13) and ((inn+1)^ <> #10)) then begin
out^ := ' ';
Inc(out);
end
else if (inn^ = #13) then
{ CR alone - ignore }
else begin
out^ := inn^;
Inc(out);
end;
Inc(inn);
end;
out^ := #0;
{ buf is now massaged }
Untested is: the effect of shortening (by way of setting the #0 terminator)
this PChar - safest to use compile time arrays or GetMem'ed buffers, what
would happen using StrAlloc/StrDispose?
A:
Here is
the final code after some small changes! For example, at the end, we needed
to tell the pointer to go back to the beginning of its new string.
procedure TForm1.RemoveSpaces(var InBuf: PChar; Size: Word);
var
Input,
OutPut,
Orig: PChar;
begin
GetMem(Output, Size);
input := Inbuf;
Orig := Output;
while input^ <> #0 do
begin
if (input^ <> #10) and (input^ <> #13) then
begin
output^ := input^;
inc (output);
end;
inc (input);
end;
Output^ := #0;
Output := Orig;
InBuf := Output;
end;
I still wonder about that darn GetData thing! I would still like to not have
to use a TMemo! If Anyone out there can solve this problem I'd be very
greatfull! I'd give you a complimetary first hand look at my new printing
rountines! That's what this whole mess is for anyhow! So far I have implemented
text output in any font at any position in inches, and all that basic stuff!
But what I think is really cool, Is my dynamic grid! You can create a grid of
any number of rows and column. Assign text to and cell, setting its Horizontal
AND Vertical Justification, Select border style for each cell and many ways to
manipulate and print these!
Using integer pointers
Question
var
myptr: PInteger;
begin
GetMem(myptr, 10);
(myptr + 1)^ := 1; { How can I do this? }
FreeMem(myptr, 10);
end;
Answer
A:
You would have to create the type first.
Type
Pinteger : ^Integer;
Var
MyPtr : Pinteger;
Possibly you have used a bad example but it does not seem to make sense to
use a (32 bit) pointer for a 16 bit value or to allocate 10 bytes for the
variable.
Pascal allows you to use NEW and DISPOSE which automatically allocates and
de-allocates the correct size block.
ie NEW(MyPtr) = GetMem(MyPtr, Sizeof(MyPtr)).
Presumably you wish to do a calculation on a variable number of integers.
In that case have a look at TList in the help. Better yet use a linear
array (or a pointer to one if there could be enough elements to make memory
an issue).
A:
For completeness, this should be
NEW(MyPtr) = GetMem(MyPtr, SizeOf(MyPtr^));
SizeOf(MyPtr) will always be 4 bytes, as 16 bit pointer.
If I understand what you want, a dynamic array of integers, since you know
how many you want at runtime, you could also do
Type
pIntArr = ^IntArr;
IntArr = Array[1..1000] of Integer;
Var
MyPtr : pIntArr;
Begin
GetMem(MyPtr, 10); { 10 = SizeOf(Integer) * 5 !!)
{ MyPtr[2]:=1; }
<<<< My turn to be incomplete !! >>>>
MyPtr[2]^:=1;
FreeMem(MyPtr,10);
End;
A:
It looks like Delphi makes a special case of pchar. Best I could figure out
that would let you use the syntax you want is:
type
intarray = array[0..20000] of integer;
procedure TForm1.Button1Click(Sender: TObject);
var
xptr: ^IntArray;
begin
GetMem(xptr, 10);
xptr^[idx] := 1; { where idx is 0 to 4 since we have 10 bytes = 5 integers }
FreeMem(xptr, 10);
end;
Note that you don't really need to allocate an array of 20,000, but Delphi
range checking won't work unless it is 20,000. (Pointer users caveat
emptor!)
MS Binary Format / IEEE conversion
Question
Couple of questions:
1. Does Delphi store real numbers in "Microsoft Binary Format" or "IEEE"?
2. How can I call a small segment of assembler code within my Delphi code?
Can I embed assembler routines within Delphi? Please note my assembler
exposure is very limited, but I have also wondered how I can do this, in
both C and now Delphi.
Answer
A:
"whatever the base-level machine uses" is not so straightforward before
Intel's 80x87 numeric coprocessors came along. I'm not sure if the 80x86
processors had any native instructions to perform floating point
arithmetic. This could be why Microsoft created their own proprietary
format for floating point numbers; they had to do all the arithmetic
themselves, using their own runtime library. Today, the 80x87 makes the
arithmetic automatic, and IEEE is now the standard.
Delphi does store the following floating point types in IEEE format:
Single 4 bytes
Double 8 bytes
Extended 10 bytes
Note that Real (6 bytes) is not on this list. I may be wrong, but I
believe Real is an intrinsic Pascal type; its existence may predate the
80x87.
[Aside: Delphi's online help says that, by default (via the $N+ compiler
directive), the compiler will generate code to perform ALL floating point
calculations using 80x87 instructions, including Real types. So either the
compiler will generate calls to a runtime library to handle Real types, or
else I am completely wrong about the above! :) ]
Anyway, in checking Visual Basic's online help, I see that its data types
also include Single and Double, which are also IEEE, and are identical to
Delphi's Single and Double types. However, there is no mention of
"Microsoft Binary Format".
I then dropped down to DOS and ran QBasic, which is Microsoft's old
QuickBasic interpreter that is now included in DOS. If you check its
online help, you will see the following:
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
MKSMBF$ and MKDMBF$ convert IEEE-format numbers to Microsoft-Binary-format
numeric strings that can be stored in FIELD statement string variables.
CVSMBF and CVDMBF convert those strings back to IEEE-format numbers.
MKSMBF$(single-precision-expression!)
MKDMBF$(double-precision-expression#)
CVSMBF (4-byte-numeric-string)
CVDMBF (8-byte-numeric-string)
Function Returns
======== ============================================================
MKSMBF$ A 4-byte string containing a Microsoft-Binary-format number
MKDMBF$ An 8-byte string containing a Microsoft-Binary-format number
CVSMBF A single-precision number in IEEE format
CVDMBF A double-precision number in IEEE format
These functions are useful for maintaining data files created with
older versions of Basic.
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
So, to sum up, if you want to access your "MetaStock" files, I think you
have 3 choices.
1. Write your program in QBasic/DOS
2. Find substitutes (hopefully compatible with Delphi) for the conversion
functions mentioned above.
3. Write these functions yourself. You will have to find documentation
for the bitwise layout of the old "Microsoft Binary Format" Single and
Double types, perhaps in MS's old Basic manuals.
Array of the Image Object
Question
How do I implement an array of objects...lets say I want 10 image
controls on my form, but don't want a different name for each one...in
VB there was an index property...how do I do it in Delphi?
Answer
You cannot do this visually & straight off, but if you don't mind some coding
this is pretty simple:
type
TForm1 = class(TForm)
...
public
images: array [1..10] of TImage;
...
end;
procedure TForm1.FormCreate(...);
var i: integer;
begin
...
for i := 1 to 10 do begin
images[i] := TImage.Create(self);
with images[i] do begin
parent := self;
tag := i; { makes it easier to detect e.g which
... set other properties as required, e.g:
OnClick := MyClickEventHndlr;
end;
end;
...
end;
To make sure you get all "uses" correct you may drop one such dynamic component
on your form, and then either delete it or set visible to false.
A more elaborate way is of course to design your own component to do the same.
Pass a record problem
Question
I have a Delphi application that has about two dozen simple type
declarations similar to the following:
type RecordA = record
this : Integer;
that : String;
the_other : Integer;
end;
Each record type can have an essentially random number of fields in it.
I'm looking for a way to write a *generic* function that can be passed
a specific record variable and a 'field number' which will be able
to determine the name of the field, it's type and it's value.
A rough example:
recA : RecordA;
.....
recA.that := 'Steve';
MyFunc(recA, 2); { Give me details on the 2nd field in recA };
function MyFunc(rec: ????; field : Integer);
begin
{ Do some magic }
Label1.Caption := recordname; { 'recA' }
Label2.Caption := fieldname; { 'that' }
Label3.Caption := _type; { 'String' }
Label4.Caption := value; { 'Steve' }
end;
Answer
A:
An idea, may not be what you're looking for, but here it is:
Define a base class, call it allrecs, eg.
tAllrecs = class
function getVal (field: integer): string; virtual;
end;
Then derive a class for each record type, eg.
recA = class (tAllrecs)
this : Integer;
that : String;
the_other : Integer;
function getVal (field: integer): string; virtual;
end;
then for each class's function define what it returns:
function recA.getVal (field: integer); string;
begin
case field of
1: getVal := intToStr (this);
2: getVal := that;
3: getVal := intToStr (the_other);
end;
end;
Then you can define
function myFunc (rec: tAllrecs; field: integer);
begin
label2.caption := allrecs.getVal(field);
end;
and you can then call myFunc with any class derived from tAllrecs, eg.
myFunc (recA, 2);
myFunc (recB, 29);
(Rather than a function, getVal will probably have to be a procedure with 3
var parameters so you can return name and type as well as value.)
BTW, please don't use "this" as a variable, it confuses us old C++
programmers :-)
("this" in C++ means the same as "self" in Delphi.)
Also, I did try this out so I have a small working project that does it.
[Sid Gudes, cougar@roadrunner.com]
A:
If you're willing to pass the whole record at a time, then set your
function/procedure to expect an 'array of const' (keeps typechecking
safe, warm & fuzzy). This is identical to 'array of TVarRec'.
See Delphi online help for the system constants defined for TVarRec.
Function pointer
Question
Is The a similar concept to pointers to functions in delphi?
I have a toolbar with speed buttons which can be used by a number of diffrerent
forms in an app. Depending on which form is currently active I would like to
execute a different funtion if a button is pressed. I would hate to do this
using a huge case statement, Ideally what I wanted to do is register a function
with the toolbar button when a form gets focus and then simply call that
function when the speed button is pressed.
Answer
A:
This is what I came up with when building a simple states machine:
This is a very simple example of using function pointers under
Borland Delphi to control program flow. Just create a simple form with
one button and add the code from Unit1 to the unit created. Add Unit2
to the project and compile. Give me a yell if you have any problems.
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
CurrProc : LongInt;
MyVal : LongInt;
implementation
uses Unit2;
{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject);
var
NewProc : LongInt;
MyString : string;
begin
CurrProc := 2; { beginning point in proc table }
MyVal := 0; { dummy variable }
NewProc := 0; { return value for next index in proc
table }
while CurrProc < 6 do
begin
{ execute the current index in the proc table and get the next proc
}
NewProc := ProcTable[CurrProc](MyVal);
{ this is just to track the values of NewProc and CurrProc }
FmtStr(MyString, 'NewProc [%d] CurrProc [%d]', [NewProc,
CurrProc]);
MessageDlg(MyString, mtInformation, [mbOK], 0);
{ set the current proc to the returned proc }
CurrProc := NewProc;
end;
end;
end.
{ This is a simple example of defining an array of function pointers }
interface
type
{ define Procs as a function }
Procs = function(var ProcNum : LongInt): LongInt;
var
{ declare the array of function pointers }
ProcTable : Array [1..5] of Procs;
{ function interface definitions }
function Proc1(var MyVal : LongInt) : LongInt; far;
function Proc2(var MyVal : LongInt) : LongInt; far;
function Proc3(var MyVal : LongInt) : LongInt; far;
function Proc4(var MyVal : LongInt) : LongInt; far;
function Proc5(var MyVal : LongInt) : LongInt; far;
implementation
uses Dialogs;
function Proc1(var MyVal : LongInt) : LongInt;
begin
MessageDlg('Proc 1', mtInformation, [mbOK], 0);
Proc1 := 6;
end;
function Proc2(var MyVal : LongInt) : LongInt;
begin
MessageDlg('Proc 2', mtInformation, [mbOK], 0);
Proc2 := 3;
end;
function Proc3(var MyVal : LongInt) : LongInt;
begin
MessageDlg('Proc 3', mtInformation, [mbOK], 0);
Proc3 := 4;
end;
function Proc4(var MyVal : LongInt) : LongInt;
begin
MessageDlg('Proc 4', mtInformation, [mbOK], 0);
Proc4 := 5;
end;
function Proc5(var MyVal : LongInt) : LongInt;
begin
MessageDlg('Proc 5', mtInformation, [mbOK], 0);
Proc5 := 1;
end;
initialization
{ initialize the contents of the array of function pointers }
@ProcTable[1] := @Proc1;
@ProcTable[2] := @Proc2;
@ProcTable[3] := @Proc3;
@ProcTable[4] := @Proc4;
@ProcTable[5] := @Proc5;
end.
A:
I think I would do something like this:
Declare in each form procedures that handle the buttonpresses, like
procedure CutButtonPressed(Sender:TObject) of Object;
Then I would simply assign the buttons' OnClick events to these
procedures in the forms OnActivate event. This would be the oop way to
do it, but if you don't like it, I think Delphi still has function
pointers.
A:
Define a base class form with an abstract function declaration for each
of the functions you want to call from your toolbar. Then derive each
of your forms from that base class form, and provide definitions for those
functions.
Eg: (There might be a couple of syntax errors here - I haven't compiled it)
type
TBaseForm = class(TForm)
public
procedure Method1; virtual; abstract;
end;
type
TDerivedForm1= class(TBaseForm)
public
procedure Method1; override;
end;
TDerivedForm2= class(TBaseForm)
public
procedure Method1; override;
end;
procedure TDerivedForm1.Method1;
begin
....
end;
procedure TDerivedForm2.Method1;
begin
....
end;
{To call the function from your toolbar, get
the currently active form and call Method1}
procedure OnButtonClick;
var
AForm: TBaseForm;
begin
AForm := ActiveForm as TBaseForm;
AForm.Method1;
end
Converting Real to a fraction of two integers
Question
I am using a rather slow function (see below) to convert a real
value to a fraction of two integers. Does anybody know of a better and
faster method as the one here presented?
Answer
A:
I have written a program that does just that. It's a dos program. You
call it with the decimal number passed as a parameter. It will print
3 columns, the first will be the continued fractions and the next two
will be the numerator and denominator. You will have to convert it
yourself to a function that you can call from your program, but that
should be to difficult.
To see how it works, I suggest that you try it as follows from the
DOS command line:
CONTFRAC 3.141592654
program contfrac; { continued fractions }
{$N+}
const
order = 20;
var
y,
lasterr,
error,
x : extended;
a : array[0..order] of longint;
i,j,
n : integer;
op,
p,
q : longint;
begin
lasterr := 1e30;
val(paramstr(1), y, n);
if n <> 0 then
halt;
x := y;
a[0] := trunc(x);
writeln;
writeln(a[0]:20, a[0]:14, 1:14);
{ this is where the smarts are }
for i := 1 to order do begin
x := 1.0 / frac(x);
a[i] := trunc(x);
p := 1;
q := a[i];
for j := pred(i) downto 0 do begin
op := p;
p := q;
q := a[j] * q + op;
end;
error := abs(y - int(q) / int(p));
if abs(error) >= abs(lasterr) then
halt;
writeln(a[i]:20, q:14, p:14, error:10);
if error < 1e-18 then
halt;
lasterr := error;
end;
end.
A:
Here's an idea that I use that seems fast enough. Here's the scheme:
We'll use the number 23.56.
Take your real number and do integer division by 1.
23.56 div 1 = 23
Now subtract the result from the number you started with.
23.56 - 23 = .56
To convert to int value just multiply by 100 if that is needed and recast.
valA := (val div 100);
valB := (valA - val); or valB := (valA - val) * 100;
val = 23.56
ValA = 23
ValB = .56 or 56
Resizing (Dynamic) Arrays
Question
Coming from a background of higher level programming languages. I have
not had a lot of experience with array's in C++ or Pascal. I was
wondering if someone could clue me in on dynamic array's in delphi.
Answer
A:
There are several techniques for doing this. The techniques depend on
whether you have an array of strings or an array of numbers (integer, real,
etc.).
1) If you just want a dynamic one-dimensional array of strings, I suggest
you look at the tStringList component, it takes care of all the management
overhead and is easy to use.
2) If you want a dynamic multi-dimensional array of strings, you can use
tStringList also (as long as the total number of elements is less than the
maximum for tStringList, I believe 16,000). To do this, you write a
linearizing map function as follows:
Assume you have a three-dimensional array of strings, and the current
dimensions are [12,80,7], and you want to find element [n,m,x]. Then you
can resolve this to an element in a one-dimensional array by using
((n-1)*80*7 + (m-1)*80 + x). You would then use this as an index into a
tStringList. To dynamically alter one of the array bounds, use the move
method of tStringList to shuffle things around. (This involves some
embedded loops but should be pretty quick to execute because tStringList
doesn't move the strings, only pointers to the strings.)
3) If you want a dynamic one-dimensional array of numbers, here's a general
technique, there are many other details. Declare a pointer to an array type
that has the maximum number of elements for that type (remembering that
Delphi-16 only allows a type to occupy up to 64K), eg.
type
bigArray: array[1..32000] of integer; {or ^double, etc.}
pMyArray: ^bigArray;
then allocate the array using
getMem (pMyArray, sizeof(integer) * n);
where n is the number of elements. Then you can refer to an array element
using, eg.
pMyArray^[51]
Don't forget to free the array after you're done with it using FreeMem.
To resize the array, define a new pointer, reallocate, and swap, eg.
pTemp: ^bigArray;
getMem (pTemp, sizeof(integer) * newnumelements);
memcopy (pTemp, pMyArray, sizeof(integer)*n);
{n is number of elements in pMyArray}
freeMem (pMyArray, sizeof(integer)*n);
pMyArray := pTemp;
4) To use a multi-dimensional array of numbers, combine the technique in (3)
above with the mapping function in (2) above.
5) If you need more than 64K in your array, you'll need to develop a
cascading list of pointers to chunks of memory, which is beyond what I can
explain here.
A:
I would encapsualte in an object. I use what I call my "Basic String
Object" (BSO), which does dynamic allocation and deallocation for
strings of any size. Internally it is a PChar pointing to allocated
memory. Externally I have two properties: AsString and AsPChar. I have
various properties and methods allowing various methods of accessing
and manipulating the string.
I wrote my own malloc() calloc() and realloc() using a static private
TString object to track the allocated pieces. This has worked wonderfully
for any time I need to grab a chunk of memory.
With the two I can allocate memory as needed (in chunks so as not to
waste too much CPU time), and is disposed of (when a certain amount of
slack exists -- again, so as to not waste too much CPU time).
Another idea that I like has already been presented (the open-ended
array). If you need bounds checking and/or dynamic resizing, you may
be forced to use a method similar to what I've done with the string object
above, and use a default array property to allow for easy access. This
allows you to use indices and types of any kind.
TMyDynamicObject =
...
PROPERTY Array[ idx :LONGINT ]:TMyType READ GetArray WRITE PutArray DEFAULT;
...
VAR Mine :TMyDynamicObject;
...
Mine := TMyDynamicObject.Create;
FOR i := 10 TO 20 DO Mine[i] := {whatever}
{MONSTER MEMORY WASTER - unless you get really crazy and use hash tables }
Mine[-100000] := {whatever}
Mine[+100000] := {whatever}
If you have a sparsely-populated array, using a hash table might
be profitable. I convert index values to strings and let TStrings do
the work when I'm really lazy and don't particularly care about the
overhead to build the conversion to strings.
A:
You can use TList (or TStringList.Objects) to store virtually whatever you
want! TList.Items stores pointers to objects or records, but it doesn't do
anything with that pointer, so if you want you can typecast that to a longint,
and not bother with the objects or records at all! Here is an example of
storing a list of integers in a TList:
var
aList: TList;
I : Integer;
L : Longint;
begin
aList := TList.Create;
L := 93823;
aList.Add(Pointer(L));
aList.Add(Pointer(83293));
for I := 1 to aList.Count do
L := L + Longint(aList.Items[I-1]);
aList.Free;
end;
You can have up to 16380 elements in a TList or TStringList. Now here's an
example of how to use a TList to store a record (or object):
type
PMyRec = TMyRec;
TMyRec = record
Name: string[40];
Addr : string[25];
Comments: string;
salary: Double;
end;
var
aList: TList;
aRecPtr: PMyRec;
I : Integer;
begin
aList := TList.Create;
New(aRecPtr);
with aRecPtr^ do
begin
Name := 'Danno';
Addr := 'unknown';
Comments := 'What a guy!';
Salary := 999000.00;
end;
aList.Add(aRecPtr);
aList.Add(... );
...
for I := 1 to aList.Count do
begin
aRecPtr := PMyRec(aList.Items[I-1]);
{do something with the record}
end;
{now dispose of all records, and the list object itself}
for I := 1 to aList.Count do
Dispose(PMyRec(aList.Items[I-1]));
aList.Free;
end;
Use Free with records
Question
If I'm maintaining a TList full of pointers to records (NOT pointers to
objects), do I need to free the records it holds before freeing the TList
itself? For example:
type
PMyRecord: ^TMyRecord;
TMyRecord = record
MyString: string;
end;
var
MyRecord: PMyRecord;
List: TList;
begin
List := TList.Create;
New(MyRecord);
MyRecord^.MyString := 'Hi There';
List.Add(MyRecord);
MyList.Items[0].Free; { <-- Do I Need This??? }
List.Free;
end;
Answer
A:
You need to typecast the call to free with the right type, as follows:
var
i : integer;
begin
...
for i := 0 to MyList.Count - 1 do
dispose(PMyRecord(MyList[i]));
MyList.Free;
end;
or
begin
for i := 0 to MyList.Count - 1 do
dispose(PMyRecord(MyList.items[i]));
MyList.Free;
end;
Items is the default property, so you don't need to specify it, altough it's
ok if you do.
A:
Don't think of it as a function so much as a reserved word. In the form:
var
p : ^mystruct;
begin
new(p);
...
dispose(p);
end;
the new() and dispose() operate exactly like the getmem() and freemem()
procedures except that the compiler supplies the number of bytes as the size
of the structure pointed to by the pointer variable. The pointer must be a
typed pointer for this reason, so the following isn't valid:
var
p : pointer;
begin
new(p);
end;
because there is no set size for the memory the pointer will point to. On
the other hand, if you use getmem() and freemem(), you can allocate bytes to
an untyped pointer, as in:
var
p : pointer;
begin
getmem( p, 32767 );
...
freemem( p, 32767 );
end;
Create large arrays
Question
To be specific, I want to a 1 MEG array of bytes:
BigArray : Array[0..999999] of Byte;
How can I do this in Delphi?
Answer
A:
In Delphi 16-bit, you can't do it directly. In the new 32-bit, you _should_
be able to, but since it won't ship for a couple months I don't know yet.
(There are some beta testers out there who know, can they talk yet?)
In 16-bit Delphi, you need to work in chunks of 32K or 64K and map. You can
do something like the following:
type
chunk: array[0..32767] of byte;
pchunk: ^chunk;
var
BigArray: array[0..31] of pChunk;
To create the array:
for i := 0 to high(bigArray) do
new (bigArray[i]);
To access the n'th byte within the array (n should be a longint):
bigArray[n shr 15]^[n and $7FFF] := y;
x := bigArray[n shr 15]^[n and $7fff];
This will even do range checking if you have range checking set in your
options!
n must be in the range [0..32*32*1024] = [0..1024*1024] = [0..1048576].
To free the array when you're all done:
for i := 0 to high(bigArray) do
dispose (bigArray[i]);
Pointer to a function
Question
I passed a pointer pointing to a procedure to my DLL. How do I call
the procedure if all I have is the pointer. I did use makeprocinstance to
get the pointer.
Answer
A:
This is what I normally use to call some functions from a DLL:
1. Declare a type:
type
TYourDLLFunc = function(Parm1: TParm1; Parm2: TParm2): TParm3;
2. Declare a variable of that type:
var
YourDllFunc: TYourDLLFunc;
3. Get the DLL handle:
DLLHandle := LoadLibrary('YourDLL.DLL');
4. Get the address of the function:
@YourDLLFunc := GetProcAddress(DLLHandle, 'YourDLLFuncName');
5. Use the YourDLLFunc variable as you would normally use the function, e.g.:
Parm3 := YourDLLFunc(Parm1, Parm2);
Pass a function as a parameter
Question
Does anyone know how can I typecast a function's address into a
LongInt? Or is there a way to pass a function as a parameter?
Here is what I'm trying to do:
function DllFunction(p: Pointer): Integer;
far; external 'mydll';
function foo: integer;
begin
result := 1;
end;
procedure test;
var
l: LongInt;
begin
l := Addr(foo); { Compile Error!!! I tried @foo and }
{ LongInt(foo), and they won't work neither. }
{ This is what I need. }
DllFunction(foo); { Compile Error!!! It can't take a function}
{ as the parameter. }
end;
Answer
A:
Sounds like what you need is a procedural type. Assuming that
DllFunction() wants functions that look like what you've described
above, something like the following should work:
type
TMyFuncType = function : integer ;
var
MyFunc : TMyFuncType ;
function foo: integer;
begin
result := 1;
end;
begin
MyFunc := foo ;
DllFunction( longint( MyFunc )) ;
You may also be able to get away with
DllFunction( longint( @foo )) ;
though I'm not sure about all of the memory issues that may be involved
with a .DLL calling a routine in another code segment like this, you may
need to declare foo as far for this to work, or export it from the unit
or something.
Also, depending on how DllFunction() is written, you might be able
to declare it thusly, so that it does the typecast implicitly:
function DllFunction( p: TMyFuncType ): Integer;
far; external 'mydll';
so you wouldn't need the dummy MyFunc variable or the @ operator.
A:
You can pass functions as parameters in Delphi/Pascal. However you have to
set up a type for the compiler to use. Try the following (I actually
compiled and tested this):
unit Unit1;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
type
IntFunc = function: integer;
function DllFunction(iFunc: IntFunc): integer; far;
begin
DllFunction := iFunc; {Notice this is a function call}
end;
function iFoo: integer; far;
begin
iFoo := 1;
end;
procedure TestIFunc;
var
i: integer;
begin
i := DllFunction(iFoo);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
TestIFunc;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
Close;
end;
end.
A:
Two things you can do. First, if you want to use longint to pass it, you
can use
i := longint(@foo).
The other thing you can do is bypass all this longint stuff and call the dll
function with
DLLfunction (@foo);
Note that if you're planning to call foo from within the DLL, you're
probably going to need a thunk to resolve DS; look at MakeProcInstance for
more info.
Array of TPoints
Question
I need to set up an array of TPoints. Let's say I have 5 points
and I know their X and Y values. How do I fill and array with those values
for Tpoints?
Answer
A:
Const
ptarr : Array[0..4] Of TPoint =
((x:0; y:4),
.
.
(x:4; y:4));
Function return type
Question
Is there a way, using Object Pascal, to write a function which
is able to return more than one data type, i.e., returning a
byte or double or record ...
Actually I need some kind of polymorphic function...
Answer
A:
You can do that in C++. You can do it in ObjectPascal too, here's
the code snippet :
// function Chameleon returns different types by raising exceptions
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
type
MyBoolean = class
public
Value : boolean;
end;
MyInteger = class
public
Value : integer;
end;
MyClass = class
public
Value : TStrings;
end;
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
procedure MyProc;
function Chameleon : boolean;
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
function TForm1.Chameleon : boolean;
var
b : MyBoolean;
i : MyInteger;
c : MyClass;
r : integer;
begin
r := Random(3);
case r of
0 : begin
b := MyBoolean.Create;
raise b;
end;
1 : begin
i := MyInteger.Create;
raise i;
end;
2 : begin
c := MyClass.Create;
raise c;
end;
end;
end;
procedure TForm1.MyProc;
begin
try
Chameleon;
except
on MyBoolean do ShowMessage('Function returned class MyBoolean');
on MyInteger do ShowMessage('Function returned class MyInteger');
on MyClass do ShowMessage('Function returned class MyClass');
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
Chameleon;
end;
end.
A:
Take a look at the Variant datatype in D2: this kind of code
function AnyType(const TypeParm: integer): Variant;
begin
case TypeParm of
1: Result := 1;
2: Result := 2.0;
3: Result := 'Three';
4: Result := StrToDate('4/4/1944');
end;
end;
is crappy programming - but quite legal!
A:
The following code snipped declares 3 procedures all same ingoing
parameters and all different output (as long as the output is physically
the same here it is 4 bytes). I don't think it is possible to fool delphi
to return a string that way. This would crash the heap manager.
Just as you need, call the appropriate function. Every call goes to
MyFuncRetAnything and P1 determines the return type. If you want you can
write another layer which makes the typecasting for you.
3 calls, 1 code.
I know this is not really what you need, but it is another approach.
(you can return strings as PChar which is also 4 bytes). But you must use
some memory allocated by the calling proc (maybe pass the result as P2?).
{my form has 3 labels and 1 button and this code}
var
MyFuncRetInt : Function (P1, P2 : Integer) : Integer;
MyFuncRetBool : Function (P1, P2 : Integer) : LongBool;
MyFuncRetPointer : Function (P1, P2 : Integer) : Pointer;
function MyFuncRetAnything (P1, P2 : Integer) : Integer;
var
RetPointer : Pointer;
RetBool : LongBool;
RetInteger : Integer;
begin
RetPointer := nil;
RetBool := False;
RetInteger := 4711;
case P1 of
1 : Result := Integer (RetPointer);
2 : Result := Integer (RetBool);
3 : Result := RetInteger;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
if MyFuncRetBool (2, 1900) then
Label1.Caption := 'True'
else
Label1.Caption := 'False';
Label2.Caption := IntToStr (MyFuncRetInt (3, 1900));
Label3.Caption := IntToHex (Integer (MyFuncRetPointer (1, 1900)), 16);
end;
initialization
MyFuncRetInt := @MyFuncRetAnything;
MyFuncRetBool := @MyFuncRetAnything;
MyFuncRetPointer := @MyFuncRetAnything;
end.
|