TurboPower B-Tree Filer and Delphi XE2 - Anyone done it? - delphi

I might be the last guy on the planet relying on B-Tree Filer but I made the jump from Delphi 2007 to XE2.
After getting over all the AnsiChar and PAnsiChar issues, the code now crashes with a Range Check with zero items on a page.
Is anyone successfully running B-Tree Filer under Delphi XE2? If so, how'd ya do it? :)
Update Range check error here:
procedure IsamUnPack(var Page : IsamPage; KeyL : Word);
var
I, K, S : Word;
P : Array [0..0] Of Byte absolute Page; {Real bounds [0..65535]}
begin
K := KeyL + 9;
S := Pred (Page.ItemsOnPage) * K + 6;
if KeyL <> MaxKeyLen then begin
for I := Page.ItemsOnPage downto 2 do begin
Move(P[S], Page.ItemArray[I], K); // Range Check error in Warren P's suggestion
S := S - K;
end;
end;
end;
While Page.ItemsOnPage should never be zero (the Range Check error is valid here) it may have been caused by data alignment issues. This code, added to BTDEFINE.INC seems to be doing the trick...
{$IFDEF VER230}
{$DEFINE UsingDelphi}
{$ENDIF}
{$IFDEF VER230} {Delphi XE2}
{$A-} {align data on byte boundaries}
{$B-} {short circuit boolean evaluation}
{$H+} {long string support}
{$I-} {suppress I/O checking}
{$J+} {writeable typed constants}
{$P-} {do not allow open string parameters}
{$Q-} {overflow checking off}
{$R-} {range checking off}
{$T-} {no type checked pointers with #}
{$V-} {no var string checking}
{$X+} {extended syntax on}
{$DEFINE Delphi2006}
{$DEFINE Delphi1Plus}
{$DEFINE Delphi2Plus}
{$DEFINE Delphi3Plus}
{$DEFINE Delphi4Plus}
{$DEFINE Delphi5Plus}
{$DEFINE Delphi6Plus}
{$DEFINE Delphi7Plus}
{$DEFINE Delphi2005Plus}
{$DEFINE Delphi2006Plus}
{$ENDIF}

I did a quick port, and I currently have it basically working, well enough for the included Delphi demo to work. My first try failed when I overlooked some string -> ansistring changes in the DEMO CODE, which caused the demo code function PadCH to malfunction. After I fixed that, the demo, and underlying library appears functional, at least for reading, but I did not test writing, modifying, and creating files yet. The above file in the demo was created in an earlier version, so at least it's binary-read compatible. I wouldn't be surprised if there were lots of bugs, data corruption issues, and so on, so please do not use this code in production, or if you do, you do so at your own risk.
My work is here: hosted at microsoft skydrive (4.3 megs, ZIP)
(filename tpbtreefiler_xe2_v2.zip)
Update Function IsamUnpack is in ISAMWORK.INC.
Update2 It appears that the OP has discovered now that adding some ifdef-version-constant support causes the {$R-} and some alignment flags to be switched on which are also required, for the library to work properly. May I suggest the following different way of declaring in BTDEFINE.INC, that gets around a classic Delphi "break every time we change Delphi compiler versions" by using a comparison that won't break on the next delphi release:
{$IF CompilerVersion > 20.0 }
{ Keep working from Delphi 2009 UP}
{$DEFINE UsingDelphi}
{$A-} {align data on byte boundaries}
{$B-} {short circuit boolean evaluation}
{$H+} {long string support}
{$I-} {suppress I/O checking}
{$J+} {writeable typed constants}
{$P-} {do not allow open string parameters}
{$Q-} {overflow checking off}
{$R-} {range checking off}
{$T-} {no type checked pointers with #}
{$V-} {no var string checking}
{$X+} {extended syntax on}
{$DEFINE Delphi2006}
{$DEFINE Delphi1Plus}
{$DEFINE Delphi2Plus}
{$DEFINE Delphi3Plus}
{$DEFINE Delphi4Plus}
{$DEFINE Delphi5Plus}
{$DEFINE Delphi6Plus}
{$DEFINE Delphi7Plus}
{$DEFINE Delphi2005Plus}
{$DEFINE Delphi2006Plus}
{$ENDIF}
Update 3 I suspect there are still porting issues in the code, that could cause data loss and data file corruption. Here's an example where the number of records (which should be a number in the range around 50 in my demo app) is being reported as a number > 1 million, which is clearly invalid.

Two more items to check
change "string" to "AnsiString"
If target build is 64 bits, your changes won't make it ready yet
Cheers

After I had it all working, I found that re-indexing from a XE2 app broke the B-TreeFiler (BTreeFiler) tables with a isam error #10122 ("The page size for the file block is greater then the MaxPageSize"). Here is the fix:
This one needs to be changed in Filer.pas for reindexing to work:
IsamInfoRec = packed Record
InfoRec : IsamSmallInfoRec;
DummyFill : AnsiChar; <<<<<<<< Here!
KeysUsed : LongInt;{Must start on an even offset for C-compatibility}
PageSizeUsed : Word; {!!.42}
End;
I hope Warren (above) updated his work with this additional fix. I have also posted this one on the TurboPower BTreeFiler SourceForge location.

Related

How to conditionally compile a newer Indy feature?

I've already found this answer on how to check the Indy version at run-time, and there are multiple different ways. However I'm looking how to use conditionals to check the Indy version at compile-time. There's a feature in newer versions of Indy, and I want my open-source project to use this feature if it's available. But I need to conditionally compile it.
I've found IdVers.inc, but this file only contains constants - no version conditionals.
More specifically, the TIdHTTP has a property HTTPOptions which has a new choice hoWantProtocolErrorContent. If this is available, I'd like to use it.
How can I conditionally use this option if it's available?
I think you can get the result you're wanting to achieve using the
{$if declared ...
construct. There is an example of its usage in SysInit.Pas in the rtl:
function GetTlsSize: Integer;
{$IF defined(POSIX) and defined(CPUX86) and (not defined(EXTERNALLINKER))}
asm
// Use assembler code not to include PIC base gain
MOV EAX, offset TlsLast
end;
{$ELSE}
begin
Result := NativeInt(#TlsLast);
{$IF DECLARED(TlsStart)}
Result := Result - NativeInt(#TlsStart);
{$ENDIF}
[...]
As well as the article I mentioned in a comment, $If Declared,
there is also this in the D2009 online help.
$if declared works with methods of classes, e.g.
procedure TMyClass.DoSomething;
begin
{$if declared(TMyClass.Added)} // Added being a procedure of TMyClass
Added;
{$endif}
end;

How to work with 0-based strings in a backwards compatible way since Delphi XE5?

I'm trying to convert my current Delphi 7 Win32 code to Delphi XE5 Android with minimal changes, so that my project can be cross-compiled to Win32 from a range of Delphi versions and Android from XE5.
Starting from XE5 there are breaking changes in language aimed at future. One of such changes is zero-based strings.
In older versions with 1-based strings the following code was correct:
function StripColor(aText: string): string;
begin
for I := 1 to Length(aText) do
but now this is obviously not right. Suggested solution is to use:
for I := Low(aText) to High(aText) do
This way XE5 Win32 handles 1-based strings and XE5 Android handles 0-based strings right. However there's a problem - previous Delphi versions (e.g. XE2) output an error on such code:
E2198 Low cannot be applied to a long string
E2198 High cannot be applied to a long string
I have quite a lot of string manipulation code. My question is - how to modify and keep above code to be compileable in Delphi 7 Win32 and Delphi XE5 Android?
P.S. I know I can still disable ZEROBASEDSTRINGS define in XE5, but that is undesired solution since in XE6 this define will probably be gone and all strings will be forced to be 0-based.
If you want to support versions that use one based strings then don't define ZEROBASEDSTRINGS. That's the purpose of that conditional.
There's no indication that I am aware of that the conditional will be removed any time soon. It was introduced in XE3 and has survived two subsequent releases. If Embarcadero remove it, none of their Win32 customers will not upgrade and they will go bust. Embarcadero have a track record of maintaining compatibility. You can still use TP objects and short strings. Expect this conditional to live as long as the desktop compiler does.
In fact, all the evidence points towards the mobile compilers retaining support for one based string indexing. All the utility string functions like Pos use one based indices, and will continue to do so. If Embarcadero really are going to remove support for one based string indexing, they'll be removing Pos too. I don't believe that is likely any time soon.
Taking your question at face value though it is trivial to write functions that return the low and high indices of a string. You just use an IFDEF on the compiler version.
function StrLow(const S: string): Integer; inline;
begin
Result := {$IFDEF XE3UP}low(S){$ELSE}1{$ENDIF}
end;
function StrHigh(const S: string): Integer; inline;
begin
Result := {$IFDEF XE3UP}high(S){$ELSE}Length(S){$ENDIF}
end;
Update
As Remy points out, the above code is no good. That's because ZEROBASEDSTRINGS is local and what counts is its state at the place where such functions would be used. In fact it's just not possible to implement these functions in a meaningful way.
So, I believe that for code that needs to be compiled using legacy compilers, as well as the mobile compilers, you have little choice but to disable. ZEROBASEDSTRINGS.
All of the RTL's pre-existing functions (Pos(), Copy(), etc) are still (and will remain) 1-based for backwards compatibility. 0-based functionality is exposed via the new TStringHelper record helper that was introduced in XE3, which older code will not be using so nothing breaks.
The only real gotchas you have to watch out for are things like hard-coded indexes, such as your loop example. Unfortunately, without access to Low/High(String) in older Delphi versions, the only way to write such code in a portable way is to use IFDEFs, eg:
{$IFDEF CONDITIONALEXPRESSIONS}
{$IF CompilerVersion >= 24}
{$DEFINE XE3_OR_ABOVE}
{$IFEND}
{$ENDIF}
function StripColor(aText: string): string;
begin
for I := {$IFDEF XE3_OR_ABOVE}Low(aText){$ELSE}1{$ENDIF} to {$IFDEF XE3_OR_ABOVE}High(AText){$ELSE}Length(aText){$ENDIF} do
DoSomething(aText, I);
end;
Or:
{$IFDEF CONDITIONALEXPRESSIONS}
{$IF CompilerVersion >= 24}
{$DEFINE XE3_OR_ABOVE}
{$IFEND}
{$ENDIF}
function StripColor(aText: string): string;
begin
for I := 1 to Length(aText) do
begin
DoSomething(aText, I{$IFDEF XE3_OR_ABOVE}-(1-Low(AText)){$ENDIF});
end;
end;
Conditional Expressions were introduced in Delphi 6, so if you don't need to support version earlier than Delphi 7, and don't need to support other compilers like FreePascal, then you can omit the {$IFDEF CONDITIONALEXPRESSIONS} check.
This is rather a sum up of the two answers:
As pointed out by Remy Lebeau, ZEROBASEDSTRINGS is a per-block conditional. That means that the following code will not work as expected:
const
s: string = 'test';
function StringLow(const aString: string): Integer; inline; // <-- inline does not help
begin
{$IF CompilerVersion >= 24}
Result := Low(aString); // Delphi XE3 and up can use Low(s)
{$ELSE}
Result := 1; // Delphi XE2 and below can't use Low(s), but don't have ZEROBASEDSTRINGS either
{$ENDIF}
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
{$ZEROBASEDSTRINGS OFF}
Memo1.Lines.Add(Low(s).ToString); // 1
Memo1.Lines.Add(StringLow(s).ToString); // 1
{$ZEROBASEDSTRINGS ON}
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
{$ZEROBASEDSTRINGS ON}
Memo1.Lines.Add(Low(s).ToString); // 0
Memo1.Lines.Add(StringLow(s).ToString); // 1 <-- Expected to be 0
{$ZEROBASEDSTRINGS OFF}
end;
There are 2 possible solutions:
A. Every time there's string items access or iteration place an IFDEF around it, which is indeed a lot of clutter for the code, but will work properly irregardless of ZEROBASEDSTRINGS setting around it:
for I := {$IFDEF XE3UP}Low(aText){$ELSE}1{$ENDIF} to {$IFDEF XE3UP}High(aText){$ELSE}Length(aText){$ENDIF} do
B. Since the ZEROBASEDSTRINGS conditional is per-block it never gets spoiled by 3rd party code and if you don't change it in your code you are fine (above StringLow will work fine as long as the caller code has the same ZEROBASEDSTRINGS setting). Note that if target is mobile, you should not apply ZEROBASEDSTRINGS OFF globally in your code since RTL functions (e.g. TStringHelper) will return 0-based results because mobile RTL is compiled with ZEROBASEDSTRINGS ON.
On a side note - One might suggest to write an overloaded versions of Low/High for older versions of Delphi, but then Low(other type) (where type is array of something) stops working. It looks like since Low/High are not usual functions then can not be overloaded that simply.
TL;DR - Use custom StringLow and don't change ZEROBASEDSTRINGS in your code.
How about defining this as an inc file? Put additional ifdefs depending on what Delphi versions you want to support. Since this code is only for versions before the ZBS to make it possible to use Low and High on strings it will not run into the problem with the ZEROBASEDSTRINGS define only being local.
You can include this code locally (as nested routines) then which reduces the risk of colliding with System.Low and System.High.
{$IF CompilerVersion < 24}
function Low(const s: string): Integer; inline;
begin
Result := 1;
end;
function High(const s: string): Integer; inline;
begin
Result := Length(s);
end;
{$IFEND}
As LU RD told above Low and High functions for string were only introduced in XE3. So how can you use functions in earlier Delphi verions, that are missed? Just the same way as always do - if the function is missed - go and write it!
You should only activate those compatibility additions for Delphi beyond XE3 version, using conditional compilation. One way is described in other answers, using >= comparison. Another usual way would be reusing jedi.inc definitions file.
Then for earlier Delphi versions you would add your own implementations of those, like
function Low(const S: AnsiString): integer; overload;
Pay attention to the overload specifier - it is what would make the trick possible, don't forget it!
You would have to write 4 functions for Delphi 7 till 2007, covering combinations of Low/High fn name and AnsiString/WideString data type.
For Delphi 2009 till XE2 you would have to add two more functions for UnicodeString datatype.
And also mark those function inline for those Delphi versions, that support it (this is where jedi.inc comes handy again.
Hopefully you don't need supprot for UTF8String, but if you do - you know what to do about it now (if compiler would manage to tell it from AnsiString when overloading...)

Why doesn't code from "The Tomes of Delphi" compile?

I'm trying to use the TDRecLst and TDSplyCm units from the code included with The Tomes of Delphi, but I get a compiler error in TDBasics.pas:
I get a similar error in TDStrRes.inc:
What's wrong, and how do I fix it?
The code is available from the author.
You're evidently using a Delphi version that's newer than Delphi 6. Despite being updated in 2005, the code from that book only detects up to that version of Delphi. TDDefine.inc defines a number of compiler symbols based on the version it detects, but when the version you're using isn't anything it recognizes, it defines no symbols. That eventually leads to problems later when the compiler encounters code like this in TDBasics.pas;
implementation
uses
{$IFDEF Delphi1}
WinTypes, WinProcs;
{$ENDIF}
{$IFDEF Delphi2Plus}
Windows;
{$ENDIF}
{$IFDEF Kylix1Plus}
Types, Libc;
{$ENDIF}
{$IFDEF Delphi1}
{$R TDStrRes.r16}
{$ENDIF}
{$IFDEF Delphi2Plus}
{$R TDStrRes.r32}
{$ENDIF}
{$IFDEF Kylix1Plus}
{$R TDStrRes.r32}
{$ENDIF}
const
UnitName = 'TDBasics';
Since none of Delphi1, Delphi2Plus, or Kylix1Plus is defined, the uses clause is empty. When we ignore all the compiler directives and inactive code blocks, the compiler ultimately sees code like this:
implementation
uses
const
UnitName = 'TDBasics';
That's why the compiler complains about expecting an identifier instead of const.
To fix it, you need to teach TDDefine.inc to recognize your version of Delphi. Easier, though, might be to ignore all the version-detection code and hard-code all the symbols that apply to the version you're using. As long as you never use any version older than Delphi 6, all the symbols will apply to all your versions.
Find the following block of code in TDDefine.pas:
{$IFDEF VER140}
{$DEFINE Delphi6}
{$DEFINE Delphi1Plus}
{$DEFINE Delphi2Plus}
{$DEFINE Delphi3Plus}
{$DEFINE Delphi4Plus}
{$DEFINE Delphi5Plus}
{$DEFINE Delphi6Plus}
{$DEFINE HasAssert}
{$ENDIF}
Remove the first and last lines so that the remaining $DEFINE instructions are processed unconditionally.

How can i detect specific RTL features at compile time?

For sake of example lets check for infamous TStrings.StrictDelimiter:
{$IF Declared(TStrings.StrictDelimiter)}
{$MESSAGE WARN 'Beware of TStrings.StrictDelimiter which is False by default!'}
{$IFEND}
However, Declared compiler intrinsic reports syntax error on conditional line: E2029 ')' expected but '.' found. (tested on XE)
For Delphi XE2 I'm using this :
{$IFDEF BDS9}
Result.VersionString := 'Delphi XE2 ' +
{$IF NOT DECLARED(Consts.SStyleFeatureNotSupported)}
'(original release version)'
{$ELSE} {$IF NOT DECLARED(FireMonkeyVersion)} // D2010 chokes when scope (FMX.Types) is mentioned!
'Update 1'
{$ELSE} {$IF NOT DECLARED(System.TestSSE)}
'Update 2'
{$ELSE}
'Update 3'
// TODO : Update this for any following update!
{$IFEND} {$IFEND} {$IFEND}
;
{$ELSE}
{$IFDEF BDS7}
Result.VersionString := 'Delphi 2010';
{$ELSE}
{$MESSAGE ERROR 'Extend this!'}
{$ENDIF}
{$ENDIF}
In other words : I test for the existence of symbols that are introduced since any particular delphi-version. The same construct can be used to set a variable or constant or compiler define, so further code can use these instead.
Note : I keep a backup around of the Source folder for all Delphi versions that I've had installed in the past few years. Putting these folders through a tool like BeyondCompare and browsing through the differencing files, will quickly give you a few symbols that you can test for....
Unfortunately this kind of expressions aren't supported, you have to know in which RTL / compiler version some feature was introduced and then use predefined conditional symbols like VER<nnn>, RTLVersion, CompilerVersion etc.

{$IFOPT A4}?

In Delphi 2009 (or older versions), how do you check the "Align" compile option in the code?
The IFOPT directive seems to work only with pure switches ( {$IFOPT A4} does not compile ).
I couldn't find an equivalent constant or such defined ( {$IF Align = 4} or such )
You can do this by defining a record with known packing rules and check it using SizeOf. Tested in Delphi 2009:
type
TTestRec = record
A: Byte;
B: Int64;
end;
{$IF SIZEOF(TTestRec) = 9}
{$MESSAGE HINT '$A1'}
{$ELSEIF SIZEOF(TTestRec) = 10}
{$MESSAGE HINT '$A2'}
{$ELSEIF SIZEOF(TTestRec) = 12}
{$MESSAGE HINT '$A4'}
{$ELSEIF SIZEOF(TTestRec) = 16}
{$MESSAGE HINT '$A8'}
{$ELSE}
{$MESSAGE HINT 'Unknown alignment'}
{$IFEND}
Write code to test the actual runtime behavior. Only way I can think of.
There is {$IFOPT A+} directive, but it doesn't tell you alignment value.
I believe there is no way to do this :(

Resources