I am trying the compile the HtmlViewer component for Delphi 7 (https://github.com/BerndGabriel/HtmlViewer). Opened the project Frameviewer7.dpk under package subdir
However I am getting the following compilation errors:
HtmlBuffer.pas(1611): Array Type required.
Which corresponds to the following code:
if FStart.BytePtr[0] = 0
And FStart is defined as FStart: TBuffPointer;
TBuffPointer = record
case Integer of
0: (BytePtr: PByte;);
1: (WordPtr: PWord;);
2: (AnsiChr: PAnsiChar;);
3: (WideChr: PWideChar;);
end;
Not sure what is wrong here. My compiler is Delphi7
FStart.BytePtr[0] indicates that FStart.BytePtr is an array, and the value of this expression is the first (0th) element in this array.
However, FStart.BytePtr is actually a pointer. But often you can use arrays and pointers to achieve the same task -- either you use an array of TSomeType, or you use a pointer to the first element in an in-memory list of TSomeType items.
I assume this is what is going on here. Hence, you want to get the first item of a list of byte values, the first occurring at address FStart.BytePtr. To obtain the byte at this location, you dereference the pointer using ^: FStart.BytePtr^.
The code you have found tries to access data using array notation on a pointer. This syntactic sugar might work in some newer version or Delphi, or using some compiler option. (I don't recall.)
This syntax uses a feature of later Delphi compilers that allows you to use indexed references for offsets from typed pointers. In some versions of Delphi this requires the POINTERMATH compiler option or directive to be specified.
Unfortunately this is not supported in Delphi 7.
The typical way to work around this is to use an array type and declare a pointer to this array type. The actual bounds of the array type are not important (in the sense that you will be using the pointer type so you will not be creating actual large array structures, only treating pointers as if they were references to such structures).
The only consideration is that the upper limit needs to be higher than or equal to the highest index you will require to specify in order to keep the compiler happy that any literal indexes you specify may be valid.
i.e. if you specified an array of only 100 items then any code that attempted to reference a 101st item would fail bounds checking either at compile time or runtime (if runtime checks are enabled).
So for a simple example we'll use an array of 65535 items:
const
MAX_BYTEARRAYDIM = 65535;
type
TByteArray = array[0..MAX_BYTEARRAYDIM] of Byte;
PByteArray = ^TByteArray;
procedure SomeExampleMethod;
var
pb: PByteArray;
begin
// ..
pb[12] := 25; // The array type is 0 based so this sets the value of byte offset 12 bytes from the address in pb
end;
This has the advantage (should it be a concern in your code) of being portable to all versions of Delphi.
Application in Your Case
In your specific case you could redefined the BytePtr type in this way. Not being familiar with the HTMLViewer code I cannot say whether this may be practical.
An alternative would be to declare the necessary array and pointer types and to to typecast as and where required, e.g.:
if PByteArray(FStart.BytePtr)[0] = 0
Of course, the same technique can be applied to other pointer types as required.
Related
Recently I've ported a huge chunk of code form C++ to Delphi (over 1mb of C code). The code is full with pointers. Despite project compiling and working well for 99% of the time, I'm seeing some weird results from time to time, which leads me to think there might be bugs with pointer handling - indeed, I've already found a couple. The problem is that they are really hard to track down without any clues/hints from compiler.
Maybe you have some tips, for such cases:
var
a: PSingle;
GetMem(a, SizeOf(Single));
a^ := 1.1;
// Call func declared as - someFunc(aIn: PSingle);
someFunc(#a); // <<-- Bug here. I mistakely added # while porting.
// someFunc needs PSingle, but gets a PPSingle instead
In the example, there's erroneous # inserted. Program does not crash, just deals with that erroneous data and keeps running. I need a way of finding such cases where "Pointer to a Pointer to a Value" gets passed instead of "Pointer to a Value".
How do you track down pointer bugs like these?
The "typed # operator" compiler option is designed to detect exactly that kind of mistake. When it's disabled, the expression #a has type Pointer, which is compatible with all pointer types. Enable the option, and the same expression has type ^PSingle, which is not compatible with the expected PSingle.
I recommend turning on that option in all projects; it baffles me that Delphi doesn't make that option the default all the time.
You can modify the state of that option within your code with the $T+ and $T- compiler directives.
Another thing you can do is convert your code to use more idiomatic Delphi. For example, pass the argument by reference instead of by pointer value. Change the definition of the argument:
procedure someFunc(var arg: Single);
With that declaration, passing #a will be an error that the compiler will find and forbid. You would instead pass a^, or you could even get rid of the pointer entirely and just declare a as a plain Single rather than PSingle.
Just because the original code was written in C and C-style C++, it doesn't mean your Delphi code has to look like it.
The problem is here:
someFunc(#a);
You are adding a pointer to a pointer not the pointer it self.
Let me make you an example:
uses
Math;
function Somefunc(value: pSingle): Single;
begin
Result := (value^ + 1.1)
end;
procedure TForm23.FormCreate(Sender: TObject);
var
a: pSingle;
b: Single;
begin
GetMem(a, SizeOf(Single));
a^ := 1.1;
//Example 1:
b := Somefunc(a);
Caption := BoolToStr(CompareValue(2.2, b, 0.00001) = 0., True); //Caption: True
//Example 2:
b := Somefunc(#a); //NOTE #a
Caption := BoolToStr(CompareValue(1.1, b, 0.00001) = 0., True); //Caption: True
FreeMem(a);
end;
As you see in the first example the actual value are changed, while in example 2 the value og a remains unchanged because you parse the pointer to the pointer and therefor it is the pointer you chanhe and not the value.
I have discovered what I think is an odd oversight (probably intentional) on the part of the Extended RTTI feature in Delphi.
I would like to dump all the fields in an record type that has about 1500 different fields in it. Yes, seriously.
Some of them are of type real48 and some are shortstring, for those two, it appears that FieldType is nil for these types at runtime:
function TRttiField.GetValue(Instance: Pointer): TValue;
var
ft: TRttiType;
begin
ft := FieldType;
if ft = nil then
raise InsufficientRtti; // This fires!
TValue.Make(PByte(Instance) + Offset, ft.Handle, Result);
end;
If I was willing to assume that all nil-fieldtype fields are in fact real48's, I could simply use the offset and (if the field width is 6) grab a real48 value.
However the second complication is that all shortstring (ie string[30]) types are similarly afflicted.
Has anybody got these two Ancient Pascal Types to work with modern Extended RTTI?
Right now I'm using a best-guess approach, and where that fails I am hardcoding rules by name of the field, but if there was some technique I could use that would get me there without having to write a lot of code to extract information from all these old pascal file-of-records that I am modernizing, I would appreciate a better idea.
Unfortunately Real48 does not have any type info.
You can see that when you try compile this:
program Project1;
begin
TypeInfo(Real48);
end.
The same goes for the string[n] syntax. But there you could probably fix it by defining your own string types like:
type
string30 = string[30];
That alone would not include the rtti for the record field so you need to hack/fix the rtti as I showed here: https://stackoverflow.com/a/12687747/587106
In Pascal there are two kinds of type declarations:
type aliases: type NewName = OldType
type creation: type NewType = type OldType
The former is just creating convenient shorthand, like typedef in C. The aliases are compatible one to another and to their original type. The created types are intentionally incompatible and cannot be mixed without explicit and unsafe by definition typecast.
var
nn: NewName; nt: NewType; ot: OldType;
...
nn := ot; // should work
nt := ot; // should break with type safety violation error.
nt := NewType(ot); // Disabling type safety. Should work even if
// it has no sense semantically and types really ARE incompatible.
Those are Pascal basics as i understand them.
Now let's look at one certain type and two its aliases:
System.Types.TStringDynArray = array of string;
System.TArray<T> = array of T;
in particular that means TArray<string> = array of string; by definition.
Now let's take function returning the former type alias and feed its result to the function expecting the latter one:
uses Classes, IOUtils;
TStringList.Create.AddStrings(
TDirectory.GetFiles('c:\', '*.dll') );
TStringList.Create.AddStrings(
TArray<string>( // this is required by compiler - but why ???
TDirectory.GetFiles('c:\', '*.dll') ) );
1st snippet would not compile due to types violation.
2nd one happily compiles and works, but is fragile towards future type changes and is redundant.
QC tells that compiler is right and the RTL design is wrong.
http://qc.embarcadero.com/wc/qcmain.aspx?d=106246
WHY compiler is right here ?
Why those aliases are incompatible ?
Even the very manner RTL was designed suggests that they were deemed compatible!
PS. David suggested even simplier example, without using TArray<T>
type T1 = array of string; T2 = array of string;
procedure TForm1.FormCreate(Sender: TObject);
function Generator: T1;
begin Result := T1.Create('xxx', 'yyy', 'zzz'); end;
procedure Consumer (const data: T2);
begin
with TStringList.Create do
try
AddStrings(data);
Self.Caption := CommaText;
finally
Free;
end;
end;
begin
Consumer(Generator);
end;
Same gotcha without explanation...
PPS. There are a number of doc refs now. I want to stress one thing: while this restriction might be indirectly inherited from Pascal Report of 1949, today is 2012 and Delphi used very differently from school labs of half-century ago.
I named few BAD effects of keeping this restrictions, and yet did not saw any good one.
Ironic thing, that this restricion may be lifted without breaking rules of Pascal: in Pascal there is no such non-strict beast as Open Arrays and Dynamic Arrays. So let those original fixed arrays be restricted as they wish, but Open Arrays and Dynamic Arrays are not Pascal citizens and are not obliged to be limited by its codebook!
Please, communicate Emba in QC or maybe even here, but if u just pass by without expressing your opinion - nothing would change!
The key to understanding this issue is the Type Compatibility and Identity topic in the language guide. I suggest you have a good read of that topic.
It is also helpful to simplify the example. The inclusion of generics in the example serves mainly to complicate and confuse matters.
program TypeCompatibilityAndIdentity;
{$APPTYPE CONSOLE}
type
TInteger1 = Integer;
TInteger2 = Integer;
TArray1 = array of Integer;
TArray2 = array of Integer;
TArray3 = TArray1;
var
Integer1: TInteger1;
Integer2: TInteger2;
Array1: TArray1;
Array2: TArray2;
Array3: TArray3;
begin
Integer1 := Integer2; // no error here
Array1 := Array2; // E2010 Incompatible types: 'TArray1' and 'TArray2'
Array1 := Array3; // no error here
end.
From the documentation:
When one type identifier is declared using another type identifier, without qualification, they denote the same type.
This means that TInteger1 and TInteger2 are the same type, and are indeed the same type as Integer.
A little further on in the documentation is this:
Language constructions that function as type names denote a different type each time they occur.
The declarations of TArray1 and TArray2 fall into this category. And that means that these two identifiers denote different types.
Now we need to look at the section discussing compatibility. This gives a set of rules to follow to determine whether or not two types are compatible or assignment compatible. We can in fact shortcut that discussion by referring to another help topic: Structured Types, Array Types and Assignments which states clearly:
Arrays are assignment-compatible only if they are of the same type.
This makes it clear why the assignment Array1 := Array2 results in a compiler error.
Your code looked at passing parameters, but mine focused on assignment. The issues are the same because, as the Calling Procedures and Functions help topic explains:
When calling a routine, remember that:
expressions used to pass typed const and value parameters must be assignment-compatible with the corresponding formal parameters.
.......
Delphi is a strongly typed language. That means that identical (in this case I mean their definitions look exactly the same) types are not assignment compatible.
When you write array of <type> you are defining a type and not an alias. As David already said in his comment the two identical types like
type
T1 = array of string;
T2 = array of string;
are not assignment compatible.
Same goes for
type
TStringDynArray = array of string;
TArray<T> = array of string;
Often people forget about the incompatibility of identical types and my guess would be that they did when they introduced IOUtils for example. Theoretically the definition of TStringDynArray should have been changed to TStringDynArray = TArray<string> but I guess that could have raised other problems (not saying bugs with generics...).
I also had the same problem with Delphi, where I wanted to pass values from one identical array to another. Not only did I have "incompatibility" problems with two like array assignments, but I also could not use the "Copy()" procedure. To get around this problem, I found that I could use a pointer to an type array of array of string, instead.
For example:
type RecArry = array of array of string
end;
var TArryPtr : ^RecArry;
Now, I can pass the values from any fixed array to another identical array without any compatibility or function problems. For example:
TArryPtr := #RecArry.LstArray //This works!
TArryPtr := #LstArray //This also works!
With this created array assignment template, I can now work with all two dimensional arrays without any problems. However, it should be understood, that when accessing this type of string array pointer, an extra element is created, so that when we would expect this type of array 2D array below, for example:
Two_Dimensional_Fixed_Array[10][0]
We now get an extra element adjusted array as seen here:
New_Two_Dimensional_Fixed_Array[10][1]
This means that we have to use some slightly tricky code to access the pointer array, because all the populated elements in Two_Dimensional_Fixed_Array[10][0] have moved down, so that they are offset by 1, as in New_Two_Dimensional_Fixed_Array[10][1].
Therefore where we would normally find the value 'X' in Two_Dimensional_Fixed_Array[1][0], it will now be found here in TArryPtr[0][1].
Its a trade off we all have to live with!
Another important note to bear in mind is the definition of a pointer array when it is declared. When a pointer array is type declared, the Borland compiler will not allow the Pointer array to have the same element size as the array to which it is pointing too. For example, if an array is declared as:
Orig_Arry : array [1..50,1] of string;
The pointer array which should point to it would be declared in the following fashion:
Type Pntr_Arry : array [1..50,2] of string;
Did you notice the the extra element? I am guessing the the Borland compiler has to widen the array pointer to allow for the pointer address.
I need to browse all published properties of some classes.
Properties where the type is an enumeration with fixed values are not listed.
See example below:
TMyEnum = (meBlue, meRed, meGreen);
TMyEnumWithVals = (mevBlue=1, mevRed=2, mevGreen=3);
TMyClass =
...
published
property Color: TMyEnum read FColor write SetColor; // This one is found
property ColorVal: TMyEnumWithVals read FColorVal write SetColorVal; // This one is never found
end;
I need fixed values because these properties are stored in a database and I need to ensure that allocated values will always be the same, regardless of Delphi compiler choices in next versions, and prevent any misplaced insert of future values in the enumeration list.
I tried with both new Delphi 2010 RTTI (with .GetDeclaredProperties) and "old" RTTI (with GetPropInfos): all properties are found except the above type of property.
The same behaviour is seen on all classes. I also reproduced this in a sample project.
Tried with and without various RTTI directives without change.
Is this a bug, a known limitation?
Is there a workaround (except removing fixed values of the enumeration)?
Using Delphi2010 Ent+Update5
[Edit] The answer below provides the workaround: The first value of the enumeration must be set to 0 instead of 1, and values contiguous. Tested and working solution.
Thanks,
Barry Kelly says:
Discontiguous enumerations and enumerations which don't start at zero don't have typeinfo. For typeinfo to be implemented, it would need to be in a different format from the existing tkEnumeration, owing to backward compatibility issues.
I considered implementing a tkDiscontiguousEnumeration (or possibly better named member) for Delphi 2010, but the benefit seemed small considering their relative scarcity and the difficulties in enumeration - how do you encode the ranges efficiently? Some encodings are better for some scenarios, worse for others.
So it's not a bug, but a known limitation.
This is a limitation. Enumerated types with explicit ordinality can not have RTTI. Documented here.
Possible solution is to declare Reserved values within your enumerated type, eg:
TMyEnum = (meReserved1, meBlue, meRed, meGreen, meReserved2, meWhite); // and so on
...
Assert(MyEnum in [meBlue..meGreen, meWhite], 'sanity check failed')
Practical enums tends to occupy contiguous ranges, so you probably will not end up in having declaring gazillions of reserved values. In case you are interoperating with C style bitfields you will have to split value to distinct enums and sets.
To add some more background information, Enums with values, are treated as subrange types with predefined constants.
For example:
type
TMyEnumWithVals = (mevBlue=1, mevRed=2, mevGreen=3);
Is treated as:
type
TMyEnumWithVals = 1..3;
const
mevBlue : TMyEnumWithVals = 1;
mevRed : TMyEnumWithVals = 2;
mevGreen : TMyEnumWithVals = 3;
However, you can't combine these with integers without casting.
var
enum : TMyEnumWithVals;
ival : Integer;
begin
enum := mevBlue;
ival := Integer(mevRed);
To make matters worse, if you use:
type
TTypeId = (tidPrimary = 100, tidSecundary = 200, tidTertiary = 300);
TTypeArray = array [TTypeID] of string;
You get an array of 201 elements, not of 3. You can use the values in between using:
var
enum : TTypeId ;
ival : Integer;
begin
enum := TTypeId(150); // Is valid.
This information can be found in the Delphi language guide, with the enum type section.
Is there a limit in Delphi to the number of items you can have in an enumerated type? I need to create an enumerated type that might have several hundred items, and want to make sure there is not a limit at 255 items for example.
type
TMyType = (mtOne, mtTwo, mtThree, ..., mtThreeHundred);
I believe the theoretical limit is 2^32 items; but in practice, RTTI generation is normally the limit, as RTTI can't exceed 65535 bytes to store everything, including the names of the enumeration elements; the names are stored in UTF-8, so it's not too bad.
On the other hand, enumerations with explicit values for the elements don't have full RTTI, so you can evade the limit that way. Here's a program which creates a source file with 500,001 enumeration elements, which itself compiles:
var
i: Integer;
begin
Writeln('type');
Writeln(' E = (');
for i := 1 to 500000 do
Writeln(' x_', i, ' = ', i, ',');
Writeln('x_last);');
Writeln('begin');
Writeln('end.');
end.
The output of this program takes some time to compile with dcc32 because the Delphi compiler uses a hash table with only 32 buckets for checking for enumeration identifier duplicates, and a hash table with only 256 buckets for file-level scope, which (in the absence of {$SCOPEDENUMS ON}) is where enumeration identifiers are added.
I found a maximum of 65535 items in a german Delphi book.
After some digging in the documenation I found the respective section:
Enumerated Types
An enumerated type is stored as an
unsigned byte if the enumeration has
no more than 256 values and the type
was declared in the {$Z1} state (the
default). If an enumerated type has
more than 256 values, or if the type
was declared in the {$Z2} state, it is
stored as an unsigned word. If an
enumerated type is declared in the
{$Z4} state, it is stored as an
unsigned double-word.
So in fact there should be a possible maximum of 4294967295 ($FFFFFFFF) items.
Try it and see? It should just take a few minutes to write a loop that will build your type statement as long as you want. Output with a messagebox (which can be copied to the clipboard with ctrl+c), paste back into Delphi, and you're all set.
Yes enums in Delphi can have more than 256 items. You won't have problem with them, but if you are going to use set types, you should take note that sets can have 256 elements at most.