f



Strings and PChar (D4 Pro)

This is probably a very basic question, but I may have been improperly 
casting a string to PChar in some cases. For instance, I got the following 
code to work as shown:

====================================================
procedure TfmOrtDatabase.btBackupClick(Sender: TObject);
var       udfn: OrtData.TUserDataFile;
          S, dbFileName, saveFileName, dirName: String;
//          currDir: String;
          currDir: array[0..50] of Char;
begin
  if Application.MessageBox( 'Backup Database Files?',
         'Backup', MB_YESNO ) = ID_YES then begin //1
//    currDir := '01234567890123456789012345678901234567890123456789';
//    SetLength(currDir,50);
    GetCurrentDirectory(sizeof(currDir)+1, @currDir);
    dirName := GetSystemFolder(CSIDL_PERSONAL);
    dirName := dirName + '\Ortmaster';
    SelectDirectory(dirName, [sdAllowCreate,sdPerformCreate], 0);
//    SelectDirectory('Select Directory', dirName, dirName);
    S := FormatDateTime('yyyymmdd', Now);
    fmDBSaveDialog.InitialDir := dirName;
    for udfn := udfCust to udfTypes do begin //2
      dbFileName := ProgramDataFolder + '\' + 
OrtData.UserFileName[udfn]+'.dbf';
      fmDBSaveDialog.FileName := OrtData.UserFileName[udfn]+'.dbf';
      if not (fmDBSaveDialog.Execute) then exit;
      if (FileExists(dbFileName)) then begin //3
         saveFileName := fmDBSaveDialog.FileName + '-' + S;
         if not( CopyFile( pChar(dbFileName), PChar(saveFileName), FALSE ) ) 
then
            MessageDlg('File Copy Error', mtInformation, [mbOK], 0);;
      end; //-3
    end; //-2

    fmDBSaveDialog.InitialDir := dirName;
    dbFileName := 'Ortmaster' + '.rcf';
    fmDBSaveDialog.FileName := dbFileName;
    dbFileName := ProgramDataFolder + '\Ortmaster' + '.rcf';
    if not (fmDBSaveDialog.Execute) then exit;
    if (FileExists(dbFileName)) then begin //2
       saveFileName := fmDBSaveDialog.FileName + '-' + S;
       if not( CopyFile( pChar(dbFileName), pChar(saveFileName), FALSE ) ) 
then
          MessageDlg('File Copy Error', mtInformation, [mbOK], 0);;
    end; //-2
  SetCurrentDirectory(currDir);
  end; //-1
end;

=========================================================

If I used an uninitialized string, the GetCurrentDirectory() function 
failed, even when I initialized the string to a constant as shown. It's 
rather late, and maybe my brain is fogged, but it seems like there should be 
a better way to do this. It would be nice if there were a function like 
GetSystemFolder() that returns a string.

BTW, the SelectDirectory() function seems to be what I was searching for in 
a post from 7/20/15.

Also, I'm not sure why the current directory got changed by the function.

Thanks,

Paul 

0
P
8/27/2016 9:39:10 AM
comp.lang.pascal.delphi.misc 5769 articles. 1 followers. miniFAQ (1) is leader. Post Follow

10 Replies
134 Views

Similar Articles

[PageSpeed] 6

"P E Schoen"  wrote in message news:nprn44$mvb$1@dont-email.me... 

I found a simpler function:

===============================================
procedure TfmOrtDatabase.btBackupClick(Sender: TObject);
var           currDir: String;

begin
GetDir(0, currDir);
....
ChDir(currDir);
end;
===============================================

But I would still like to learn more about strings and PChar.

Thanks,

Paul 
0
P
8/27/2016 10:03:01 AM
On Sat, 27 Aug 2016 05:39:10 -0400, P E Schoen wrote:
> This is probably a very basic question, but I may have been improperly 
> casting a string to PChar in some cases. For instance, I got the following 
> code to work as shown:
> 
[snip]
> 
> If I used an uninitialized string, the GetCurrentDirectory() function 
> failed, even when I initialized the string to a constant as shown. It's 
> rather late, and maybe my brain is fogged, but it seems like there should be 
> a better way to do this. It would be nice if there were a function like 
> GetSystemFolder() that returns a string.

