Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-pascal / 2021-06

GetSaveFileNameA limited to 100 characters in default name

14 messages · 3 people · –

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

James Richters

I'm using GetSaveFileNameA() in a windows console program to obtain a save-as directory and file name, I am passing sending it a default file name with:

TFileName.lpstrFile:=Pchar(DefaultFileName);

DefaultFileName is an AnsiString that contains a full path and filename.

This all works fine as long as DefaultFileName is 100 characters or less, if it's 101 or more, then GetSaveFileNameA() never opens a dialog box and just returns False;

Does anyone know where the 100 character limit is coming from?

TFileName.lpstrFile is a PChar, and DefaultFileName is an AnsiString, Neither of which have a length limit that I know of. I am doing a Writeln if Both DefaultFileName and TFileName.lpstrFile and they both match, nothing is being truncated.

James

Reply by email

Alexander Grotewohl
to James Richters

The Windows API doesn't understand what an 'AnsiString' is and cannot resize it dynamically like FreePascal would do as it is used.

You need to use something like

buf: array[0..512] of char;
TFileName.nMaxFile := sizeof(buf);

But then you should also handle the possibility that the dir is greater than 512 and allocate a bigger buffer as needed. So maybe dynamically allocating the space would be better.

Alexander Grotewohl https://dcclost.com ________________________________
Earlier message 34 lines
From: fpc-pascal on behalf of James Richters via fpc-pascal Sent: Friday, June 18, 2021 1:56 PM To: 'FPC-Pascal users discussions' Cc: James Richters Subject: [fpc-pascal] GetSaveFileNameA limited to 100 characters in default name I’m using GetSaveFileNameA() in a windows console program to obtain a save-as directory and file name, I am passing sending it a default file name with: TFileName.lpstrFile:=Pchar(DefaultFileName); DefaultFileName is an AnsiString that contains a full path and filename. This all works fine as long as DefaultFileName is 100 characters or less, if it’s 101 or more, then GetSaveFileNameA() never opens a dialog box and just returns False; Does anyone know where the 100 character limit is coming from? TFileName.lpstrFile is a PChar, and DefaultFileName is an AnsiString, Neither of which have a length limit that I know of… I am doing a Writeln if Both DefaultFileName and TFileName.lpstrFile and they both match, nothing is being truncated. James

Reply by email

James Richters
to Alexander Grotewohl
I'm converting my AnsiString to a Pchar with
TFileName.lpstrFile:=Pchar(DefaultFileName);

But I didn't understand the meaning of
TFileName.nMaxFile:=100; I thought it was the maximum number of files to display or something.. not the maximum number of characters in the file name. which it appears to be.

So now I have:
TFileName.nMaxFile:= Length(DefaultFileName)+1;
TFileName.lpstrFile:=Pchar(DefaultFileName);

I need the +1 for the #0 at the end of the Pchar, and now it works fine, and I can have strings as long as they need to be.

Thank you for the help!

James

Reply by email

Jean SUZINEAU
to James Richters: I'm using GetSaveFileNameA() in a windows console program to obtain a save-as directory…

I'm not familiar with GetSaveFileNameA, but usually for the size of a FileName  buffer I use the constant MAX_PATH which is defined in unit SysUtils.

MAX_PATH itself seems to be an alias from system unit where it is defined accordingly to the operating system (260 on windows, 4096 on Linux, 1024 on BSD, Solaris and Darwin).

Did you have a look at Microsoft's documentation for GetSaveFileNameA and the OPENFILENAMEA structure it takes as parameter ? https://docs.microsoft.com/en-us/windows/win32/api/commdlg/nf-commdlg-getsavefilenamea

https://docs.microsoft.com/en-us/windows/win32/api/commdlg/ns-commdlg-openfilenamea

Particularly, you need to put in |nMaxFile |the size of the buffer pointed to by lpstrFile.

The folowing code works with a 120 characters long filename :

