Link to home
Start Free TrialLog in
Avatar of aztec
aztec

asked on

PChar problem

These PChars are driving me nuts...they never give a result you'd expect...very unpredictable. I have a string array with about 20 character strings of 255 chars each. I simply want to loop thru them in a 'for' loop, and tack all these character strings together into 1 Pchar variable. Like this:

For x:=1 to 20 do
begin
  hpchar:=nil;
  StrPCopy(hpchar, strarray[x]);
  StrCat(mainpchar, hpchar);
end;


(hpchar and mainpchar are declared as PChar variables)

..I've tried every darn variation you can think of...with StrAlloc, StrLCat, StrCat...nothing works right.
I can't even initialize my 'mainpchar' variable right...if I initialize it to nil and then try to tack something on to it, it gives a EAccessViolation error. Very frustrating, these PChar's.

Regards,
  Shawn Halfpenny
  drumme59@sprint.ca

P.S: How come I never receive email notification when I question is answered? I always have the little box checked for it?
Avatar of boabyte
boabyte

Have you tried reading the string array (I assume an array of characters) into a string and then using StrPCopy. I use this a lot and it works fine?

Westy
ie in line before StrPCopy do

  MyString := StrArray[x] ;
  StrPCopy(hpchar,MyString) ;

This is a technique I use from the Delphi 1 days as sometime (unknown reason, maybe optimised away) you have to read a value into a local variable. Examples I have dound is from reading a Database value ie  using  DB1.fieldbyname('FIELD1').AsString rather than reading it into a variable. Anyway hope it helps.

Westy(boabyte)
ASKER CERTIFIED SOLUTION
Avatar of erajoj
erajoj
Flag of Sweden image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of aztec

ASKER

No, you guys aren't getting it. I tried to keep my explantion simple before but i might as well give you all the details now:
I'm using a sort utility which accepts multiple input files passed through a PChar variable (ie. 'infile1.txt+infile2.txt+infile3.txt...'). Also, a person using my application could potentially select THOUSANDS of input files. Now, since the StrAlloc function can only allocate a length of 65536 (..Cardinal variable..according to D3 documentation), this won't do as the length of 65536 *could* possibly be exceeded. I thought PChars were supposed to be virtually limitless anyway...I'm confused?
   Anyway, in my app, I allow the user to select his input files...I save these file names in a ListBox as ListBox string Items.

Here's basically the code I got, but it doesn't work right. All I want is all these filename strings to be tacked together - one after another (with a '+' sign in between them), into one big long PChar variable..

infileP:=nil;    {initialize..}
for i:=0 to (ListBox5.items.count - 1) do
begin
  if m <> 0 then StrCat(infileP, '+');
  hinfile:=stralloc(1000);  
  StrPCopy(hinfile, ListBox5.items[i]);
  StrCat(infileP, hinfile);
  strdispose(hinfile);
end;
Did you read my answer?
1. StrAlloc can allocate 2 GB long PChars. Read 2 lines further down in the help file.
2. Use AppendStr with Pascal-style strings. Then you will get dynamic allocation and a radically faster code on large counts of strings, see my first example.

var
  iIndex: Integer;
  sTemp: string; // only available on the stack
begin
  if ( Listbox5.Items.Count = 0 ) then Exit;
  StrDispose( infileP ); // prevent heap leaks

  sTemp := Listbox5.Items[ 0 ];
  for iIndex := 0 to Listbox5.Items.Count - 1 do AppendStr( sTemp, '+' + Listbox5.Items[ iIndex ] ); // ordinary string operations
  infileP := StrAlloc( Length( sTemp ) + 1 ); // reserve space for zero terminator, this is essential even before StrCat!
  infileP := StrPCopy( sTemp ); // copy from stack to heap
end;

/// John
Why you use StrAlloc, StrPCopy etc.

Just
var infile: string; // not local variable

in the procedure

  infile := '';
  for I := 0 to ListBox5.Items.Count - 1 do
  begin
    if I <> 0 then infile := infile + '+';
    infile := infile + ListBox5.Items[I];
  end;

if you want use infile as null-terminated string
use PChar(infile) and all
 
My comment/answer was an example of how to use the functions, nothing else.
I supposed Shawn had his reasons for using PChars, probably for self-educational purposes, so therefor...
To be able to PChar-typed long strings is a fundament of OP9+, so everybody should already know that.

