openfilename

General FreeBASIC programming questions.
jevans4949
Posts: 1186
Joined: May 08, 2006 21:58
Location: Crewe, England

openfilename

Post by jevans4949 »

I wrote this routine some while ago and it works in 32-bit mode.Problem is Micosoft made Getopenfilename and getsavefilename redundant from Windows Vista, so as far as I can see there is no 64-bit version. The suggested method is to use the Common Item Dialog. The example on the Microsoft website is in C++, and uses additional functions. My question is, has anybody solved this problem, and have they a lump of Freebasic code they can post here?

Code: Select all

#Include once "windows.bi"
#Include Once "win\commdlg.bi"
#Include Once "win/commctrl.bi"
'***********************************************************************
' Call GetOpenFileName or GetSaveFileName for FB Console Program
'***********************************************************************
Function getfname (ownerwindow As hwnd, _ 
		ByRef iotype As UByte, _ 'I, O, U or A
		ByRef dialogtitle As String, _
		ByRef fname As String, 		_ 'returned full path filename
		ByRef fnpos As Integer,		_ 'returned pos of filename in above
		ByRef fxpos As Integer, 	_ 'returned pos of extension in above
		Byref ffilter As String, 		_ 'File filter elements sepeated by backslash
		ByRef fstartdir As String,	_ 'starting directory
		byRef fdefext As String		_ 'default extension
		) As Integer
		
	Dim ofn As OPENFILENAME
	Dim freturn As String *MAX_PATH+1 
	Dim i As Integer
	Dim l As Integer = Len(ffilter)
	Dim As Short Ptr t, s
	' Pass user's filename if supplied
	freturn = fname
	' The file filter passed contains file types and relevant descriptions in pairs,
	' seperated by backslash, and terminated by 2 backslashes. We copy this,
	' change this to a sequence of null-terminated strings, as required by the API.
	ofn.lpstrfilter = Callocate(l+3,SizeOf(String))
	s = CPtr(Short Ptr,@ffilter) 
	t = CPtr(Short Ptr,ofn.lpstrFilter)
	While *s<>0
		If *s = Asc("\") Then
			*t = 0
		Else
			*t = *s
		EndIf
		s+=1
		t+=1
	Wend
	'Set up openfilename parameter block	
    ofn.lStructSize = Sizeof(OPENFILENAME)
    ofn.lpstrFile = @freturn
    ofn.nMaxFile = Sizeof(freturn)
    If Len(fstartdir)>0 Then
    	ofn.lpstrInitialDir = @fstartdir
    EndIf
    If Len(fdefext)>0 Then
    	ofn.lpstrdefext = @fdefext
    EndIf
    ofn.lpstrTitle = @dialogtitle
    Select Case iotype
    	Case Asc("I")
    		ofn.flags = OFN_FILEMUSTEXIST Or OFN_PATHMUSTEXIST 
    	Case Asc("O")
    		ofn.flags = OFN_OVERWRITEPROMPT
    	Case Asc("U"),Asc("A")
    		ofn.flags = OFN_CREATEPROMPT
    End Select
    ' Call suitable procedure
    Select Case(iotype)
    	Case Asc("O")
    		i = GetSavefilename(@ofn)
    	Case Asc("I"),asc("U"),Asc("A") 
    		i = GetOpenFileName(@ofn)
    	Case Else
    		messagebox(ownerwindow,"Invalid I/O Type","PROGRAM ERROR",MB_OK)
    		i = 0
    End Select
	'check results and set return
	If i<>0 Then
		fname = freturn
		fnpos = ofn.nFileOffset
		fxpos = ofn.nFileExtension
	Else
		fname = ""
		fnpos = 0
		fxpos = 0
	EndIf
	Function = i
End Function
srvaldez
Posts: 3373
Joined: Sep 25, 2005 21:54

Re: openfilename

Post by srvaldez »

hello jevans4949
have a look at this thread, maybe that's what you are hunting for viewtopic.php?f=6&t=27723
jj2007
Posts: 2326
Joined: Oct 23, 2016 15:28
Location: Roma, Italia
Contact:

Re: openfilename

Post by jj2007 »

jevans4949 wrote:Problem is Micosoft made Getopenfilename and getsavefilename redundant from Windows Vista, so as far as I can see there is no 64-bit version.
64-bit OpenFileName
Imortis
Moderator
Posts: 1923
Joined: Jun 02, 2005 15:10
Location: USA
Contact:

Re: openfilename

Post by Imortis »

jj2007 wrote:
jevans4949 wrote:Problem is Micosoft made Getopenfilename and getsavefilename redundant from Windows Vista, so as far as I can see there is no 64-bit version.
64-bit OpenFileName
While this is probably fantastically useful, it is not useful to the original poster as it does not answer the question he asked. If this was asked on the MASM forum you linked to it would have been quite appreciated, I am sure.
jj2007
Posts: 2326
Joined: Oct 23, 2016 15:28
Location: Roma, Italia
Contact:

Re: openfilename

Post by jj2007 »

Imortis,
OP thought that M$ had abandoned OpenFileName in 64-bit land. I just demonstrated that this is not the case.
jevans4949
Posts: 1186
Joined: May 08, 2006 21:58
Location: Crewe, England

Re: openfilename

Post by jevans4949 »

Thanks forthe information. Have not yet had time to check it out. I can understand ASM.
Imortis
Moderator
Posts: 1923
Joined: Jun 02, 2005 15:10
Location: USA
Contact:

Re: openfilename

Post by Imortis »

jj2007 wrote:Imortis,
OP thought that M$ had abandoned OpenFileName in 64-bit land. I just demonstrated that this is not the case.
I am well aware. It is lucky that the user in question understands ASM, but if anyone else has the same question, they may not have the same skillset.
Code posted in the freeBASIC forum, really should be freeBASIC code. And while you did not post that code "in" the forum itself, you have linked to it, which feels a bit spam-y. Not to mention the implication that FB is incapable of handling this problem without resorting to inline ASM or another language entirely...

In the future, please refrain from posting code that is incompatible with the compiler this forum is meant to service.
marcov
Posts: 3455
Joined: Jun 16, 2005 9:45
Location: Netherlands
Contact:

Re: openfilename

Post by marcov »

Imortis wrote:
jj2007 wrote:Imortis,
OP thought that M$ had abandoned OpenFileName in 64-bit land. I just demonstrated that this is not the case.
I am well aware. It is lucky that the user in question understands ASM, but if anyone else has the same question, they may not have the same skillset.
(trust me, a bit of ASM knowledge doesn't help much with that pile of macros)
Josep Roca
Posts: 564
Joined: Sep 27, 2016 18:20
Location: Valencia, Spain

Re: openfilename

Post by Josep Roca »

Although GetOpenFilename also works in 64 bit, it is a pain to use with unicode because of the embeded nulls. I have a wrapper, but needs to use my framework: https://github.com/JoseRoca/WinFBX/blob ... FileDialog

It is easier to use the IFileOpenDialog COM interface.

Single selection example:

Code: Select all

#define UNICODE
#define _WIN32_WINNT &h0602
#INCLUDE ONCE "win/shobjidl.bi"

' ========================================================================================
' Displays the File Open Dialog
' The returned pointer must be freed with CoTaskMemFree
' ========================================================================================
FUNCTION AfxIFileOpenDialog (BYVAL hwndOwner AS HWND, BYVAL sigdnName AS SIGDN = SIGDN_FILESYSPATH) AS WSTRING PTR

   ' // Create an instance of the FileOpenDialog interface
   DIM hr AS LONG
   DIM pofd AS IFileOpenDialog PTR
   hr = CoCreateInstance(@CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, @IID_IFileOpenDialog, @pofd)
   IF pofd = NULL THEN RETURN NULL

   ' // Set the file types
   DIM rgFileTypes(1 TO 3) AS COMDLG_FILTERSPEC
   rgFileTypes(1).pszName = @WSTR("FB code files")
   rgFileTypes(2).pszName = @WSTR("Executable files")
   rgFileTypes(3).pszName = @WSTR("All files")
   rgFileTypes(1).pszSpec = @WSTR("*.bas;*.inc;*.bi")
   rgFileTypes(2).pszSpec = @WSTR("*.exe;*.dll")
   rgFileTypes(3).pszSpec = @WSTR("*.*")
   pofd->lpVtbl->SetFileTypes(pofd, 3, @rgFileTypes(1))

   ' // Set the title of the dialog
   hr = pofd->lpVtbl->SetTitle(pofd, "A Single-Selection Dialog")

   ' // Set the folder
   DIM pFolder AS IShellItem PTR
   SHCreateItemFromParsingName (CURDIR, NULL, @IID_IShellItem, @pFolder)
   IF pFolder THEN
      pofd->lpVtbl->SetFolder(pofd, pFolder)
      pFolder->lpVtbl->Release(pFolder)
   END IF

   ' // Display the dialog
   hr = pofd->lpVtbl->Show(pofd, hwndOwner)

   ' // Get the result
   DIM pItem AS IShellItem PTR
   DIM pwszName AS WSTRING PTR
   IF SUCCEEDED(hr) THEN
      hr = pofd->lpVtbl->GetResult(pofd, @pItem)
      IF SUCCEEDED(hr) THEN
         hr = pItem->lpVtbl->GetDisplayName(pItem, sigdnName, @pwszName)
         FUNCTION = pwszName
      END IF
   END IF

   ' // Cleanup
   IF pItem THEN pItem->lpVtbl->Release(pItem)
   IF pofd THEN pofd->lpVtbl->Release(pofd)

END FUNCTION
' ========================================================================================

' // Initialize the COM library
CoInitialize(NULL)

DIM hwnd AS HWND  ' handle of the parent window
DIM pwszName AS WSTRING PTR = AfxIFileOpenDialog(hwnd)
' // Display the name of the selected file
IF pwszName THEN
'   MessageBoxW(hwnd, *pwszName, "IFileOpenDialog", MB_OK)
   ? *pwszName
   CoTaskMemFree(pwszName)
ELSE
   ? "No file selected"
END IF

' // Uninitialize the COM library
CoUninitialize
Multiple selection example:

Code: Select all

#define UNICODE
#define _WIN32_WINNT &h0602
#INCLUDE ONCE "win/shobjidl.bi"

' ========================================================================================
' Displays the File Open Dialog (multiple selection)
' Returns a pointer to the IShellItemArray collection.
' ========================================================================================
FUNCTION AfxIFileOpenDialog2 (BYVAL hwndOwner AS HWND, BYVAL sigdnName AS SIGDN = SIGDN_FILESYSPATH) AS IShellItemArray PTR

   ' // Create an instance of the FileOpenDialog interface
   DIM hr AS LONG
   DIM pofd AS IFileOpenDialog PTR
   hr = CoCreateInstance(@CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, @IID_IFileOpenDialog, @pofd)
   IF pofd = NULL THEN RETURN NULL

   ' // Set the file types
   DIM rgFileTypes(1 TO 3) AS COMDLG_FILTERSPEC
   rgFileTypes(1).pszName = @WSTR("FB code files")
   rgFileTypes(2).pszName = @WSTR("Executable files")
   rgFileTypes(3).pszName = @WSTR("All files")
   rgFileTypes(1).pszSpec = @WSTR("*.bas;*.inc;*.bi")
   rgFileTypes(2).pszSpec = @WSTR("*.exe;*.dll")
   rgFileTypes(3).pszSpec = @WSTR("*.*")
   pofd->lpVtbl->SetFileTypes(pofd, 3, @rgFileTypes(1))

   ' // Set the title of the dialog
   hr = pofd->lpVtbl->SetTitle(pofd, "A Multiple-Selection Dialog")
   ' // Allow multiselection
   hr = pofd->lpVtbl->SetOptions(pofd, FOS_ALLOWMULTISELECT)

   ' // Set the folder
   DIM pFolder AS IShellItem PTR
   SHCreateItemFromParsingName (CURDIR, NULL, @IID_IShellItem, @pFolder)
   IF pFolder THEN
      pofd->lpVtbl->SetFolder(pofd, pFolder)
      pFolder->lpVtbl->Release(pFolder)
   END IF

   ' // Display the dialog
   hr = pofd->lpVtbl->Show(pofd, hwndOwner)

   ' // Get the result
   DIM pItemArray AS IShellItemArray PTR
   IF SUCCEEDED(hr) THEN
      hr = pofd->lpVtbl->GetResults(pofd, @pItemArray)
      FUNCTION = pItemArray
   END IF

   ' // Release the
   IF pofd THEN pofd->lpVtbl->Release(pofd)

END FUNCTION
' ========================================================================================

' // Initialize the COM library
CoInitialize(NULL)

DIM hwnd AS HWND  ' handle of the parent window
DIM pItems AS IShellItemArray PTR = AfxIFileOpenDialog2(hwnd)
IF pItems THEN
   DIM dwItemCount AS LONG
   pItems->lpVtbl->GetCount(pItems, @dwItemCount)
   FOR idx AS LONG = 0 TO dwItemCount - 1
      DIM pItem AS IShellItem PTR
      pItems->lpVtbl->GetItemAt(pItems, idx, @pItem)
      IF pItem THEN
         DIM pwszName AS WSTRING PTR
         pItem->lpVtbl->GetDisplayName(pItem, SIGDN_FILESYSPATH, @pwszName)
         IF pwszName THEN
            ? *pwszName
            CoTaskMemFree(pwszName)
            pwszName = NULL
         END IF
         pItem->lpVtbl->Release(pItem)
         pItem = NULL
      END IF
   NEXT
   pItems->lpVtbl->Release(pItems)
ELSE
   ? "No file selected"
END IF

' // Uninitialize the COM library
CoUninitialize
A bit of COM programming knowledge is useful with WIndows.
jj2007
Posts: 2326
Joined: Oct 23, 2016 15:28
Location: Roma, Italia
Contact:

Re: openfilename

Post by jj2007 »

Josep Roca wrote:It is easier to use the IFileOpenDialog COM interface.
Works like a charm, José, I will have to study that one. Many people fear COM as if it was some dangerous disease, but it's really just badly documented, that's all. Thanks a lot for this example!
Imortis wrote:It is lucky that the user in question understands ASM, but if anyone else has the same question, they may not have the same skillset.
The code I posted over there is extremely simple, and you don't need to know anything about ASM to understand it. And, as I wrote above, I only wanted to contradict the wrong statement "there is no 64-bit GetOpenFileName".

In contrast, I have just wasted half an hour trying to convince FB to do something as simple as creating a string with embedded nullbytes (as often required by Windows). The MASM equivalent is

Code: Select all

mov rax, Chr$("Sources", 0, "*.as?", 0, "Resources", 0, "*.rc", 0, "Includes", 0, "*.inc", 0, "All files", 0, "*.*", 0, 0)
Do I have to explain what it does? In the end, with a lot of trial and error, I achieved this heroic goal by using inline asm and POKING the nulls, see below. Why doesn't FB understand the simple Chr("Test CrLf", 13, 10, "it works!") syntax?

Now the code below is pure FB, it does compile with lots of silly warnings (for perfectly valid standard Windows code) but no error. However, it won't open the dialog. Why? Because for reasons that I am too stupid too understand, the FB compiler chose to use Unicode for some of the strings (you need to know how to use an assembly level debugger to detect that "compiler feature"). Why is that? Nowhere do I define _unicode_ or whatever. Can it be undefined? Can you define _ASCII_?

Code: Select all

#include "Windows.bi"
#Include Once "win\commdlg.bi"

Dim ofn As OPENFILENAME
Dim buffer As string * MAX_PATH
Dim filter As string = "Sourcesx*.basxx"
' ofn.lpstrFilter=Chr("Sources", 0, "*.as?", 0, "Resources", 0, "*.rc", 0, "Includes", 0, "*.inc", 0, "All files", 0, "*.*", 0, 0)
asm
  mov eax, [filter]
  mov byte ptr [eax+7], 0
  mov byte ptr [eax+13], 0
  mov byte ptr [eax+14], 0
end asm

  ofn.lStructSize=sizeof(OPENFILENAME)
  ofn.hwndOwner=GetForegroundWindow()
  ofn.lpstrFilter=@filter
  lstrcpy(@buffer, "Testme.asm")
  ofn.lpstrFile=@buffer
  ofn.nMaxFile=MAX_PATH
  ofn.lpstrTitle=@"Grab a file:"  ' <<<<<<<<<<<<<<<<< UNICODE, WTF - it's NOT defined
  ofn.Flags=OFN_FILEMUSTEXIST or OFN_EXPLORER or OFN_NOCHANGEDIR
  ofn.lpstrDefExt=@"asm"  ' <<<<<<<<<<<<<< UNICODE
  GetOpenFileName(@ofn)
  MessageBox(0, @buffer, "Your choice:", MB_OK or MB_SETFOREGROUND)
Xusinboy Bekchanov
Posts: 783
Joined: Jul 26, 2018 18:28

Re: openfilename

Post by Xusinboy Bekchanov »

In my program, GetOpenFilename works fine. Both in 32-bit and 64-bit. I only used the Unicode version.
https://github.com/XusinboyBekchanov/My ... ialogs.bas
paul doe
Moderator
Posts: 1730
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: openfilename

Post by paul doe »

jj2007 wrote:...
In contrast, I have just wasted half an hour trying to convince FB to do something as simple as creating a string with embedded nullbytes (as often required by Windows). The MASM equivalent is

Code: Select all

mov rax, Chr$("Sources", 0, "*.as?", 0, "Resources", 0, "*.rc", 0, "Includes", 0, "*.inc", 0, "All files", 0, "*.*", 0, 0)
Do I have to explain what it does? In the end, with a lot of trial and error, I achieved this heroic goal by using inline asm and POKING the nulls, see below. Why doesn't FB understand the simple Chr("Test CrLf", 13, 10, "it works!") syntax?
...
Because it understands another syntax:

Code: Select all

#include "Windows.bi"
#Include Once "win\commdlg.bi"

#define chNull chr( 0 )
#define chNullNull chr( 0, 0 )

Dim ofn As OPENFILENAME
Dim buffer As string * MAX_PATH = "testme.bas"
Dim filter As string = _
  "Sources (*.bas)" + chNull + "*.bas" + chNull + _
  "Includes (*.bi)" + chNull + "*.bi" + chNullNull

with ofn
  .lStructSize=sizeof( OPENFILENAME )
  .hwndOwner=GetForegroundWindow()
  .lpstrFilter=strPtr( filter )
  .lpstrFile=strPtr( buffer )
  .nMaxFile=MAX_PATH
  .lpstrTitle=strptr( "Grab a file" )
  .Flags=OFN_FILEMUSTEXIST or OFN_EXPLORER or OFN_NOCHANGEDIR
  .lpstrDefExt=strPtr( "asm" )
end with

GetOpenFileName( @ofn )
MessageBox( 0, strPtr( buffer ), "Your choice:", MB_OK or MB_SETFOREGROUND )
No need to resort to assembler for trivial tasks.
jj2007
Posts: 2326
Joined: Oct 23, 2016 15:28
Location: Roma, Italia
Contact:

Re: openfilename

Post by jj2007 »

paul doe wrote:Because it understands another syntax
That looks nice, Paul - thanks. Did you test it? On my machine, the dialog shows but the filters look a bit strange...

I know you all hate assembly, but if you know how to use OllyDbg, an int 3 is sometimes useful:

Code: Select all

Dim filter As string = _
  "Sources (*.bas)" + chNull + "*.bas" + chNull + _
  "Includes (*.bi)" + chNull + "*.bi" + chNullNull
asm
  int 3
  mov eax, [filter]
end asm
paul doe
Moderator
Posts: 1730
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: openfilename

Post by paul doe »

jj2007 wrote:
paul doe wrote:Because it understands another syntax
That looks nice, Paul - thanks. Did you test it? On my machine, the dialog shows but the filters look a bit strange...
...
No problems for me here:
Image
...
I know you all hate assembly, but if you know how to use OllyDbg, an int 3 is sometimes useful:
...
We don't hate assembly. I don't doubt that it could be marvelously useful, but your first post was reported for being considered spam. Other members might not appreciate the constant linking to another forum, dedicated to another language.
jj2007
Posts: 2326
Joined: Oct 23, 2016 15:28
Location: Roma, Italia
Contact:

Re: openfilename

Post by jj2007 »

paul doe wrote:No problems for me here
One more FB mystery! With your original code (plus the int 3) and three different "toolchains", I always get this:

Image

Under the hood it becomes clear that there are no nullbytes around. Probably I just have a very old version: FreeBASIC Compiler - Version 1.07.1 (2019-09-27), built for win32 (32bit)

Image
Post Reply