How to adjust the level of compression with TZCompressionStream? - delphi

When I create a TZCompressionStream object with:
var
cs: TZCompressionStream;
dest: TStream;
level: TZCompressionLevel;
...
cs := TZCompressionStream.Create(level, dest);
I get this compiler error:
E2250 There is no overloaded version of 'Create' that can be called with these arguments
But my code is according to the constructor declaration:
Create(compressionLevel: TZCompressionLevel; dest: TStream); overload;
When I used XE, everything was OK. But now with XE5 there is this error. Why?
Update:
Working code: cs := TZCompressionStream.Create(dest);
Faliing code: cs := TZCompressionStream.Create(clMax, dest);
I also tried to change the order of the arguments, which was unsuccessful.

I'm assuming that your code is as stated in your edit:
cs:=TZCompressionStream.Create(clMax, dest);
The obvious explanation is that clMax is not what you think it is. There is probably another unit that defines clMax and that unit appears after ZLib in your list of uses. Solve the problem by either:
fully qualifying the enumerated value: ZLib.clMax, or
change the order of the uses so that ZLib appears after the unit which defines the other clMax.

Related

Delphi: Code eliminated by linker incorrectly

I'm using Delphi 10.2. I'm having a problem calling TList<>.Last. The Evaluate window tells me the code for the function has been eliminated by the linker.
The code snippets:
uses
ModelObjects,
ProximitySearch,
System.Classes,
System.UITypes,
System.Generics.Collections,
Winsoft.FireMonkey.FPdfView,
Winsoft.FireMonkey.PDFium;
...
type
TWidgetFinder = class(TObject)
private
fFieldInfos: TList<TFieldInfo>;
fPAnnotation: FPDF_ANNOTATION;
...
procedure TWidgetFinder.ConfigureFieldInfo;
var
key: String;
buffer: TBytes;
textLen: LongWord;
temp: String;
begin
...
SetLength(buffer, KShortBufferLength);
textLen := FPDFAnnot_GetStringValue(fPAnnotation, ToStringType(key), buffer, Length(buffer))
temp := TEncoding.Unicode.GetString(buffer, 0, textLen - 2);
fFieldInfos.Last.Name := TEncoding.Unicode.GetString(buffer, 0, textLen - 2);
...
The problem was fFieldInfos.Last.Name was empty. I thought I was not converting the buffer to a string correctly. But the correct string is written to temp. When I Evaluate fFieldInfos.Last.Name after assigning to it I get the following message:
Function to be called, {System.Generics.Collections}TList<ModelObjects.TFieldInfo>.Last, was eliminated by linker
I've seen the SO solutions that suggest I call the eliminated function innocuously during initialization. But it cannot be that Delphi is eliminating code randomly and I must discover each elimination as a bug. I don't understand what I have done that tells the linker TList<>.Last is not being used when I am clearly using it. Can someone help me understand this?
Thanks
TList<T>.Last is a function marked as inline. Such methods usually are not contained in the binary so you cannot use them in the evaluator during debugging. The same is the case most likely if you type fFieldInfos[fFieldInfos.Count-1] because GetItem (the getter behind the index property) is marked as inline as well.
What you can type into the evaluator though is fFieldInfos.List[fFieldInfos.Count-1] to get the last item in the list.
P.S. As for the issue of Name being empty - if TFieldInfo is a record that assignment is not going to work because .Last will return a copy of that record and assign Name to that one not affecting the one inside the list.

E2250 There is no overloaded version of 'StrPas' that can be called with these arguments