program test_GetSaveFileName;
uses
     SysUtils, CommDlg;
const DefaultFilename= 'Test';
var
    Buffer: array[0..MAX_PATH] of Char; //size MAX_PATH + 1 for
terminating null char
    ofn: TOPENFILENAMEA;
begin
      StrPLCopy( Buffer, DefaultFilename, MAX_PATH);
      ofn.lStructSize:= sizeof(ofn);
      ofn.hInstance:= 0;
      ofn.lpstrFilter:= nil;
      ofn.lpstrCustomFilter:= nil;
      ofn.nMaxCustFilter:= 0;
      ofn.nFilterIndex:= 0;
      ofn.lpstrFile:= Buffer;
      ofn.nMaxFile:= Sizeof( Buffer);
      ofn.lpstrFileTitle:= nil;
      ofn.nMaxFileTitle:= 0;
      ofn.lpstrInitialDir:= nil;
      ofn.lpstrTitle:= 'test GetSaveFileNameA';
      ofn.Flags:= 0;
      ofn.nFileOffset:= 0;
      ofn.nFileExtension:= 0;
      ofn.lpstrDefExt:= nil;
      ofn.lCustData:= 0;
      ofn.lpfnHook:= nil;
      ofn.lpTemplateName:= nil;
      ofn.pvReserved:= nil;
      ofn.dwReserved:= 0;
      ofn.FlagsEx:= 0;
      if GetSaveFileNameA( @ofn)
      then
          WriteLn( ofn.lpstrFile);
end.

Reply by email

Jean SUZINEAU
to James Richters: I'm converting my AnsiString to a Pchar with TFileName.lpstrFile:=Pchar(DefaultFileName);…

I haven't seen your reply.
Le 18/06/2021 à 23:50, James Richters via fpc-pascal a écrit :

Show quoted text 9 lines
So now I have: TFileName.nMaxFile:= Length(DefaultFileName)+1; TFileName.lpstrFile:=Pchar(DefaultFileName); I need the +1 for the #0 at the end of the Pchar, and now it works fine, and I can have strings as long as they need to be.

I think that this way you tell GetSaveFileNameA that it can write Length(DefaultFileName)+1 chars in Pchar(DefaultFileName), but you have only allocated Length(DefaultFileName) chars to DefaultFileName .

It's unlikely, but this way you can end up with the corruption of the byte right  after the memory block DefaultFileName is pointing to ...

Reply by email

James Richters
to Jean SUZINEAU
MAX_PATH itself seems to be an alias from system unit where it is defined

accordingly to the operating system (260 on windows, 4096 on Linux, 1024 on BSD, Solaris and Darwin).

It's unlikely, but this way you can end up with the corruption of the byte

right after the memory block DefaultFileName is pointing to . This got me thinking. my default filename is just a recommended default. but TFileName.lpstrFile is also what returns the filename to use, and the user could use the save-as dialog to add a directory, and change the name of the file to something else..etc. so the file name could be significantly larger than the default.
So I think I'm best off setting TFileName.nMaxFile := Max_Path; then TFileName.lpstrFile will always be able to hold anything the operating system can support.
James

Reply by email

Jean SUZINEAU
to James Richters

It's curious, I'm not completely sure we are reasoning about the same function and record.

You mention TFileName but for me in FPC 3.2.0:

-  TFileName is declared as "TFileName= type string;" in sysutilh.inc.

- OPENFILENAMEA is declared as record in CommDlg.pp line 94
   and TOPENFILENAMEA=OPENFILENAMEA;
   and POPENFILENAMEA=^OPENFILENAMEA;
-function GetSaveFileNameA is imported from Comdlg32.dll in CommDlg.pp
line 595
   function GetSaveFileNameA(_para1:LPOPENFILENAME):WINBOOL; stdcall;
external 'comdlg32' name 'GetSaveFileNameA';

Le 19/06/2021 à 02:51, James Richters via fpc-pascal a écrit :

Show quoted text 5 lines
This got me thinking… my default filename is just a recommended default… but TFileName.lpstrFileis also what returns the filename to use, and the user could use the save-as dialog to add a directory, and change the name of the file to something else..etc… so the file name could be significantly larger than the default…

Yes

Show quoted text 5 lines
So I think I’m best off setting TFileName.nMaxFile:= Max_Path;then TFileName.lpstrFile will always be able to hold anything the operating system can support.

The nMaxFile value include the terminal null character, so it should be Max_Path+1, and it must be the size of the buffer lpstrFile is pointing to.

You shouldn't use "TFileName.lpstrFile:=Pchar(DefaultFileName);" because in this case lpstrFile will point to a buffer of size Length(DefaultFileName)+1 .

And to get the value back from the function, given a variable s: string,
you should do something like s:= StrPas( TFileName.lpstrFile);

From Microsoft documentation:

nMaxFile
The size, in characters, of the buffer pointed to by lpstrFile. The buffer must be large enough to store the path and file name string or strings, including the terminating NULL character. The GetOpenFileName <https://docs.microsoft.com/en-us/windows/desktop/api/commdlg/nf-commdlg-getopenfilenamea> and GetSaveFileName
<https://docs.microsoft.com/en-us/windows/desktop/api/commdlg/nf-commdlg-getsavefilenamea> functions return FALSE if the buffer is too small to contain the file information. The buffer should be at least 256 characters long.

Reply by email

James Richters
to Jean SUZINEAU
It's curious, I'm not completely sure we are reasoning about the same

function and record.
It seems I should not be using TFilename as the variable name. I'm using a variable called TFileName with GetSaveFileNameA() Here is the relevant code.. after I fixed it to use Max_Path

Uses CRT,CRT.Helper,Classes,Sysutils,windows,commdlg,Math,Process;
Var
   TFilename           : TOpenFileNameA;
   DefaultFileName   : AnsiString = '' ;
   OutputFileName   : AnsiString = '' ;
fillchar(TFileName, sizeof(TFileName), 0);
TFileName.lStructSize:=sizeof(TFileName);
TFileName.hwndOwner:=0;
TFileName.nMaxFile:=Max_Path+1;
TFileName.lpstrFile:=Pchar('');
TFileName.Flags := OFN_EXPLORER or OFN_FILEMUSTEXIST or OFN_HIDEREADONLY;
TFileName.lpstrDefExt:='';
TFileName.lpstrTitle:='Save File As:';
TFileName.lpstrFilter:='TAP Files (*.Tap)'+#0+'*.Tap'+#0+'All Files
(*.*)'+#0+'*.*'+#0;
TFileName.lpstrFile:=Pchar(DefaultFileName);
SaveAsResult:=GetSaveFileNameA(@TFilename);
.
.
OutputFileName:= strpas(TFileName.lpstrFile);
.
.
Assign(TapFile,OutputFileName);
ReWrite(TapFile);
.
.

It seems TFileName is already defined and I am re-defining it. TFileName was not my idea.. that is not how I name my variables. It's something I got from an example on how to use GetSaveFileNameA() that I copied and pasted and then adjusted. that is also where TFileName.nMaxFile:=100; came from that caused my original problem.

I don't have a really good understanding of the conventions of putting various letters in front of Variables, like the T.. but I would never put the T there myself.. variables I define never have the T.. so if I wasn't copying an example, I would have done something like:

SaveAsFile : TOpenFileNameA;

James

Reply by email

Jean SUZINEAU
to James Richters

I think you should have a look at this documentation on strings, particularly for PChar :
https://www.freepascal.org/docs-html/ref/refsu9.html

Le 20/06/2021 à 02:09, James Richters a écrit :

DefaultFileName: AnsiString = '' ;

So Length(DefaultFileName) = 0

TFileName.nMaxFile:=Max_Path+1;

This says that lpstrFile points to a buffer of size Max_Path+1.

TFileName.lpstrFile:=Pchar('');

