• Strings and PChar (D4 Pro)

    From P E Schoen@21:1/5 to All on Sat Aug 27 05:39:10 2016
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From JJ@21:1/5 to P E Schoen on Sat Aug 27 17:05:22 2016
    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.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From P E Schoen@21:1/5 to All on Sat Aug 27 06:03:01 2016
    "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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From P E Schoen@21:1/5 to All on Sat Aug 27 06:15:53 2016
    "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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From P E Schoen@21:1/5 to P E Schoen on Sat Aug 27 06:28:39 2016
    "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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Hans-Peter Diettrich@21:1/5 to P E Schoen on Sat Aug 27 12:50:09 2016
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From JJ@21:1/5 to P E Schoen on Sat Aug 27 18:00:09 2016
    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;

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From JJ@21:1/5 to P E Schoen on Sat Aug 27 18:09:56 2016
    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().

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Hans-Peter Diettrich@21:1/5 to All on Sat Aug 27 13:41:39 2016
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Hans-Peter Diettrich@21:1/5 to P E Schoen on Sat Aug 27 13:12:42 2016
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From P E Schoen@21:1/5 to Hans-Peter Diettrich on Sat Aug 27 18:59:38 2016
    "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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)