My code :
function ThisModuleName: Char; //bulo String
var
p: array [0..512] of char;
fileNamePart: pchar;
begin
GetModuleFileName(HInstance, #p[0], 512);
GetFullPathName(#p[0], 512, #p[0], fileNamePart);
result := StrPas(WideString(#p[0])); //stalo WideString
end;
In Delphi 7 compiles.
In Delphi 10.2 it gives an error:
[dcc32 Error] verinfo.pas(98): E2250 There is no overloaded version of 'StrPas' that can be called with these arguments
This code is wrong on all Delphi versions. I doubt it compiles anywhere. I'm guessing that the code you presented is not the Delphi 7 code, but rather the code after you've hacked at it for a while.
The return type should be string and not char. Furthermore, the cast to WideString is bogus. Finally, a zero-based array of characters can be treated as PChar.
Your function should be translated like so:
function ThisModuleName: string;
var
p: array [0..511] of Char;
fileNamePart: PChar;
begin
GetModuleFileName(HInstance, p, Length(p));
GetFullPathName(p, Length(p), p, fileNamePart);
Result := p;
end;
Having said all of that, while this is a faithful translation of the code in the question, it does not return a module name. I really don't know what your code is trying to do, but the call to GetFullPathName appears to be wrong in your code.
My guess is that you are trying to convert potential short 8.3 file names to long names. I believe that you need an extra buffer to make that work. Here's what that code looks like, with some error checking added:
function ThisModuleName: string;
var
ModuleFileName, Buffer: array [0..511] of Char;
FilePart: PChar;
begin
Win32Check(GetModuleFileName(HInstance, ModuleFileName, Length(ModuleFileName))<>0);
Win32Check(GetFullPathName(ModuleFileName, Length(Buffer), Buffer, FilePart)<>0);
Result := Buffer;
end;
Instead of asking a question for every problem you encounter in your porting project it might pay dividends to learn a bit more about Unicode Delphi.
Instead of calling the API directly, you can call System.SysUtils.GetModuleName, which simply returns a string.
It wraps GetModuleFilename, and by doing so it also shows how to call that function. I hope I'm allowed to quote a couple of lines from the unit mentioned above. It also uses the MAX_PATH constant, which contains the maximum length of a path.
Note that GetModuleFilename already returns a fully qualified path, so calling GetFullPathName afterwards is redundant.
function GetModuleName(Module: HMODULE): string;
var
ModName: array[0..MAX_PATH] of Char;
begin
SetString(Result, ModName, GetModuleFileName(Module, ModName, Length(ModName)));
end;
This is mainly useful if you want the path of a dll, if you're interested in the main executable, you can simply use Application.ExeName.

Can I modify a constant in the RTL class System.Classes.TStream and rebuild it at runtime in Delphi XE6?

I am trying to work around a known ugly performance limitation in System.Classes.pas, which has a 1980s era constant buffer limit ($F000) that looks like this:
function TStream.CopyFrom(const Source: TStream; Count: Int64): Int64;
const
MaxBufSize = $F000;
....
This is causing major performance penalties in our Delphi application. In delphi XE2 through XE5, we were able to modify this and use one of the following approaches:
I could modify the Delphi sources, and then, by invoking dcc32.exe from a batch file, rebuild the System.Classes.dcu file in the Delphi library folder. I realize this is ugly and I didn't like doing this, but I don't like this ugly performance issue in the RTL either, and our users can not live with the performance headaches it causes.
I could try to put a modified system.classes.pas file somewhere in my project search path.
Neither of the above approaches is working for me in Delphi XE6, now, thanks probably to some internal compiler changes. The error I get in a minimal command line application that includes System.Contnrs in its uses clause, is this:
[dcc32 Fatal Error] System.Classes.pas(19600): F2051 Unit System.Contnrs was compiled with a different version of System.Classes.TComponent
The sample program to reproduce this problem (assuming you have modified System.Classes.pas and changed the MaxBufSize constant), is shown here:
program consoletestproject;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.Contnrs,
System.SysUtils;
var
List:System.Contnrs.TObjectList;
begin
WriteLn('Hello world');
end.
Again, this problem reproduces easily in Delphi XE6, but is not a problem in XE5, or earlier.
What is the recommended practice when you absolutely MUST work around a fundamental RTL or VCL limitation using a modified copy of System.Classes.pas or System.SysUtils.pas or some other very low level unit? (Yes, I know you should NOT do this if you don't have to, don't bother with a lecture.)
Are there a magic set of command line parameters you can use via "dcc32.exe" on the command line, to produce a modified DCU that will link properly with the application example above?
As a secondary question, are there .dcu files for which no source exists that will break when one tries to do this, in which case the answer to all of the above is, "you can't fix this, and if there's a bug in the RTL, you're out of luck"?
One possible workaround is to include "$(BDS)\source\rtl\common" in your project search path (or library path), forcing each broken (needing recompile) DCU to rebuild EACH time, but this seems ugly and wrong.
You can overcome this limitation using a detour, try this sample which uses the Delphi Detours Library
First define the signature of the method to hook
var
Trampoline_TStreamCopyFrom : function (Self : TStream;const Source: TStream; Count: Int64): Int64 = nil;
then implement the detour
function Detour_TStreamCopyFrom(Self : TStream;const Source: TStream; Count: Int64): Int64;
const
MaxBufSize = 1024*1024; //use 1 mb now :)
var
BufSize, N: Integer;
Buffer: TBytes;
begin
if Count <= 0 then
begin
Source.Position := 0;
Count := Source.Size;
end;
Result := Count;
if Count > MaxBufSize then BufSize := MaxBufSize else BufSize := Count;
SetLength(Buffer, BufSize);
try
while Count <> 0 do
begin
if Count > BufSize then N := BufSize else N := Count;
Source.ReadBuffer(Buffer, N);
Self.WriteBuffer(Buffer, N);
Dec(Count, N);
end;
finally
SetLength(Buffer, 0);
end;
end;
Finally replace the original function by the trampoline (you can use this code in the initialization part of some unit)
Trampoline_TStreamCopyFrom := InterceptCreate(#TStream.CopyFrom, #Detour_TStreamCopyFrom);
And to release the hook you can use
if Assigned(Trampoline_TStreamCopyFrom) then
InterceptRemove(#Trampoline_TStreamCopyFrom);
Update 1: The suggestion below does not work for the Classes unit in XE6. The basic technique is sound and does solve similar problems. But for XE6, at least the Classes unit, it is not immediately obvious how to re-compile it.
This appears to be a fault introduced in XE6 because this technique is meant to work and is officially endorsed by Embarcadero: http://blog.marcocantu.com/blog/2014_august_buffer_overflow_bitmap.html
Update 2:
In XE7, this problem no longer exists. It would appear that whatever was broken in XE6 has been fixed.
You need the compiler options to match those used when the unit was compiled by Embarcadero. That's the reason why your implementation section only change fails when it seems like it ought to succeed.
Start a default project and use CTRL + O + O to generate these options. I get
{$A8,B-,C+,D+,E-,F-,G+,H+,I+,J-,K-,L+,M-,N-,O+,P+,Q-,R-,S-,T-,U-,V+,W-,X+,Y+,Z1}
when I do this in XE6.
Put that at the top of your copy of the unit and you should be good to go. You can probably get away with a cut-down subset of these, depending on your host project options. In my code I find that:
{$R-,T-,H+,X+}
suffices.

Delphi XE4 gives E2036 when accessing generic list items of 'object's

This is probably similar / continuation on the previous question below:
Why Delphi XE3 gives "E2382 Cannot call constructors using instance variables"?
Now I'm trying Delphi XE4 with the same code (with 'constructor' changed to 'procedure' as per the solution of the above question).
Now I have also these things in a generics list, i.e. I have
TCoordRect = object
public
function Something: Boolean;
end;
and then a list of these in a function parameter, which I loop through and try to access the items directly:
function DoSomething(AList: TList<TCoordRect>): Boolean;
var
i: Integer;
begin
Result := False;
for i := 0 to AList.Count - 1 do
begin
Result := Result or AList[i].Something; // <-- Here comes the compiler error!
end;
end;
This gives the compiler error "E2036 Variable required". However, if I don't access it directly, i.e put instead a local variable and use that first, then it works:
function DoSomething(AList: TList<TCoordRect>): Boolean;
var
i: Integer;
ListItem: TCoordRect;
begin
Result := False;
for i := 0 to AList.Count - 1 do
begin
ListItem := AList[i];
Result := Result or ListItem.Something; // <-- Now this compiles!
end;
end;
And another "workaround" is to remove all these 'object' types and change them to 'class', but I'm curious as to why this does not work like it used to? Is it again just something with "the compiler moving towards mobile development" or is there some more specific reason, or is this even a bug? BTW I also reported this as a QC issue, so will see if something comes from there.
It's a compiler bug, and it's present in all earlier versions of the compiler. The fault is not limited to XE4. Submitting a QC report is the correct response.
I would not be surprised if Embarcadero never attempt to fix it. That's because you are using deprecated object. Switch to using record and the code compiles.
The issue you have uncovered in this question is unrelated to the SO question you refer to at the top of your question.
Incidentally, this really is a case of old meets new. Legacy Turbo Pascal objects, and modern day generic containers. You are mixing oil and water!

Reference parameters in Delphi to C++ DLL

I'm creating a DLL in Delphi that must have the following C++ structure.
DWORD Load(char* &Test);
So the test must be a reference parameter. I tried 'var' and 'out' in Deplhi, but I get an error in my C++ application that uses the DLL.
A literal translation of that code is this:
function Load(var Test: PAnsiChar): DWord; cdecl;
Notice the calling convention. If you're missing that, then Delphi places the first parameter in a register, but the C++ code expects it on the top of the stack.
Like 'Rob Kennedy' stated, it must have 'cdecl'. I fixed the problem by using that. Here is the fixed code
function Load(out Test : PAsniChar) : Integer; cdecl ; export;
begin
Test := 'Test String';
end;
Thanks for the help!

Resources