ICS HTTPCLI Free Exception - delphi

How can I properly free the component once i make it with a loop like this? If I free it like I do now I get some GETMEM.INC exception. I am coming from Indy so i don't really know ICS too much.
Thanks
const
URLs : array[0..3] of string =
(
'http://www.example.com',
'http://www.example.com',
'http://www.example.com',
'http://www.example.com'
) ;
var
Output: array of TStringList;
S: array of TMemoryStream;
Async: array of TSslHttpCli;
implementation
procedure RequestDone(Sender: TObject; RqType: THttpRequest;
ErrCode: Word);
begin
with Sender as TSSLHTTPCLI do begin
S[Tag].Position:=0;
Output[Tag].LoadFromStream(S[Tag]);
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
i:integer;
begin
for i := 0 to High(URLS) do begin
S[i]:=TMemoryStream.Create;
Output[i]:=TStringList.Create;
Async[i]:=TSslHttpCli.Create(nil);
Async[i].Tag:=i;
Async[i].FollowRelocation:=true;
Async[i].NoCache:=true;
Async[i].SocketFamily:=sfAny;
Async[i].OnRequestDone:=RequestDone;
Async[i].RcvdStream:=S[i];
Async[i].URL:= URLs[i];
Async[i].MultiThreaded:=true;
Async[i].GetASync;
end;
end;
procedure TForm1.Button4Click(Sender: TObject);
var
i:integer;
begin
for i := 0 to High(URLS) do begin
Output[i].Free;
Async[i].RcvdStream.Free;
Async[i].Free; // << -- EXCEPTION
// S[i].Free;
end;
end;

You never allocate any memory for Result, Asynch, or S. You need to SetLength on each of them before you can put anything into them (or take anything back out).
procedure TForm1.Button1Click(Sender: TObject);
var
i:integer;
begin
SetLength(Result, Length(URLS));
SetLength(S, Length(URLS));
SetLength(Asynch, Length(URLS));
for i := 0 to High(URLS) do begin
S[i]:=TMemoryStream.Create;
Result[i]:=TStringList.Create;
Async[i]:=TSslHttpCli.Create(nil);
// etc.
end;
end;
BTW, Result is a terrible name for a variable, especially one that's global in scope. It's the return value from a function that's automatically generated by the compiler, and use anywhere but in a function makes your code hard to read. See this, for instance:
var
Result: string = '';
procedure AddToReslt(CharToAdd: Char);
begin
// Many many lines of code
// go in here. Maybe a few loops
// of if statements.
Result := Result + CharToAdd;
end;
function DoSomeMath: Integer;
begin
// Some really complex numeric code, maybe
// calculating the value of `pi` to the 900th
// digit
Result := 2 * 2;
end;
Now quickly - remembering that each of them containss lots of code - which one is a function and which is a procedure?

Related

Is there a way to solve an I/O error 6 in Delphi?

