Problem adding lots of strings to a TStringList - delphi

I have a problem adding strings to a TStringList. I've searched other posts but couldn't find an answer to this.
What I'm trying to do is to add a big amount of strings to a TStringList (more than 14000) but somewhere in the process I get an EAccessViolation. Here's the code I'm using:
procedure TForm1.FormCreate(Sender: TObject);
begin
List := TStringList.Create;
List.Duplicates := dupAccept;
end;
procedure TForm1.ButtonStartClick(Sender: TObject);
begin
List.Clear;
List.Add('125-AMPLE');
List.Add('TCUMSON');
List.Add('ATLV 4300');
List.Add('150T-15');
List.Add('TDL-08ZE');
List.Add('RT20L');
List.Add('SIN LINEA');
List.Add('TIARA');
List.Add('FL200ZK1');
List.Add('FL250ZK1');
List.Add('SIN LINEA');
List.Add('CENTAURO-70 S.P.');
List.Add('CORSADO');
{ This list continues to about 14000 strings...}
List.Add('VOSJOD 2');
List.Add('Z 125');
List.Add('ZUMY');
List.Add('NEW AGE 125');
List.Add('SIN LINEA');
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
FreeAndNil(List);
end;
¿What's wrong with this code? The list contains duplicate strings so I set the Duplicates property to dupAccept. I was able to load the list using LoadFromFile, but I don't want to have a text file outside my application.
I hope you can help me!!! Please tell me if you need any further information.
Thank you very much. I really appreciate your help.

The suggestions for using an external file are on the mark here. However, your post indicates your desire for not having an external file. I would then suggest you link the file to the executable as a resource. You can easily do this following these steps:
Place all the strings into a text file called stringdata.txt (or whatever name you choose). Then create a .rc file of whatever name you choose and put the following into it (STRING_DATA can be any identifier you choose):
STRING_DATA RCDATA "stringdata.txt"
Create a .res file from the .rc:
BRCC32 <name of rc>.rc
Now reference this file from the source code. Place the following someplace in the unit:
{$R <name of res>.res}
Instead of loading from a file stream, load from a resource stream:
StringData := TResourceStream.Create(HInstance, 'STRING_DATA', RT_RCDATA);
try
List.LoadFromStream(StringData);
finally
StringData.Free;
end;
If you do command-line automated builds, I would suggest you keep the .rc file under source control and build the .res during the build process. This way you can also keep the stringdata.txt file under source control and any edits are automatically caught on the next build without having to explicitly build the .res file each time the .txt file changes.

What Delphi version are you using? Some older versions had a bug in the memory manager that can cause an access violation when trying to reallocate an array to a size that's too large.
Try adding FastMM4 to your project to replace the old memory manager and see if that helps.
Also, you're probably better off keeping the list in an external file. Yes, it's another file, but it also means that you can change the list without having to recompile the entire program. This also makes creating (and distributing!) updates easier.

Mason is probably right for the cause of the AV; this is quite a large array to grow.
On a side note, when doing such a long processing on a StringList, it's recommended to surround it by BeginUpdate/EndUpdate to avoid firing any update event.
Even if you don't have any now, they might be added later and you'll get problems.

Set list.capacity to the number of items you plan to add, immediately after you create the list. Alternatively, place the list in an RC file (named other than with the name of your project) and add it to your project. This gets compiled into your application, but does not involve executable code to create the list.

I would also worry about compiler integrity with a 14,000 line procedure. People have found other cases where going beyond anything reasonable breaks the compiler in various ways.

You may also want to try THashedStringList, could see a speed boost (although not in this function), although I'm not sure if the add method is a whole lot different.

try using the following instead of your code to add the strings to the StringList
var
Str: string;
begin
Str := '125-AMPLE' + #13#10;
Str := Str + 'TCUMSON' + #13#10;
Str := Str + 'ATLV 4300' + #13#10;
Str := Str + '150T-15' + #13#10;
................
List.Text := Str;
end;

Related

Parsing Memo for Files and Folders