GetCurrentDirectory() arguments are: the buffer pointer, then the buffer
length. Not the buffer length, then the buffer pointer.

And, currDir variable was declarated as "array[0..50] of Char" which means
that it has a storage of 51 bytes plus one byte alignment padding (a total
of 52 bytes). The "sizeof(currDir)+1" expression would translate to "51+1"
which is 52 bytes. You're lucky you've declared currDir variable as an odd
length Char array. If you've declared it as an array with a length of
multiple of 4, there would be no padding following its storage. The data
following its storage might be used by other variable. So, if you use
"sizeof(currDir)+1" you may overwrite one byte which follows the actual
array - a byte outside of the array.
0
JJ
8/27/2016 10:05:22 AM
"JJ"  wrote in message news:1rco3b1rvcu6.1k5cho7qohwwc$.dlg@40tude.net...

[snip]
> GetCurrentDirectory() arguments are: the buffer pointer, then the buffer 
> length. Not the buffer length, then the buffer pointer.

From the Delphi help:

DWORD GetCurrentDirectory(

    DWORD nBufferLength,    // size, in characters, of directory buffer
    LPTSTR lpBuffer     // address of buffer for current directory
   );

> And, currDir variable was declarated as "array[0..50] of Char" which means 
> that it has a storage of 51 bytes plus one byte alignment padding (a total 
> of 52 bytes). The "sizeof(currDir)+1" expression would translate to "51+1" 
> which is 52 bytes. You're lucky you've declared currDir variable as an odd 
> length Char array. If you've declared it as an array with a length of 
> multiple of 4, there would be no padding following its storage. The data 
> following its storage might be used by other variable. So, if you use 
> "sizeof(currDir)+1" you may overwrite one byte which follows the actual 
> array - a byte outside of the array.

Yes, I probably should have used "sizeof(currDir)-1".

Also, perhaps I could use the return value of the function with a buffer 
size of zero. It would return the size needed, and then I could use alloc() 
to create the memory space to use. I just don't know what is the "best" way 
to handle functions like this, and I may have been using the PChar() cast 
improperly.

Thanks for the quick reply!

Paul 

0
P
8/27/2016 10:15:53 AM
"P E Schoen"  wrote in message news:nprogt$ri8$1@dont-email.me... 

> But I would still like to learn more about strings and PChar.

I found the following discussion, which is helpful:

http://www.delphigroups.info/2/0d/17014.html

Thanks,

Paul
0
P
8/27/2016 10:28:39 AM
P E Schoen schrieb:
> This is probably a very basic question, but I may have been improperly 
> casting a string to PChar in some cases. For instance, I got the 
> following code to work as shown:
> 
> ====================================================
> procedure TfmOrtDatabase.btBackupClick(Sender: TObject);
> var       udfn: OrtData.TUserDataFile;
>          S, dbFileName, saveFileName, dirName: String;
> //          currDir: String;
>          currDir: array[0..50] of Char;
> begin
>  if Application.MessageBox( 'Backup Database Files?',
>         'Backup', MB_YESNO ) = ID_YES then begin //1
> //    currDir := '01234567890123456789012345678901234567890123456789';
> //    SetLength(currDir,50);
>    GetCurrentDirectory(sizeof(currDir)+1, @currDir);

Unlike a fixed size ShortString, a dynamic string variable is a pointer 
to a dynamically allocated char array. I'd guess that sizeof(currDir) 
will return 4, the size of a pointer. Try Length instead, which returns 
the current (allocated) size of the string. Also use PCHAR(currDir) to 
get a pointer to the payload, not to the pointer variable.

DoDi
0
Hans
8/27/2016 10:50:09 AM
On Sat, 27 Aug 2016 06:03:01 -0400, P E Schoen wrote:
> 
> But I would still like to learn more about strings and PChar.

Let's start with the simplest one: ShortString. ShortString storage has a
fixed 256 bytes length. This is why ShortString can't hold more than 255
characters. The first byte is the length of the string. The remaining bytes
are the string characters. The data for "ABC" would be like below (in
hexadecimal) where xx is an undefined byte.

  03,41,42,43,xx,xx,...

i.e.

  ShortStringStorage = record
    StringLength : Byte;
    StringData   : Array [1..255] of Char;
  end;