/// John
Avatar of aztec

ASKER

Hi John...
  Thanks for your answer. I always thought that strings could only be 255 characters in length? They can be bigger? That's why I've been trying to do all this conversion stuff to PChar.
  Does this mean I have to turn the 'Huge Strings' option on? I was told some time back to turn these off as they were causing major problems in migrating my app from Delphi 1 to Delphi 3.
 
   anyway, regarding your answer...a few questions:

sTemp := Listbox5.Items[ 0 ];
for iIndex := 0 to Listbox5.Items.Count - 1 do AppendStr( sTemp, '+' +Listbox5.Items[ iIndex ] ); // ordinary string operations

The 'AppendStr' statement generates this error for me :
  "Types of actual and formal var parameters must be identical".
  Any thoughts?

  So basically what you're proposing is to tack the whole thing together in a regular string variable, THEN convert that whole thing to a PChar. OK, so a string varibale is in fact NOT limited to 255 chars then?

Also in the 'For' statement, I assume you meant to start the 'For' loop with iIndex = 1...not 0 as you have.

Cheers
   Shawn Halfpenny
Hi again,
My NT-partition blew up a few days ago and I haven't gotten to reinstalling Delphi yet so the code was not tested at all.
I'm not sure why AppendStr doesn't work, but you can replace it with "sTemp := sTemp+...".
Always use huge strings in your 32-bit apps. The migration problems occur since there is no position 0 in the huge strings, but there is a #0 (zero terminator) put at the end of it.
All your remarks are correct.
If I get Delphi up and running today(CET), I will take a look at the AppendStr problem and get back to you.
By the way...there is seldom any need for PChar use in Delphi32 code at all. Most things can be handled with strings and simple type-conversions (as vladika wrote).

/// John
Hi again...
Delphi up and running...
I can't repeat your "Types of actual and formal var parameters must be identical"-error.
I tried different solutions using AppendStr, StrCat, Move...what did I discover?
The AppendStr method is about 3 times faster than the StrCat one.
I tried to concatenate 10000 300 char long strings, and on my machine it took about 180 seconds to do so.
I also tried to use Move. It was much, much, much (even more much) faster, but it is rather complicated to implement.
Here's my xtremely optimized solution ( ~400 times faster on 5000 strings (~150 ms))(this code works!):

function TForm1.AssembleString( list: TStrings ): string;
var
  iIndex, iLen, iCount: Integer;
  szBuf: PChar;
  sTemp: string;
begin
  {$B-} // make sure you compile w/o complete bool eval
  if ( ( not Assigned( list ) ) and ( ListBox1.Items.Count = 0 ) ) then begin // no need to continue...
    Result := '';
    Exit;
  end;
  iCount := list.Count;
  iLen   := 0;

  for iIndex := 0 to iCount - 1 // precalc buffer size...
    do Inc( iLen, Length( list[ iIndex ] ) );

  SetString( Result, nil, iLen + iCount - 1 ); // set string buffer size
  szBuf := PChar( Result ); // store temporary char pointer

  // this block saves ListBox1.Items.Count-1 if-statements in the following for-loop... :-)
  sTemp := list[ 0 ];
  iLen  := Length( sTemp );
  System.Move( PChar( sTemp )^, szBuf^, iLen); // memory copy from list string to string buffer
  Inc( szBuf, iLen );

  for iIndex := 1 to iCount - 1 do
  begin
    szBuf^ := '+';
    Inc( szBuf );
    sTemp := list[ iIndex ];
    iLen  := Length( sTemp );
    System.Move( PChar( sTemp )^, szBuf^, iLen); // memory copy from list string to string buffer
    Inc( szBuf, iLen );
  end;
end;

Usage:
  sMyString := AssembleString( ListBox5.Items );

Nothing to it, really...

/// John
HUUGE BUG ABOVE!

Should be:

begin
  // {$B-} no need, sorry!
  if ( ( not Assigned( list ) ) {>>>} OR {<<<} ( ListBox1.Items.Count = 0 ) ) then begin // no need to continue...
    Result := '';
    Exit;
  end;

/// John
Avatar of aztec

ASKER

Worked great John...thanks very much for your help on this!

cheers!
   Shawn