procedure TfrmSongs.Display;
var
i: Integer;
begin
redOutput.Clear;
redOutput.Lines.Add('The TOP 10');
for i := 1 to iCount-1 do
begin
redOutput.Lines.Add(IntToStr(i)+arrSongs[i]);
end;
end;
procedure TfrmSongs.FormActivate(Sender: TObject);
var
tSongList: TextFile;
sSong: string;
begin
iCount := 0;
AssignFile(tSongList, ExtractFilePath(Application.ExeName)+'Songs.txt');
Reset(tSongList);
while not EOF do
begin
Readln(tSongList, sSong);
arrSongs[iCount] := sSong;
Inc(iCount);
end;
CloseFile(tSongList);
Display;
end;
I'm trying to display the array I tried to create via a text file in a rich edit. But every time I run the app, it gives me an 'I/O error 6' error and nothing displays. I don't know if it's something with the text file or if it's something with the display procedure.
There are a few problems with your code, but regarding the I/O error specifically, error 6 means "invalid file handle".
Since you are getting a popup error notification, you clearly have I/O checking enabled, which it is by default.
I/O error 6 is not typical for a failure on System.Reset(), and you are not seeing any other kind of error related to a failure in opening a file, so we can safely assume that the file is being opened successfully, and that System.Readln() and System.CloseFile() are not being passed an invalid I/O handle.
So that leaves just one line that could be receiving an invalid I/O handle:
while not EOF do
System.Eof() has an optional parameter to tell it which file to check. Since you are omitting that parameter, Eof() will use System.Input instead. And a GUI process does not have a STDIN handle assigned by default. So that is likely where error 6 is coming from.
That line needs to be changed to this instead:
while not EOF(tSongFile) do
UPDATE: given the declaration of arrSongs you have shown in comments (arrSongs: array[1..MAX] of string;), there are additional problems with your code. You need to make sure the reading loop does not try to store more than MAX strings in the array. Also, your reading loop is trying to store a string at index 0, which is not a valid index since the array starts at index 1. Also, Display() is skipping the last string in the array. See what happens when you omit important details?
Try this instead:
private
arrSongs: array[1..MAX] of string;
...
procedure TfrmSongs.Display;
var
i: Integer;
begin
redOutput.Clear;
redOutput.Lines.Add('The TOP 10');
for i := 1 to iCount do
begin
redOutput.Lines.Add(IntToStr(i) + arrSongs[i]);
end;
end;
procedure TfrmSongs.FormActivate(Sender: TObject);
var
tSongList: TextFile;
sSong: string;
begin
iCount := 0;
AssignFile(tSongList, ExtractFilePath(Application.ExeName) + 'Songs.txt');
Reset(tSongList);
try
while (not EOF(tSongList)) and (iCount < MAX) do
begin
Readln(tSongList, sSong);
arrSongs[1+iCount] := sSong;
Inc(iCount);
end;
finally
CloseFile(tSongList);
end;
Display;
end;
That being said, I would suggest getting rid of the reading loop completely. You can use a TStringList instead:
uses
..., System.Classes;
...
private
lstSongs: TStringList;
...
procedure TfrmSongs.Display;
var
i: Integer;
begin
redOutput.Clear;
redOutput.Lines.Add('The TOP 10');
for i := 0 to lstSongs.Count-1 do
begin
redOutput.Lines.Add(IntToStr(i+1) + lstSongs[i]);
end;
end;
procedure TfrmSongs.FormCreate(Sender: TObject);
begin
lstSongs := TStringList.Create;
end;
procedure TfrmSongs.FormDestroy(Sender: TObject);
begin
lstSongs.Free;
end;
procedure TfrmSongs.FormActivate(Sender: TObject);
begin
lstSongs.LoadFromFile(ExtractFilePath(Application.ExeName) + 'Songs.txt');
Display;
end;
Or, you can use TFile.ReadAllLines() instead:
uses
..., System.IOUtils;
...
private
arrSongs: TStringDynArray;
...
procedure TfrmSongs.Display;
var
i: Integer;
begin
redOutput.Clear;
redOutput.Lines.Add('The TOP 10');
for i := 0 to High(arrSongs) do
begin
redOutput.Lines.Add(IntToStr(i+1) + arrSongs[i]);
end;
end;
procedure TfrmSongs.FormActivate(Sender: TObject);
begin
arrSongs := TFile.ReadAllLines(ExtractFilePath(Application.ExeName) + 'Songs.txt');
Display;
end;

TClientDataSet Custom compare field function

I am using an in-memory TClientDataSet with a TStringField column which contains folders path (Delphi 7).
When I create an index on this column the order is not what I am looking for.
As an example I get :
c:\foo
c:\fôo\a
c:\foo\b
when I would like this order :
c:\foo
c:\foo\b
c:\fôo\a
So I searched a way to use my own compare field function.
Based on this RRUZ answer How to change the implementation (detour) of an externally declared function I tried the following :
type
TClientDataSetHelper = class(DBClient.TClientDataSet);
...
MyCDS : TClientDataSet;
...
// My custom compare field function
function FldCmpHack
(
iFldType : LongWord;
pFld1 : Pointer;
pFld2 : Pointer;
iUnits1 : LongWord;
iUnits2 : LongWord
): Integer; stdcall;
begin
// Just to test
Result := -1;
end;
...
---RRUZ code here---
...
procedure HookDataCompare;
begin
HookProc
(
(MyCDs as TClientDataSetHelper).DSBase.FldCmp, <== do not compile !!!
#FldCmpHack,
FldCmpBackup
);
end;
When I try to compile I get an error (MyCDs as TClientDataSetHelper).DSBase.FldCmp : not enough actual parameters
I do not understand why this does not compile. Could you please help me ?
Is it even possible to "detour" IDSBase.FldCmp in DSIntf.pas ? Am i totally wrong ?
Thank you
EDIT
Finally, thanks to Dsm answer, I transformed the TStringFieldcolumn into a TVarBytesField in order to avoid doubling the buffer. Plus, when a TVarBytesField is indexed the order is based on the bytes value so I get the order I want. For having all child folders after a parent folder and before the next parent folder (c:\foo.new after c:\foo\b), I patched TVarBytesFieldlike this :
TVarBytesField = class(DB.TVarBytesField)
protected
function GetAsString: string; override;
procedure GetText(var Text: string; DisplayText: Boolean); override;
procedure SetAsString(const Value: string); override;
end;
function TVarBytesField.GetAsString: string;
var
vBuffer : PAnsiChar;
vTaille : WORD;
vTexte : PAnsiChar;
vI : WORD;
begin
Result := '';
GetMem(vBuffer, DataSize);
try
if GetData(vBuffer) then
begin
vTaille := PWORD(vBuffer)^;
vTexte := vBuffer + 2;
SetLength(Result, vTaille);
for vI := 1 to vTaille do
begin
if vTexte^ = #2 then
begin
Result[vI] := '\';
end
else
begin
Result[vI] := vTexte^;
end;
Inc(vTexte);
end;
end;
finally
FreeMem(vBuffer);
end;
end;
procedure TVarBytesField.GetText(var Text: string; DisplayText: Boolean);
begin
Text := GetAsString;
end;
procedure TVarBytesField.SetAsString(const Value: string);
var
vBuffer : PAnsiChar;
vTaille : WORD;
vTexte : PAnsiChar;
vI : WORD;
begin
vBuffer := AllocMem(DataSize);
try
vTaille := WORD(Length(Value));
PWORD(vBuffer)^ := vTaille;
vTexte := vBuffer + 2;
for vI := 1 to vTaille do
begin
if Value[vI] = '\' then
begin
vTexte^ := #2
end
else
begin
vTexte^ := Value[vI];
end;
Inc(vTexte);
end;
SetData(vBuffer);
finally
FreeMem(vBuffer);
end;
end;
The message is telling you that FldCmp is a function, and it is expecting you to execute it, but it has not got enough parameters. I am sure that you already realised that and probably already tried to get the address of the function with the # (like you do for FldCmpHack) and found that that does not work.
The reason for that is, I am afraid, that FldCmp is not a normal function. DSBase is actually an interface, which will have been assigned (looking at the source code) by a class factory. What you actually need is the real function itself and for that you need the real object that the class factory creates. And I am sorry, but I can't see any realistic way of doing that.
However, the DSBase field is only created if it has not been assigned, so you could, in theory, create your own IDSBase interface object, which is the way this type of problem is meant to be handled. That is a lot of work, though, unless you know class that the class factory produces and can descend from that.
A sneakier alternative is to override the Translate property and create some sort of hash (perhaps by translating the ASCII codes to their HEX values) so that the database keeps them in the right order
TClientDataSetHelper = class(TClientDataSet)
public
function Translate(Src, Dest: PAnsiChar; ToOem: Boolean): Integer; override;
end;

Delphi Script (Changing Section Name for VMProtect)

I don't know Delphi and would someone help me to change a small Delhpi script from vmprotect?
function GetRandomSectionName: String;
var I:Integer;
B:Byte;
begin
Result:='';
for I:=1 to 8 do
begin
B:=32+Random(Ord('z')-32);
Result:=Result+Chr(B);
end;
end;
procedure OnAfterSaveFile;
var I:Integer;
begin
with VMProtector.OutputFile do
for I:=0 to Sections.Count-1 do
Sections.Items[I].Name:=GetRandomSectionName;
end;
It should only randomize the sections which is starting with .vmp the rest should stay.
It should only randomize the sections which is starting with .vmp the rest should stay.
Use the System.Pos function to match a substring in a string.
procedure OnAfterSaveFile;
var I:Integer;
begin
with VMProtector.OutputFile do
for I:=0 to Sections.Count-1 do
if Pos('.vmp',Sections.Items[I].Name) = 1 then // Only .vmp sections
Sections.Items[I].Name:=GetRandomSectionName;
end;

Custom procedure for sorting an array

Im going through one of my old exam papers, preparing for my finals, and for the love of life, I cant figure out how to do this!
The program was working earlier, but it wasn't sorting the array. Now Im getting an error saying EAccess violation with message: access violation at address 00404BDE
Here's my code (its kind of long, maybe you can help me spot my error) :
private
{ Private declarations }
iCount : Integer;
arrDams : array [1..200] of string;
Procedure List;
procedure Display;
procedure Sort;
procedure Search (sDam : String);
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Display; //Display with Numbers
var
k : Integer;
begin
for K := 1 to 200 do
begin
RedOut.Lines.Add (IntToStr(k) + '.) ' + (arrDams[k]));
end;
end;
procedure TForm1.FormCreate(Sender: TObject); // Create
begin
//
end;
procedure TForm1.List; // TextFile to array
var
MyFile : TextFile;
k : Integer;
begin
If FileExists('Dams.txt') <> True
then Application.Terminate;
AssignFile (MyFile, 'Dams.txt');
Reset(MyFile);
For K := 1 to 200 do
begin
Readln(MyFile, arrDams[k])
end;
end;
procedure TForm1.Search(sDam: String); // Search
begin
end;
procedure TForm1.Sort; // Sort;
var
K,L : byte;
sKeep : string;
begin
for k := 1 to iCount -1 do
begin
for l := k + 1 to iCount do
begin
if arrDams[k] > arrDams[L] then
begin
sKeep := arrDams[k];
arrDams[k] := arrDams[L];
arrDams[L] := sKeep
end;
end;
end;
end;
procedure TForm1.btnListClick(Sender: TObject);
begin
List;
Display;
end;
procedure TForm1.btnDisplayClick(Sender: TObject);
begin
display;
sort;
end; //<---------- ERROR OVER HERE!
end.
Theres 3 buttons at the top of the form, namely Show list, Make new textfile with list and Sort list alphabetically. The button Im working on is to sort the list. This question paper says I must make a sort procedure and must be called when the Sort Button is clicked.
Thanks for any advice/help
P.S.
Can you please point me to a link where they explain Selection Sorting in depth - the logic isn't with me on this..
You don't initialize iCount. So it is 0. Therefore iCount-1 is -1. However, you use Byte, an unsigned type, for your loop variable. Now, -1 when interpreted as an unsigned Byte is 255. If you follow this all through it means that you access the array out of bounds. In fact what happens is that the inner loop executes exactly once with a value of l equal to 0 and k equal to 255.
Were you to enable the range checking compiler option, you would have encountered a runtime error as soon as you run off the end of the array.
Presumably you want to initialize iCount to some value. I cannot tell what, but you will know.
Beyond that, stop using unsigned types for loop variables. Replace Byte with Integer.

Delphi-TeeChart : Marks.DrawItem not working

I'd like to draw marks in Chart.ChartAfterDraw using Aseries.Marks.DrawItem method. I'm using the following code.
procedure TMyForm.ChartAfterDraw(Sender: TObject);
var
mark : TTextShape;
begin
mark := TTextShape.Create(Chart);
mark.Font := ClickedSeries.Marks.Font;
ClickedSeries.Marks.DrawItem(mark,
clRed,
'pippo',
ClickedSeries.Marks.Positions[ ClickedTask ] );
end;
But as you can see in figure below mark is created fine, in correct position, but its text is not printed.
In debug mod when cursor go past the end of TMyForm.ChartAfterDraw the following message appear.
This sounds like an error occured in Marks.DrawItem method. Unfortunately i'm using TeeChart Lite, so i cant't see where that method fails (if it really fails)
Can you help me? Thank you.
P.s. I'm using XE5
It may be easier to directly draw your texts/marks using Chart1.Canvas.Rectangle() and Chart1.Canvas.TextOut() functions, but here you have an example using Marks.DrawItem() function. Note you had to create and initialize a TSeriesMarkPosition to pass it to the Marks.DrawItem().
uses Series;
type
TShapeAccess=class(TCustomTextShape);
var ClickedSeries: TChartSeries;
ClickedTask: Integer;
procedure TForm1.FormCreate(Sender: TObject);
var i: Integer;
begin
Chart1.View3D:=false;
for i:=0 to 3 do
with Chart1.AddSeries(TPointSeries) do
FillSampleValues;
end;
procedure TForm1.Chart1Click(Sender: TObject);
var i: Integer;
begin
for i:=0 to Chart1.SeriesCount-1 do
begin
ClickedTask:=Chart1[i].Clicked(Chart1.GetCursorPos.X, Chart1.GetCursorPos.Y);
if ClickedTask>-1 then
begin
ClickedSeries:=Chart1[i];
Break;
end;
end;
Chart1.Draw;
end;
procedure TForm1.Chart1AfterDraw(Sender: TObject);
var
mark : TTextShape;
position: TSeriesMarkPosition;
tmpString: String;
begin
if (ClickedSeries<>nil) and (ClickedTask>-1) and (ClickedTask<ClickedSeries.Count-1) then
begin
tmpString:='pippo';
mark := TTextShape.Create(Chart1);
mark.Text:=tmpString;
mark.Font := ClickedSeries.Marks.Font;
TShapeAccess(mark).CalcBounds(Chart1);
position:=TSeriesMarkPosition.Create;
position.LeftTop.X:=ClickedSeries.CalcXPos(ClickedTask);
position.LeftTop.Y:=ClickedSeries.CalcYPos(ClickedTask);
position.Width:=Chart1.Canvas.TextWidth(tmpString)+2;
position.Height:=Chart1.Canvas.TextHeight(tmpString)+4;
ClickedSeries.Marks.DrawItem(mark, clRed, tmpString, position);
end;
end;

Resources