A pointer to a ShortString (e.g. @MyShortString) would point to the first
byte of its storage - to the string length. MyShortString[0] would also
point to the string length - as Char type. And you can modify the string
length by altering the first byte. SizeOf(MyShortString) will always resolve
to 256 if MyShortString was declared as just ShortString. If it was declared
as ShortString[100], SizeOf(MyShortString) would resolve to 101.
Length(MyShortString) will resolve to the string length.


PChar...
PChar type is just a pointer to a Char type. It doesn't hold any actual
characters. It's equivalent to: ^Char. When used as a string pointer, the
data it points to is expected to be a null-terminated string - to the first
character of the string. SizeOf(MyPChar) would resolve to 4 because it's a
pointer. Length() is not applicable for PChar type. The data for "ABC" would
be like below.

  41,42,43,00


String...
String type is a combination between ShortString and PChar (null terminated
string). The String storage layout is like ShortString except that the
string length is 16-bit instead of 8-bit. The data for "ABC" would be like
below.

  03,00,41,42,43,00

i.e.

  (* 
  StringStorage = record
    StringLength : Word;
    StringData   : Array [1..n] of Char;
  end;
  Where n is variable. i.e. variable field size
  *)

The type itself works similar to PChar (i.e. a pointer), but it points to
the first character of the string instead to the string length. i.e. to the
third byte. SizeOf(MyString) would also resolve to 4 just like PChar.
Length(MyString) would resolve to the string length. You can't use
MyString[0] to access the string length. It's not allowed because the string
length is 16-bit. You'll have to use Length() instead.


Typecasting to PChar...
String can be safely typecasted to PChar, but not a pointer to ShortString
(PShortString). This is because ShortString is not a null terminated string.
If you need to typecast PShortString to PChar, you'll need to have a null
character following the string data (not the string storage). But be sure
the string length plus the null character doesn't exceed 255 characters.
e.g.

  procedure Example;
  var MyShortString : ShortString;
  var MyString      : String;
  var MyPChar       : PChar;
  begin
    MyShortString := 'ABC';
    if Length(MyShortString) < 255 then
    begin
      (* Append null character without changing string length *)
      MyShortString[Length(MyShortString)+1] := #0;
      MyPChar := @MyShortString[1];
    end
    else
    begin
      MyString := MyShortString;
      MyPChar := PChar(MyString);
    end;
    Application.MessageBox(MyPChar, 'Dialog', MB_OK);
  end;
0
JJ
8/27/2016 11:00:09 AM
On Sat, 27 Aug 2016 06:15:53 -0400, P E Schoen wrote:
> "JJ"  wrote in message news:1rco3b1rvcu6.1k5cho7qohwwc$.dlg@40tude.net...
> 
>> GetCurrentDirectory() arguments are: the buffer pointer, then the buffer 
>> length. Not the buffer length, then the buffer pointer.
> 
> From the Delphi help:
> 
> DWORD GetCurrentDirectory(
> 
>     DWORD nBufferLength,    // size, in characters, of directory buffer
>     LPTSTR lpBuffer     // address of buffer for current directory
>    );

Oh, right. My bad.

> 
> Also, perhaps I could use the return value of the function with a buffer 
> size of zero. It would return the size needed, and then I could use alloc() 
> to create the memory space to use. I just don't know what is the "best" way 
> to handle functions like this, and I may have been using the PChar() cast 
> improperly.

I usually use String type, Length() and SetLength() since I don't want to be
bothered by memory allocation. Delphi runtime will do it automatically. e.g.

  SetLength(MyStr, MAX_PATH-1);
  SetLength(MyStr, GetCurrentDirectory(MAX_PATH-1, PChar(MyStr)));

Note that if GetCurrentDirectory() fails, calling GetLastError() would not
retrieve the error code for GetCurrentDirectory(), but the API function(s)
used by SetLength().
0
JJ
8/27/2016 11:09:56 AM
P E Schoen schrieb:
> "JJ"  wrote in message news:1rco3b1rvcu6.1k5cho7qohwwc$.dlg@40tude.net...
> 
> [snip]
>> GetCurrentDirectory() arguments are: the buffer pointer, then the 
>> buffer length. Not the buffer length, then the buffer pointer.
> 
>  From the Delphi help:
> 
> DWORD GetCurrentDirectory(
> 
>    DWORD nBufferLength,    // size, in characters, of directory buffer
>    LPTSTR lpBuffer     // address of buffer for current directory
>   );