I'm totally new to Delphi I guess learning by doing something should be ok (My hope!)
My Idea was because I often have to re-create the same tasks:
creating always the same directorys which contains sometimes files and sometimes leaved empty...
So my conclusion was to automate it in some way.
Assume a Memo containing the following:
config.xml|enc
/skin
/data/defines.dat:blub
/temp
The Basepath where all the stuff above should be created inside:
C:\users\BGates\test
":blub" is just placeholders e.g. :blub can contain any text which comes from another memo in my application which means that later defines.dat is filled with the text blub contains...
As you can see sometimes I use | and sometimes : for the placeholder...
So from the information above I would like to parse the contents of the memo to create a directory structure like this:
C:\users\BGates\test\
config.xml
skin
data
defines.dat (while defines.dat will contain the stuff which comes from blub)
temp
My problem is the parsing of the memo esspecially how to decide its a folder or its a folder in another folder, then its a file in the root or a file inside of a folder and so on...
Well it might be there is an easier way (I was reading about csv files and such but then? My tool would be hard to understand for someone using it which doesn't know how a csv file needs to look like) while my example above feels maybe familier to them...
Could someone show me please an example how to parse it in a correct (best practice) way So I could learn from it?
There are routines in the SysUtils unit that make file path parsing a lot easier. Have a look at ExtractFileName and ExtractFilePath, for starters. Also, if you're using a recent version of Delphi, (D2010 or any of the XE line,) the IOUtils unit contains a set of helper methods under the TPath record that simplify working with paths.
For example, if I wanted to deal with the line /data/defines.dat:blub, I'd do something like this:
function NormalizePath(const name: string): string;
begin
result := StringReplace(name, '/', '\', [rfReplaceAll]);
end;
procedure ProcessLine(line: string);
var
path, filename, data: string;
colonPos: integer;
begin
colonPos := pos(':', line);
if colonPos > 0 then
begin
data := copy(line, colonPos + 1);
delete(line, colonPos, MAXINT);
end;
line := TPath.Combine(BASE_PATH, normalizePath(line));
if ExtractFileExt(line) = '' then
path := line
else begin
path := ExtractFilePath(line);
filename := line;
end;
ForceDirectories(path); //ensure that the folder exists
if filename <> '' then
TFile.WriteAllText(filename, data);
end;
Note: I just wrote this off the top of my head. It may contain bugs. Don't trust it without testing it first. Also, this uses functionality from IOUtils, and things will be a little bit trickier if you don't have it in your version of Delphi. But this should give you the general idea of how to deal with the problem you're trying to solve.

Get functionname from the functionpointer?

I have a pointer to functions like this.
TTestEvent = function(): Boolean;
procedure ExecuteTest(aTest: TTestEvent; aType: String);
begin
if aTest then
NotifyLog(aType + ' success')
else
TestError(aType + ' failed');
end;
// Call the test
procedure TestAll;
begin
ExecuteTest(LoadParcels, 'LoadParcel');
end;
But it would be even nicer to extract the name of the function from the functionpointer aTest.
So instead of
aType + ' success'
I want something like
ExtractName(aTest) + ' success'
Can this be done in Delphi 2007 ?
If you use some of our Open Source classes, you'll be able to find the name of any symbol.
You'll have to create a .map file when building your executable, by setting "Detailed map" in your projects option.
You can then deliver the .map with the .exe, or compress the .map into our proprietary .mab format, which can be appended to the .exe. The .mab format is just a lot more efficient than .zip or other for this task: it is about 10 times smaller than the original .map file (that is, much smaller than JCLDebug or MaxExpect offers, and very much smaller than using the standard "Remote debugging symbol" embedding project option).
Then you can use the TSynMapFile class to retrieve debugging information from the .map file, or the information embedded into the .exe:
function ExtractName(aSymbolAddress: pointer): string;
var i: integer;
begin
with TSynMapFile.Create do // no name supplied -> will read from .exe
try
i := FindSymbol(aSymbolAddress);
if i>=0 then
result := Symbols[i].Name else
result := '';
finally
Free;
end;
end;
It will work for function names, but also any other symbols, like methods or global variables.
See this blog article about the class. And note that even if it is used by our mORMot framework, or its logging features, you just do not need to use the whole framework (just SynCommons.pas and SynLZ.pas units). See the Map2Mab.dpr program in "SQLite3\Samples\11 - Exception logging" sub folder to embed .map file content into an .exe.
You cannot do this with built-in features. In order to get a function name from an address you need to know the map of the executable. This is not part of an executable unless you take steps to add it.
Debugging tools like JclDebug and madExcept offer the functionality you are looking for.
You may implement a registration mechanism based on a Dictionary with
the function pointer as a Key and
the function name as a Value.
ExtractName would be a method of the dictionary.
Don't forget to make it thread safe if need be.

How operate on TFileStream

Hello recently I replace TextFile with TFileStream. I never use it so I have small problem with it.
How can I add someting to my file after I assign it to variable?
How can I read someting form that file?
I need defined line form that file so I was doing something like that:
var linia_klienta:array[0..30] of string;
AssignFile(tempPlik,'klienci.txt');
Reset(tempPlik);
i:=0;
While Not Eof(tempPlik) do
begin
Readln(tempPlik,linia_klient[i]);
inc(i);
end;
CloseFile(tempPlik);
Then when line two is needed I simply
edit1.text = linia_klienta[1];
If you need to read a text file and access each line, try instead using a TStringList class with this class you can load a file, read the data (accesing each line using a index) and save the data back.
something like this
FText : TStringList;
i : integer;
begin
FText := TStringList.Create;
try
FText.LoadFromFile('C:\Foo\Foo.txt');
//read the lines
for i:=0 to FText.Count-1 do
ProcessLine(FText[i]); //do something
//Add additional lines
FText.Add('Adding a new line to the end');
FText.Add('Adding a new line to the end');
//Save the data back
FText.SaveToFile('C:\Foo\Foo.txt');
finally
FText.Free;
end;
end;
end;
I newer versions of Delphi you can use TStreamReader / TStreamWriter here is an example of using TStreamReader ... this is only for manipulating text files
var
SR : TStreamReader;
line : String;
begin
SR := TStreamReader.Create('D:\test.txt');
while not (SR.EndOfStream) do
begin
line := SR.ReadLine;
ShowMessage(line);
end;
SR.Free;
end;
TStream and its immediate descendants are mostly low-level access class. They mostly deal with generic buffers. There are some more specialized classes that descend from or use a stream to perform higher level tasks.
Since Delphi 1 TReader and TWriter could be used to read and write Delphi types directly (inlcuding strings), but they were not designed to handle "line-oriented" files (unluckily they were designed too much with component properties streaming in mind, not as a general purpose framework).
Turbo Power SysTools has a nice TStAnsiTextStream class that implements line-oriented access to text files in a way similar to that of TextFile. Since Delphi 2009 new classes (see opc0de answer) implement the same kind of access without the need of third party libraries (moreover they support different encodings thanks to Delphi 2009 extend codepage support, including Unicode).
Depending with what you want to do, its the stream class you need.
Do you want to work with text (characters with break-lines and end-of-line characters) data ?
OR, do you want to work with binary data ?
I see you are using an array of char, instead, of a string.
Do you really want to use character data as if it was binary ?
Sometimes, some applications require that case.

How do I get the sha-256 hash of a section which contains code in a Portable Executable file, in Delphi?

I would like to get the sha-256 hash for a section which contains code(.text, CODE) in a Portable Executable file, in Delphi.
So far, I've tried to get the start and end address of the section to which the AddressOfEntryPoint points to, but if I load the same file several times, I get different start and end addresses.
Can anyone please help me?
This is the code:
procedure TForm1.Button1Click(Sender: TObject);
var x:TJCLPEImage;
aoep,cs,ce: cardinal;
pise: Pimagesectionheader;
nos : integer;
i : integer;
begin
x := TJCLPEImage.Create();
x.FileName:=edit1.Text;
aoep := x.OptionalHeader32.AddressOfEntryPoint;
pise := Pointer(PByte(#(x.LoadedImage.FileHeader.OptionalHeader)) + x.LoadedImage.FileHeader.FileHeader.SizeOfOptionalHeader);
for i:=0 to x.ImageSectionCount-1 do
begin
if (pise.VirtualAddress <= aoep) and (aoep < (pise.VirtualAddress + pise.Misc.VirtualSize)) then
break;
end;
inc(pise);
cs := DWORD(x.LoadedImage.MappedAddress) + DWORD(pise.PointerToRawData);
ce := cs + pise.Misc.VirtualSize;
Label1.caption:='Code start: '+Inttostr(cs);
Label2.caption:='Code end: '+inttostr(ce);
end;
Thank you.
I cant comment to your question yet, so i am trying to reply here, but not sure if i am thinking right about what you are asking.
Seems you want a way to assure no one changed your file after it loaded in memory.
That's why you want a sha-256 hash of that section, and probably you need to get that section and then hash it.
I never used JCL classes to do that. But found this unit that maybe help for you. It allow you to edit PE files. Was written in 2007, so maybe you will need upgrade some code. But i am most sure you will find the bases to what you want.
http://www.coderprofile.com/networks/source-codes/71/portable-executable-file-unit
I could not test it at all. But till what i tested, the start address did not changed here..
To get the Sha-256, will find many VCL components (or at least ActiveX) to do that. I could advise you to use LIBEAY32.DLL, but that would probably add one more dll to your application. Unless you already use it.
Hope that help in anyway.

How to delete files matching pattern within a directory

That is, delete all files matching pattern within a given directory
Example, Delete all *.jpg files within DirectoryName
procedure TForm1.Button1Click(Sender: TObject);
begin
DeleteFiles(ExtractFilePath(ParamStr(0)),'*.jpg');
end;
procedure DeleteFiles(APath, AFileSpec: string);
var
lSearchRec:TSearchRec;
lPath:string;
begin
lPath := IncludeTrailingPathDelimiter(APath);
if FindFirst(lPath+AFileSpec,faAnyFile,lSearchRec) = 0 then
begin
try
repeat
SysUtils.DeleteFile(lPath+lSearchRec.Name);
until SysUtils.FindNext(lSearchRec) <> 0;
finally
SysUtils.FindClose(lSearchRec); // Free resources on successful find
end;
end;
end;
In more recent versions of Delphi, you would probably use the classes in System.IOUtils, which are essentially wrapping FindFirst, FindNext etc:
procedure DeleteFilesMatchingPattern(const Directory, Pattern: string);
var FileName: string;
begin
for FileName in TDirectory.GetFiles(Directory, Pattern) do TFile.Delete(FileName);
end;
You can use the SHFileOperation function. The nice thing about using SHFileOperation is you have the option of deleting the files to the recycle bin and you get the normal API animations so the user will know what is going on. The downside is the delete will take a little longer than Jeff's code.
There are several wrappers out there. I use this free wrapper from BP Software. The entire wrapper file is only 220 lines and is easy to read and use. I don't install this as a component. I have found it easier to add this unit to my project and just Create and free the object as needed.
Update: The download link for the BP Software site is no longer valid. There is an older version on the Embarcadero website.
TSHFileOp (1.3.5.1) (3 KB) May
31, 2006 TComponent that is a wrapper
for the SHFileOperation API to copy,
move, rename, or delete (with
recycle-bin support) a file system
object.
The file name parameter for SHFileOperation supports MS DOS style wildcards. So you can use the component like this:
FileOps := TSHFileOp.Create(self);
FileOps.FileList.Add(DirectoryName + '\*.jpg');
FileOps.HWNDHandle := self.Handle;
FileOps.Action := faDelete;
FileOps.SHOptions :=
[ofAllowUndo, ofNoConfirmation, ofFilesOnly, ofSimpleProgress];
FileOps.Execute;
I usually show the "Are you sure" message myself so I always pass the ofNoConfirmation flag so Windows does not ask again.
If you don't want to delete every jpg file or you need to delete from multiple directories you can add full file names or different paths with wild cards to the FileList string list before calling execute.
Here is the MSDN Page for SHFileOperation
Note that SHFileOperation has been replaced by IFileOperation starting with Windows Vista. I have continued to use SHFileOperation on Windows Vista without any problems.

Resources