But here you make it point to a buffer of size 1 (a constant with just a null char)

TFileName.Flags:= OFN_EXPLORER or OFN_FILEMUSTEXIST or OFN_HIDEREADONLY;

I think OFN_FILEMUSTEXIST forbid to create new file.

TFileName.lpstrFile:=Pchar(DefaultFileName);

New assignment of lpstrFile, make it point to DefaultFileName, which seems to be of length 0, so a buffer of size 1 (a constant with just a null char at the end).

I think you should at least add the begining of your code : SetLength(
DefaultFileName, Max_Path);
It seems TFileName is already defined and I am re-defining it…TFileName was not my idea.. that is not how I name my variables…

The name is a bit misleading, but as soon as it's a variable of type TOpenFileNameA, it's fine.

Reply by email

James Richters
to Jean SUZINEAU

I have it working better now.
I was having a crash with Code 216
Appearantly

   SaveAsFileName.lpstrFile:= Pchar(DefaulSaveAsFileName);

Just doesn't work the way I want it to. If I try to do the conversion to Pchar that way, I get an error 216 on:

  SaveAsResult:=GetSaveFileNameA(@SaveAsFileName);
I ended up making a buffer
SaveAsFileNameBuffer: array[0..Max_Path+1] of char;
Then putting my default file name in the buffer with:
SaveAsFileNameBuffer:=Pchar(DefaulSaveAsFileName);
Then assigning the buffer with:
SaveAsFileName.lpstrFile:=SaveAsFileNameBuffer;
So even though it looks like that would be the same thing as:
SaveAsFileName.lpstrFile:= Pchar(DefaulSaveAsFileName);    It is not.. and
even though I had  SaveAsFileName.nMaxFile:=Max_Path+1;
SaveAsFileName.lpstrFile:= Pchar(DefaulSaveAsFileName);   still fails with a
216

Here is my complete test program, I'm not positive it's completely correct, but it seems to work better:

Program TestGetSaveFileNameA;
Uses windows, commdlg;
Var
    SaveAsFileName   : TOpenFileNameA;
    SaveAsResult     : Boolean;
    DefaulSaveAsFileName,OutpuSaveAsFileName : Ansistring;
    SaveAsFileNameBuffer: array[0..Max_Path+1] of char;
    MyFile : Text;
Begin
   DefaulSaveAsFileName := 'X:\Something with a really really really long
long long long file name to see if there is still a bug with really long
filenames.tap';
   fillchar(SaveAsFileName, sizeof(SaveAsFileName), 0);
   SaveAsFileName.lStructSize:=sizeof(SaveAsFileName);
   SaveAsFileName.hwndOwner:=0;
   SaveAsFileName.nMaxFile:=Max_Path+1;
//   SaveAsFileName.lpstrFile:=Pchar(DefaulSaveAsFileName);    // This
causes an Error 216
   SaveAsFileNameBuffer:=Pchar(DefaulSaveAsFileName);
   SaveAsFileName.lpstrFile:=SaveAsFileNameBuffer;
   SaveAsFileName.lpstrFilter:='TAP Files (*.tap)'+#0+'*.tap'+#0;
   SaveAsFileName.Flags := OFN_EXPLORER;
   SaveAsFileName.lpstrDefExt:='tap';
   SaveAsFileName.lpstrTitle:='Save File As:';
   Writeln(SaveAsFileName.lpstrFile,' ',SaveAsFileName.nMaxFile);
   SaveAsResult:=GetSaveFileNameA(@SaveAsFileName);
   Writeln(SaveAsResult,'  File Name Selected: ',SaveAsFileName.lpstrFile);
   If SaveAsResult then
      Begin
         OutpuSaveAsFileName:=strpas(SaveAsFileName.lpstrFile);
         Writeln('File Name AnsiString: '+OutpuSaveAsFileName);
         Assign(MyFile,OutpuSaveAsFileName);
         ReWrite(MyFile);
         Writeln(MyFile,'Test');
         Writeln(MyFile,OutpuSaveAsFileName);
         Close(MyFile);
      End;