According to MSDN I'd think that the length comes first.

> Also, perhaps I could use the return value of the function with a buffer 
> size of zero. It would return the size needed, and then I could use 
> alloc() to create the memory space to use. I just don't know what is the 
> "best" way to handle functions like this, and I may have been using the 
> PChar() cast improperly.

Most API functions, dealing with variable amount of data, return the 
required size when called with a Nil pointer. MSDN also claims that the 
*required* size is returned by GetCurrentDirectory, if the string 
doesn't fit into the buffer, but I'd not rely on that. At least the call 
succeeded if the returned (actual) size is lower than the allocated size.

All non-empty dynamic Strings are automatically terminated by an 
invisible and not counted zero char. I.e. you can/should SetLength(str, 
charcount) to extend or shrink a dynamic String to the required length, 
with a zero byte at its end as expected by API (C) funtions.

DoDi
0
Hans
8/27/2016 11:12:42 AM
JJ schrieb:

> String...
> String type is a combination between ShortString and PChar (null terminated
> string). The String storage layout is like ShortString except that the
> string length is 16-bit instead of 8-bit. The data for "ABC" would be like
> below.
> 
>   03,00,41,42,43,00
> 
> i.e.
> 
>   (* 
>   StringStorage = record
>     StringLength : Word;
>     StringData   : Array [1..n] of Char;
>   end;
>   Where n is variable. i.e. variable field size
>   *)

This is not true for 32 bit Delphi, which uses something close to 
Windows BSTR (OLE compatible). The pointer goes to the first character 
(fully compatible PCHAR), and before the characters the 32 bit size and 
reference count is stored. See managed data types in OH.

When a ShortString is retyped into a dynamic String, the compiler 
usually mocks about many references to str[0], used to set and retrieve 
the used length of the ShortString. The Length and SetLength functions 
handle both string types, so that they allow to change string types 
without further changes to the existing code.

DoDi
0
Hans
8/27/2016 11:41:39 AM
"Hans-Peter Diettrich"  wrote in message 
news:e2dbqcFkv7fU2@mid.individual.net...

> P E Schoen schrieb:

>>  From the Delphi help:
>
>> DWORD GetCurrentDirectory(
>
>>    DWORD nBufferLength,    // size, in characters, of directory buffer
>>    LPTSTR lpBuffer     // address of buffer for current directory

> According to MSDN I'd think that the length comes first.

Yes, as described here:
https://msdn.microsoft.com/en-us/library/windows/desktop/aa364934%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396

> Most API functions, dealing with variable amount of data, return the 
> required size when called with a Nil pointer. MSDN also claims that the 
> *required* size is returned by GetCurrentDirectory, if the string doesn't 
> fit into the buffer, but I'd not rely on that. At least the call succeeded 
> if the returned (actual) size is lower than the allocated size.

> All non-empty dynamic Strings are automatically terminated by an invisible 
> and not counted zero char. I.e. you can/should SetLength(str, charcount) 
> to extend or shrink a dynamic String to the required length, with a zero 
> byte at its end as expected by API (C) funtions.

I found another Delphi function that works with strings. I made the 
following test project:

procedure TfmTestMain.Button1Click(Sender: TObject);
var       curDir, curDir2, curDir3: String;
          PcurDir3: PChar;
begin
  curDir := GetCurrentDir;
  setLength(curDir2, MAX_PATH); //fsDirectory);
  GetCurrentDirectory(length(curDir2), PChar(curDir2) );
  curDir3 := StrPas(PcurDir3);
  setLength(curDir3, MAX_PATH);
  PcurDir3 := @curDir3[1];
  GetCurrentDirectory(length(curDir3), PcurDir3 );
  Label1.Caption := curDir;
  Label2.Caption := curDir2;
  Label3.Caption := curDir3;
end;

This works, but I get a warning that PcurDir3 might not be initialized. I 
think I understand that, since the curDir3 string has not been initialized. 
The StrPas function is not needed. I think I am beginning to understand the 
string and PChar concepts better.

Thanks,

Paul 

0
P
8/27/2016 10:59:38 PM
Reply: