I got a problem in my Lazarus project: everytime I want to use a function it throws the above error (External: SIGSEGV). I don't know what that means, but some debugging showed me, that this is the code, causing the error:
class function TUtils.AsStringArray(const Strs:TStrings): TStringArray;
var
s:string;
i:integer;
begin
SetLength(Result, Strs.Count);
i := 1;
for s in Strs do
begin
Result[i] := s;
i := i + 1;
end;
end;
And the definitions
TStringArray = array of string;
TUtils = class
public
[...]
class function AsStringArray(const Strs:TStrings): TStringArray; static;
end;
The exception occurs after i := i + 1;. I would be really thankful if you could help me!
Dynamic arrays such as TStringArray = array of string; are zero-based; your code uses it as 1-based and raises access violation.
You should replace i := 1; by i := 0;
To the second Problem, it is because you are accesing to the index i, wich at the start it is 1 that is why you have the problem, the range of the array is determined by "length - 1", so if your length is 1, then your range is 0. So to solve the problem in your for loop you have to put Result[i-1] := s; like this you acces the index you really want.
More of this on http://wiki.freepascal.org/Dynamic_array
Related
Good day, I'm doing some Codeforces exercises in my free time, and I had a problem to test if the user was a boy or a girl, well, my problem isn't that, i have just demonstrated the code.
While compiling my code in my computer ( I'm using version 3.0.4 for i386 ) i get no error, but codeforces gives me this error
program.pas(15,16) Error: Operator is not overloaded: "freq(Char;AnsiString):LongInt;" + "ShortInt"
program.pas(46,4) Fatal: There were 1 errors compiling module, stopping
The error wasn't clear enough to me, as the same script was perfectly compiled with my version.
The platform is using ( version 3.0.2 i386-Win32 ).
program A236;
uses wincrt, sysutils;
var
username : String;
function freq(char: char; username : String): Integer;
var
i: Integer;
begin
freq:= 0;
for i:= 1 to length(username) do
if char = username[i] then
freq:= freq + 1;
//writeln(freq);
end;
function OddUserName(username : String): Boolean;
var
i, counter: Integer;
begin
OddUserName:= false; // even
counter:= 0;
for i:= 1 to length(username) do
if freq(username[i], username) <> 1 then
delete(username, i, 1)
else
counter:= counter + 1;
if counter mod 2 <> 0 then
OddUserName:= true; // odd
//writeln(counter);
//writeln(OddUserName);
end;
begin
readln(username);
if not OddUserName(username) then
writeln('CHAT WITH HER!')
else
writeln('IGNORE HIM!');
//readkey();
end.
The error is supposed to be at this line probably :
function freq(character: char; username : String): Integer;
Thanks for everyone who helps.
Inside of a function, the function's name can be used as a substitute for using an explicit local variable or Result. freq() and OddUserName() are both doing that, but only freq() is using the function name as an operand on the right-hand side of an assignment. freq := freq + 1; should be a legal statement in modern Pascal compilers, see Why i can use function name in pascal as variable name without definition?.
However, it would seem the error message is suggesting that the failing compiler is treating freq in the statement freg + 1 as a function type and not as a local variable. That would explain why it is complaining about not being able to add a ShortInt with a function type.
So, you will have to use an explicit local variable instead, (or the special Result variable, if your compiler provides that), eg:
function freq(charToFind: char; username : String): Integer;
var
i, f: Integer;
begin
f := 0;
for i := 1 to Length(username) do
if charToFind = username[i] then
f := f + 1;
//writeln(f);
freq := f;
end;
function freq(charToFind: char; username : String): Integer;
var
i: Integer;
begin
Result := 0;
for i := 1 to Length(username) do
if charToFind = username[i] then
Result := Result + 1;
//writeln(f);
end;
I have a Question about Delphi StringLists and sorting them. I am sorting a list of attributes (with duplicate entries) so I need to retain their original index before the sort. Here is a sample of what I am trying
procedure TestFind;
var
i, iIndex :integer;
slStrings : TStringlist;
begin
slStrings := TStringList.Create;
slStrings.Sorted := False;
slStrings.Add('Zebra');
slStrings.AddObject('Zebra',TObject(1));
slStrings.Add('Bat');
slStrings.AddObject('Bat',TObject(2));
slStrings.Add('Cat');
slStrings.AddObject('Cat',TObject(3));
slStrings.Add('Hat');
slStrings.AddObject('Hat',TObject(4));
slStrings.Add('Aat');
slStrings.AddObject('Aat',TObject(5));
slStrings.sorted := True;
if slStrings.Find('Zebra',iIndex) then
begin
while slStrings.Strings[iIndex] = slStrings.Strings[iIndex + 1] do
begin
i := ObjectToInt(slStrings.Objects[iIndex]) ;
AddMemoData ('Stringlist Found at Position: ' + inttoStr(i) + ' Current index position is: ' + inttoStr(iIndex), false);
inc(iIndex);
end;
i := ObjectToInt(slStrings.Objects[iIndex]) ;
AddMemoData ('Stringlist Found at Position: ' + inttoStr(i) + ' Current index position is: ' + inttoStr(iIndex), false);
end;
end;
When I run this I get a Value of 0,8 for Zebra, this makes no sense to me, I would expect a message of 1,4
I really can't work out what your code is trying to achieve, but it is accessing beyond the end of the list. To avoid that your while test can be modified like so:
while (iIndex<slStrings.Count-1)
and (slStrings.Strings[iIndex] = slStrings.Strings[iIndex + 1]) do
Your use of Objects[] will work. Values placed there are kept with their matching Strings[] values when the list is sorted.
However, if I were you I would not use a string list for this task. I would declare a record like this:
TMyRec = record
Name: string;
Index: Integer;
end;
I would hold them in a TList<TMyRec> and then sort them using a custom comparer.
I note that you add each object twice, once with an associated index, and once without. Those latter instances will get a default index value of 0. I also observe that the code you present will not execute because of the out of bounds error that I identified. Further, even when you fix that it does not give output of the form that you claim.
In other words, it appears that the code you posted is very different from the code that you are running. I've answered based on the code that you included in the question. I hope that you can accept an answer on that basis and don't expect help with the code that you have, that we cannot see. Perhaps I should just have voted to close.
Anyway, perhaps the main problem is here:
slStrings.Add('Zebra');
slStrings.AddObject('Zebra',TObject(1));
slStrings.Add('Bat');
slStrings.AddObject('Bat',TObject(2));
slStrings.Add('Cat');
slStrings.AddObject('Cat',TObject(3));
slStrings.Add('Hat');
slStrings.AddObject('Hat',TObject(4));
slStrings.Add('Aat');
slStrings.AddObject('Aat',TObject(5));
This is equivalent to:
slStrings.AddObject('Zebra',TObject(0));
slStrings.AddObject('Zebra',TObject(1));
slStrings.AddObject('Bat',TObject(0));
slStrings.AddObject('Bat',TObject(2));
slStrings.AddObject('Cat',TObject(0));
slStrings.AddObject('Cat',TObject(3));
slStrings.AddObject('Hat',TObject(0));
slStrings.AddObject('Hat',TObject(4));
slStrings.AddObject('Aat',TObject(0));
slStrings.AddObject('Aat',TObject(5));
Did you actually mean to write this:
slStrings.AddObject('Zebra',TObject(1));
slStrings.AddObject('Bat',TObject(2));
slStrings.AddObject('Cat',TObject(3));
slStrings.AddObject('Hat',TObject(4));
slStrings.AddObject('Aat',TObject(5));
the Solution was this:
procedure TestFind;
var
i, iIndex, iStringSize :integer;
slStrings : TStringlist;
begin
slStrings := TStringList.Create;
slStrings.Sorted := False;
slStrings.AddObject('Zebra',TObject(1));
slStrings.AddObject('Bat',TObject(2));
slStrings.AddObject('Cat',TObject(3));
slStrings.AddObject('Hat',TObject(4));
slStrings.AddObject('Zebra',TObject(6));
slStrings.AddObject('Aat',TObject(5));
slStrings.AddObject('Zebra',TObject(7));
slStrings.sorted := True;
if slStrings.Find('Bat',iIndex) then
begin
//find lowest position of string matching found string
while iIndex > 0 do
begin
if (g_slVials.Strings[iIndex] = g_slVials.Strings[iIndex-1]) then
iIndex := iIndex - 1
else
break;
end;
iStringSize := slStrings.Count;
while iIndex < iStringSize -1 do //check for more matching strings in higher range
begin
if (g_slVials.Strings[iIndex] = g_slVials.Strings[iIndex+1]) then
begin
i := ObjectToInt(slStrings.Objects[iIndex]) ;
AddMemoData ('Stringlist Found at Position: ' + inttoStr(i) + ' Current index position is: ' + inttoStr(iIndex), false);
inc(iIndex);
end else
break;
end;
i := ObjectToInt(slStrings.Objects[iIndex]) ;
AddMemoData ('Stringlist Found at Position: ' + inttoStr(i) + ' Current index position is: ' + inttoStr(iIndex), false);
end;
end;
this allows me to find all matching strings and return their index position
I have tried the following code (and varients) without any sucess, nor can I find any examples of how to call these Windows Functions from Delphi out there. Any clues would be very gratefully received.
The CryptProtectMemory does appear to produce some possibly encrypted result, but the unprotect does not change that result at all.I suspect I have done something charactisticly stupid, but I havent found it all day...
function WinMemEnc(PlnTxt: String): String;
var
Enc: Pointer;
j: Integer;
EncSze: Cardinal;
ws: String;
const
CRYPTPROTECTMEMORY_SAME_PROCESS: Cardinal = 0;
EncryptionBlockSize: Integer = 8;
begin
if Length(PlnTxt) mod EncryptionBlockSize = 0 then
j := Length(PlnTxt)
else
j := ((Length(PlnTxt) div 8) + 1) * 8;
ws := StringofChar(' ', j);
Move(PlnTxt[1], ws[1], j);
Enc := Pointer(ws);
EncSze := j * 2;
if CryptProtectMemory(Enc, EncSze, CRYPTPROTECTMEMORY_SAME_PROCESS) then
begin
Setlength(Result, j);
Move(Enc, Result[1], EncSze);
end;
end;
function WinMemDcr(EncInp: String): String;
var
Enc: Pointer;
j: Integer;
EncSze: Cardinal;
ws: String;
const
CRYPTPROTECTMEMORY_SAME_PROCESS: Cardinal = 0;
begin
j := Length(EncInp);
EncSze := j * 2;
ws := EncInp;
Enc := Pointer(ws);
if CryptUnprotectMemory(Enc, EncSze, CRYPTPROTECTMEMORY_SAME_PROCESS) then
begin
Setlength(Result, j);
Move(Enc, Result[1], EncSze);
end;
end;
You have set EncryptionBlockSize := 8; while in my library CRYPTPROTECTMEMORY_BLOCK_SIZE = 16.
You also mistakenly move only half of the input string to ws, because j holds the length of the string while Move() moves Count number of bytes. A Unicode Char is 2 bytes.
As said in the comments, encryption/decryption works on bytes and storing an encryption in a string is a potential disaster.
So here's my suggestion for a encryption/decryption of a string with encrypted storage in TBytes.
function MemEncrypt(const StrInp: String): TBytes;
begin
Result := TEncoding.Unicode.GetBytes(StrInp);
if Length(Result) mod CRYPTPROTECTMEMORY_BLOCK_SIZE <> 0 then
SetLength(Result, ((Length(Result) div CRYPTPROTECTMEMORY_BLOCK_SIZE) + 1) * CRYPTPROTECTMEMORY_BLOCK_SIZE);
if not CryptProtectMemory(Result, Length(Result), CRYPTPROTECTMEMORY_SAME_PROCESS) then
raise Exception.Create('Error Message: '+IntToStr(GetLastError));
end;
function MemDecrypt(const EncInp: TBytes): String;
var
EncTmp: TBytes;
begin
EncTmp := Copy(EncInp);
if CryptUnprotectMemory(EncTmp, Length(EncTmp), CRYPTPROTECTMEMORY_SAME_PROCESS) then
result := TEncoding.Unicode.GetString(EncTmp)
else
raise Exception.Create('Error Message: '+IntToStr(GetLastError));
end;
In the decryption a copy of the input TBytes is made to preserve the encrypted data.
And finally a test procedure:
procedure TForm13.Button2Click(Sender: TObject);
const
Txt = '1234567890123456789012345678901';
var
s: string;
b: TBytes;
begin
s := Txt;
Memo1.Lines.Add(s);
b := MemEncrypt(Txt);
s := MemDecrypt(b);
Memo1.Lines.Add(s);
end;
Without testing it (purely from the looks of your code), I believe the problem lies in the MOVE statement:
Move(Enc, Result[1], EncSze);
You are moving data from the location of the pointer - not from the data that the pointer is pointing to.
You should use
Move(Enc^, Result[1], EncSze);
to move data from the location that is POINTED TO by the pointer, and not from the pointer itself.
To clarify: The Enc variable is - say - located at address $12345678 and the data you are manipulating is located at address $99999999
This means that at address $12345678 is located 4 bytes ($99 $99 $99 and $99). And at address $99999999 is located the data you are manipulating.
The statement
Move(Enc, Result[1], EncSze);
thus moves EncSize bytes from the address $12345678 to the 1st character of the string variable Result. This you do not want, as it will only move 4 bytes of $99 and then whatever follows at address $1234567C and on.
To move data from the address $99999999 you need to tell the compiler, that you want to move data from the location POINTED TO by the pointer, and not from the POINTER itself:
Move(Enc^, Result[1], EncSze);
But other that that, I agree with David. You should stop using strings as storage for non-string data. It'll bite you in the a** at some point. Use a byte array (TBytes) instead.
in our last class we defined a couple of functions and procedures in our unit file.
procedure WriteNap(const elo: string; const n: TNap; const uto: string);
begin
Write(elo, Nap2Str(n), uto);
end;
function PredNap(const n: TNap): TNap;
begin
case n of
hetfo: PredNap := vasarnap;
NemNap: PredNap := n;
else
PredNap := Pred(n)
end;
end;
function SuccNap(const n: TNap): TNap;
begin
case n of
NemNap: SuccNap := n;
else
SuccNap := Succ(n)
end;
end;
I have to get rid of the word 'Nap' so that i can just use Pred, Write, Succ in the main program instead of PredNap etc... i tried to create another unit file which uses this unit and contains functions like Succ Pred... didnt really work out..
thanks in advance :)
Declare in somewhere in your file:
var Pred = Function (Const n:TNap):TNap;
And before you do anything in the body of your program, write:
Pred := #PredNap;
What this code does is, it creates a new function pointer with the same signature as your PredNap() and then assign the address of PredNap() to this pointer. So, whenever you call Pred() the actual function that gets called is PredNap(). Hope this helps.
I want to develop a setup package for conditionally upgrading an existing package. I want to check the existing software version against to-be-installed version. In order to do that, I have to compare the version strings.
How can I convert the string value to a numerical value in a Inno setup script?
RegQueryStringValue(HKEY_LOCAL_MACHINE, 'Software\Blah blah', 'Version', version)
version = 'V1.R2.12';
numVersion := ??string_to_numerical_value??(version);
This is a little more tricky, as you would want to handle versions like 'V1.R2.12' and 'V0.R15.42' correctly - with the simple conversion in the other answer you would get 1212 and 1542, which would not compare the way you would expect.
You need to decide how big each part of the version number can be, and multiply the parts by that value to get a correct end number. Something like this:
[Code]
function string_to_numerical_value(AString: string; AMaxVersion: LongWord): LongWord;
var
InsidePart: boolean;
NewPart: LongWord;
CharIndex: integer;
c: char;
begin
Result := 0;
InsidePart := FALSE;
// this assumes decimal version numbers !!!
for CharIndex := 1 to Length(AString) do begin
c := AString[CharIndex];
if (c >= '0') and (c <= '9') then begin
// new digit found
if not InsidePart then begin
Result := Result * AMaxVersion + NewPart;
NewPart := 0;
InsidePart := TRUE;
end;
NewPart := NewPart * 10 + Ord(c) - Ord('0');
end else
InsidePart := FALSE;
end;
// if last char was a digit the last part hasn't been added yet
if InsidePart then
Result := Result * AMaxVersion + NewPart;
end;
You can test this with the following code:
function InitializeSetup(): Boolean;
begin
if string_to_numerical_value('V1.R2.12', 1) < string_to_numerical_value('V0.R15.42', 1) then
MsgBox('Version ''V1.R2.12'' is not as recent as version ''V0.R15.42'' (false)', mbConfirmation, MB_OK);
if string_to_numerical_value('V1.R2.12', 100) > string_to_numerical_value('V0.R15.42', 100) then
MsgBox('Version ''V1.R2.12'' is more recent than version ''V0.R15.42'' (true)', mbConfirmation, MB_OK);
Result := FALSE;
end;
Whether you pass 10, 100 or 1000 for AMaxVersion depends on the number and range of your version number parts. Note that you must not overflow the LongWord result variable, which has a maximum value of 2^32 - 1.
I haven't tried that (and my Pascal knowledge is a bit rusty), but something like the following should work:
function NumericVersion(s: String): Integer;
var
i: Integer;
s1: String;
begin
s1 := '';
for i := 0 to Length(s)-1 do
if (s[i] >= '0') and (s[i] <= '9') then
s1 := s1 + s[i];
Result := StrToIntDef(s1, 0);
end;
Please not that you'll have to play with the start and end value for i as I'm not sure whether it is zero-based or not (s[0] may contain the length of the string if it is a "Pascal String").
I've implemented two version strings (actually one string and one dword value) in the registry to overcome complexity.
displayversion="v1.r1.0"
version="10100" (=1*10^4 + 1*10^2 + 0*10^0)
That's simple. Though not an answer to this question, however one might think the other way around when faced with complexity, which could be avoided in a simpler way.