End.

James

Reply by email

Jean SUZINEAU
to James Richters

Le 21/06/2021 à 03:04, James Richters a écrit :

Show quoted text 17 lines
Var     ... DefaulSaveAsFileName: Ansistring; SaveAsFileNameBuffer: array[0..Max_Path+1] of char;    ... Begin DefaulSaveAsFileName := 'X:\Something with a really really really long long long long file name to see if there is still a bug with really long filenames.tap'; ... SaveAsFileNameBuffer:=Pchar(DefaulSaveAsFileName); ...

This sounds a bit odd to me but it seems to work.
I would prefer to use

   StrPLCopy( SaveAsFileNameBuffer, DefaulSaveAsFileName,
SizeOf(SaveAsFileNameBuffer));
instead of
SaveAsFileNameBuffer:=Pchar(DefaulSaveAsFileName);

Reply by email

James Richters
to Jean SUZINEAU
I would prefer to use StrPLCopy( SaveAsFileNameBuffer, DefaulSaveAsFileName,
SizeOf(SaveAsFileNameBuffer));
instead of   SaveAsFileNameBuffer:=Pchar(DefaulSaveAsFileName);

I'm curious what the difference is between StrPLCopy() and PChar()

I wonder if PChar() was my problem.. maybe I don't need the buffer, maybe I just needed:
StrPLCopy( SaveAsFileName.lpstrFile, DefaulSaveAsFileName, Max_Path+1); ?

James

Reply by email

Alexander Grotewohl
to James Richters

It's best to think of these in two parts. The Windows API part and then the interfacing with Pascal part.

SaveAsFileName.lpstrFile = long pointer to a (C) string filename buffer

This buffer isn't created for you, you provide it and tell Windows it's size.

If you were to just do

StrPLCopy( SaveAsFileName.lpstrFile, DefaulSaveAsFileName, Max_Path+1);

there is no buffer there to receive DefaultSaveAsFileName. (or more correctly the lpstrFile pointer will point to random memory).

So, you first create the buffer, StrPLCopy the default string into it, and then pass it to the Windows API.

On return, it uses that same buffer (Windows doesn't have to create the buffer because you did) and knows the maximum it can place into the buffer (because you provided that information).

Afterwards you can take that buffer (a C-string) and convert it to something Pascal-like again.

This is important because almost everything you do with the Windows API will be some variant of this procedure and you need to be able to repeat this every time you use it without bugs 🙂

Alexander Grotewohl https://dcclost.com ________________________________
Earlier message 21 lines
From: fpc-pascal on behalf of James Richters via fpc-pascal Sent: Monday, June 21, 2021 1:56 PM To: 'FPC-Pascal users discussions' Cc: James Richters ; 'Jean SUZINEAU' Subject: Re: [fpc-pascal] GetSaveFileNameA limited to 100 characters in default name >I would prefer to use > StrPLCopy( SaveAsFileNameBuffer, DefaulSaveAsFileName, SizeOf(SaveAsFileNameBuffer)); >instead of > SaveAsFileNameBuffer:=Pchar(DefaulSaveAsFileName); I'm curious what the difference is between StrPLCopy() and PChar() I wonder if PChar() was my problem.. maybe I don't need the buffer, maybe I just needed: StrPLCopy( SaveAsFileName.lpstrFile, DefaulSaveAsFileName, Max_Path+1); ? James

Reply by email

James Richters
It's best to think of these in two parts. The Windows API part and then the interfacing with Pascal part. SaveAsFileName.lpstrFile = long pointer to a (C) string filename buffer This buffer isn't created for you, you provide it and tell Windows it's size.

Thank you for the explanation, that explains why it crashed without the buffer but was fine with it. For some reason I thought SaveAsFileName.lpstrFile was a Pchar Variable.

James

Reply by email