[C++] - Opening a directory and getting the path of a selected file

Hello all,

I am simply trying to make a C++ dll, which opens a folder and allows user to select one of the files in this directory (via a mouse click), then the path of this file shall be passed to another program (which uses this dll).

I am using Visual Studio Express 2017.
Operating system is windows.

I am total newbie in C++ (but I have good programming experience in C programming language).

"As a first step", I am trying to write a simple C++ program which can perform the functionality mentioned above.

I made a search and found the examples in this page:
http://www.cplusplus.com/forum/general/130611/

But when I tried to use the code from "closed account (j3Rz8vqX)", it throws an exception at the function "GetOpenFileName".
The exception says --> Unhandled exception at 0x75876344 (comdlg32.dll) in Open_folder_select_file.exe: 0xC0000005: Access violation reading location 0xCCCCCCCC.

Can you please help me to solve this issue?

Thanks
Can you post the relevant code?
Sure, this is the code -->

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>
#include <windows.h>

char* myFunction(){
    OPENFILENAME ofn;
    char *FilterSpec ="Object Files(.obj)\0*.obj\0Text Files(.txt)\0.txt\0All Files(.)\0*.*\0";
    char *Title ="Open....";
    char szFileName[MAX_PATH];
    char szFileTitle[MAX_PATH];

    *szFileName = 0; *szFileTitle = 0;

    /* fill in non-variant fields of OPENFILENAME struct. */
    ofn.lStructSize       = sizeof(OPENFILENAME);
    ofn.hwndOwner         = GetFocus();
    ofn.lpstrFilter       = FilterSpec;
    ofn.lpstrCustomFilter = NULL;
    ofn.nMaxCustFilter    = 0;
    ofn.nFilterIndex      = 0;
    ofn.lpstrFile         = szFileName;
    ofn.nMaxFile          = MAX_PATH;
    ofn.lpstrInitialDir   = "."; // Initial directory.
    ofn.lpstrFileTitle    = szFileTitle;
    ofn.nMaxFileTitle     = MAX_PATH;
    ofn.lpstrTitle        = Title;
    ofn.lpstrDefExt   = 0;//I've set to null for demonstration

    ofn.Flags             = OFN_FILEMUSTEXIST|OFN_HIDEREADONLY;

    if (!GetOpenFileName ((LPOPENFILENAME)&ofn))
    {
        return ("NULL"); // Failed or cancelled
    }
    else
    {
      return szFileName;
    }
}

int main(){
    std::cout<<"File location\\name: \n"<<myFunction()<<std::endl;
    return 0;
}
I am way out on windows programming but any chance its a 32 bit toy in a 64 bit program problem? This looks like older win 32 code.
The easiest issue I can spot is that you're returning a pointer to a local variable (szFileName), so that's undefined behavior. You might want to pass szFileName[MAX_PATH] buffer in as a paramater.

C++ also forbids pointing a non-const char* to a string literal, so you should make your first two char* variables be const char*.

You should really compile with warnings enabled.
main.cpp:9:10: warning: address of local variable 'szFileName' returned [-Wreturn-local-addr]
     char szFileName[MAX_PATH];


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>
#include <windows.h>

bool openFileDialog(char szFileName[]){
    OPENFILENAME ofn;
    const char *FilterSpec = "Object Files(.obj)\0*.obj\0Text Files(.txt)\0.txt\0All Files(.)\0*.*\0";
    const char *Title = "Open....";

    char szFileTitle[MAX_PATH] = "";

    *szFileName = 0;

    /* fill in non-variant fields of OPENFILENAME struct. */
    ofn.lStructSize       = sizeof(OPENFILENAME);
    ofn.hwndOwner         = GetFocus();
    ofn.lpstrFilter       = FilterSpec;
    ofn.lpstrCustomFilter = NULL;
    ofn.nMaxCustFilter    = 0;
    ofn.nFilterIndex      = 0;
    ofn.lpstrFile         = szFileName;
    ofn.nMaxFile          = MAX_PATH;
    ofn.lpstrInitialDir   = "."; // Initial directory.
    ofn.lpstrFileTitle    = szFileTitle;
    ofn.nMaxFileTitle     = MAX_PATH;
    ofn.lpstrTitle        = Title;
    ofn.lpstrDefExt       = 0; // I've set to null for demonstration
    ofn.Flags             = OFN_FILEMUSTEXIST|OFN_HIDEREADONLY;

    // false if failed or cancelled
    return GetOpenFileName(&ofn);
}

int main(){
    char openedFileName[MAX_PATH];
    if (openFileDialog(openedFileName))
        std::cout << "File location\\name: " << openedFileName << "\n";
    else
        std::cout << "User cancelled the operation\n";
	
    return 0;
}


g++ -Wall main.cpp -lcomdlg32 -o main
Last edited on
Hello Ganado,

Many thanks for your reply.

I tried your code, and it runniny on my PC, and I also realized that the exception was being thrown because I commented out these 2 lines from the code:
1
2
const char *FilterSpec = "Object Files(.obj)\0*.obj\0Text Files(.txt)\0.txt\0All Files(.)\0*.*\0";
ofn.lpstrFilter = (LPCWSTR)FilterSpec;



I added them back, and no exception is being thrown now.


But sadly, the program output is always
User cancelled the operation


I expected it to open a directory, and allow me to select a file in this directory.

---------------------------------------------------

Side note: Visual studio gives me errors of these type when I try to compile the code:

ofn.lpstrFilter = FilterSpec;
E0513	a value of type "const char *" cannot be assigned to an entity of type "LPCWSTR"


I fix it by explicit casting the const char* to LPCWSTR
ofn.lpstrFilter = (LPCWSTR)FilterSpec;
If you have Unicode enabled (you do), then c-style casting a char* to a wchar* might be part of the problem.
I can't check right now, but try changing all your chars to TCHARs and add _T("text") around all your string literals.

Shouldn't need to do dubious casting.

Also, for the GetOpenFileName call, you can rework the logic to see if what comdlg errors happened.
msdn wrote:
If the user cancels or closes the Open dialog box or an error occurs, the return value is zero. To get extended error information, call the CommDlgExtendedError function, which can return one of the following values.
Last edited on
Many thanks Ganado!! it worked after taking your comments into consideration.

Here are the results:

First Trial:

Visual Studio options --> Character Set: Use Unicode Character Set

Code -->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
OPENFILENAME ofn;
DWORD error_value;

using namespace std;

bool openFileDialog(TCHAR szFileName[]) 
{	
	const TCHAR* FilterSpec = (const TCHAR*)"All Files(.)\0*.*\0";
	const TCHAR* Title = (const TCHAR*)"Open";

	const TCHAR* myDir = (const TCHAR*)"C:\\c_plus_plus_trial";

	TCHAR szFileTitle[MAX_PATH] = {'\0'};

	ZeroMemory(&ofn, sizeof(OPENFILENAME));

	*szFileName = 0;

	/* fill in non-variant fields of OPENFILENAME struct. */
	ofn.lStructSize = sizeof(OPENFILENAME);
	
	ofn.hwndOwner = GetFocus();
	ofn.lpstrFilter = FilterSpec;
	ofn.lpstrCustomFilter = NULL;
	ofn.nMaxCustFilter = 0;
	ofn.nFilterIndex = 0;
	ofn.lpstrFile = szFileName;
	ofn.nMaxFile = MAX_PATH;
	ofn.lpstrInitialDir = myDir ; // Initial directory.
	ofn.lpstrFileTitle = szFileTitle;
	ofn.nMaxFileTitle = MAX_PATH;
	ofn.lpstrTitle = Title;
	//ofn.lpstrDefExt = 0; // I've set to null for demonstration
	ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST;

	// false if failed or cancelled
	if (GetOpenFileName(&ofn) == 1)
	{
		cout << "SUCCESS !!! \n";
		return 1;
	}
	else
	{
		cout << "Failure :( \n";
		error_value = CommDlgExtendedError();
		cout << std::hex << error_value;

		return 0;
	}	
}

int main() 
{
	TCHAR openedFileName[MAX_PATH];

	if (openFileDialog(openedFileName))
		std::cout << "File location\\name: " << openedFileName << "\n";
	else
		std::cout << "User cancelled the operation\n";

	// TODO --> Understand these below 2 lines
	std::cout << "Press ENTER to continue...";
	std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}


Program Output -->
SUCCESS !!!
File location\name: 00EFFB10



Second trial:

Visual Studio options --> Character Set: Use Multi-Byte Character Set

Code -->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
OPENFILENAME ofn;
DWORD error_value;

using namespace std;

bool openFileDialog(TCHAR szFileName[]) 
{	
	const TCHAR* FilterSpec = "All Files(.)\0*.*\0";
	const TCHAR* Title = "Open";

	const TCHAR* myDir = "C:\\c_plus_plus_trial";

	TCHAR szFileTitle[MAX_PATH] = {'\0'};

	ZeroMemory(&ofn, sizeof(OPENFILENAME));

	*szFileName = 0;

	/* fill in non-variant fields of OPENFILENAME struct. */
	ofn.lStructSize = sizeof(OPENFILENAME);
	
	ofn.hwndOwner = GetFocus();
	ofn.lpstrFilter = FilterSpec;
	ofn.lpstrCustomFilter = NULL;
	ofn.nMaxCustFilter = 0;
	ofn.nFilterIndex = 0;
	ofn.lpstrFile = szFileName;
	ofn.nMaxFile = MAX_PATH;
	ofn.lpstrInitialDir = myDir ; // Initial directory.
	ofn.lpstrFileTitle = szFileTitle;
	ofn.nMaxFileTitle = MAX_PATH;
	ofn.lpstrTitle = Title;
	//ofn.lpstrDefExt = 0; // I've set to null for demonstration
	ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST;

	// false if failed or cancelled
	if (GetOpenFileName(&ofn) == 1)
	{
		cout << "SUCCESS !!! \n";
		return 1;
	}
	else
	{
		cout << "Failure :( \n";
		error_value = CommDlgExtendedError();
		cout << std::hex << error_value;

		return 0;
	}	
}

int main() 
{
	TCHAR openedFileName[MAX_PATH];

	if (openFileDialog(openedFileName))
		std::cout << "File location\\name: " << openedFileName << "\n";
	else
		std::cout << "User cancelled the operation\n";

	// TODO --> Understand these below 2 lines
	std::cout << "Press ENTER to continue...";
	std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}


Program Output -->
SUCCESS !!!
File location\name: C:\c_plus_plus_trial\text_1.txt



So it seems that the Unicode option is the reason for misbehavior.
But sadly I don't understand why.
Last edited on
unicde strings and ascii strings won't compare equally so if looking for a specific file, it won't match -- you compare something like "f i l e . t x t" against "file.txt" for example (not exactly, the spaces are really zero null chars). you have to promote the ascii text to unicode then compare them.
Last edited on
You shouldn't even think about not using Unicode these days. First you need to understand the difference between multibyte, unicode and "TCHAR"s, which is either "char" or "wchar_t" in windows.

Yopu should use wchar_t directly always and functions specificcally ended in "W" in win32 API ( there are APIs which ONLY support Unicode ). These are not windows 95 days.
Here is an alternate "canned" routine, that I copied directly from Microsoft's documentation. It creates the standard file open dialog box. I modified it slightly, to be used as as function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
void GetFileNamePath(LPWSTR pszFilePath) {

    HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
    if (SUCCEEDED(hr))
    {
        IFileOpenDialog* pFileOpen;

        // Create the FileOpenDialog object.
        hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL, IID_IFileOpenDialog, reinterpret_cast<void**>(&pFileOpen));

        if (SUCCEEDED(hr))
        {
            // Show the Open dialog box.
            hr = pFileOpen->Show(NULL);

            // Get the file name from the dialog box.
            if (SUCCEEDED(hr))
            {
                IShellItem* pItem;
                hr = pFileOpen->GetResult(&pItem);
                if (SUCCEEDED(hr))
                {
                    LPWSTR pTemp;
                    hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &pTemp);
                    wcscpy_s(pszFilePath, MAX_PATH, pTemp);
                    if (SUCCEEDED(hr))  CoTaskMemFree(pTemp);
                    pItem->Release();
                }
            }
            pFileOpen->Release();
        }
        CoUninitialize();
    }
    return;
}


In your "Include" list, add this:

 
#include <shobjidl.h> 


To call this function, use a code similar to this:

1
2
3
static wchar_t  szFilePath[MAX_PATH];
   ...
GetFileNamePath(szFilePath);


This should give you the functionality that you need. You can name the function anything that you want. Same with the string that you use to hold the file name and path. "MAX_PATH" is a Microsoft defined macro. Currently, the value is 260. If you're curious, here is the MS page where I found the code:

https://docs.microsoft.com/en-us/windows/win32/learnwin32/example--the-open-dialog-box
Many thanks jonnin for the explanation.


@modoran, I followed your advice, and I read a little about Unicode and Multibyte from these 2 links:

https://www.joelonsoftware.com/2003/10/08/the-absolute-minimum-every-software-developer-absolutely-positively-must-know-about-unicode-and-character-sets-no-excuses/

https://stackoverflow.com/a/402918/4145697

They were very useful.

Just to confirm, to make a string use wide char, I just put an 'L' prefix before it, am I correct?
for example const wchar_t myDir = L"C:\\c_plus_plus_trial"


@anachronon, many thanks for the code and the link, I shall try it today.
L is NOT unicode, its wide (16 bits) char type conversion.
You need to read the material a few times to get this down. Its very confusing because a bunch of different solutions to the problem were produced in short order between the ascii or 1-byte code days and the unicode days. I am a little rusty on this (I am not doing anything major in c++ now); L may produce valid unicode strings but I think the behavior of L depends on your compiler settings (?) or locale settings(?) somewhat. See what you can make of https://stackoverflow.com/questions/6384118/what-does-the-l-in-front-a-string-mean-in-c

You shouldn't even think about not using Unicode these days.
I mostly agree, esp for UIs, but...
-- there is nothing wrong with using ascii when it is appropriate. It would be a big mess to write my personal work tools, mostly 20-50 c++ lines xml crawlers, for unicode when the files are in ascii char set format. It is much harder to work with, and there are times and places where it isn't helpful to go there.
Last edited on
Win32 API has Windows specific functions for file system management. With C++17 there is the <filesystem> library that works with any OS.
https://en.cppreference.com/w/cpp/filesystem
Thanks Furry Guy for this useful link.

But I was only able to find a way to get a list of the files in a directory, using the <filesystem> library.

But is there a way using the <filesystem> library to open the directory in a new window, and allow the user to select a file in this directory (using mouse select), and then a path to this file would be returned?

Thank you.
But is there a way using the <filesystem> library to open the directory in a new window, and allow the user to select a file in this directory (using mouse select), and then a path to this file would be returned?


No. Assuming you're on Windows:
https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/bb776913(v=vs.85)

But is there a way using the <filesystem> library to open the directory in a new window, and allow the user to select a file in this directory (using mouse select), and then a path to this file would be returned?


The "canned" function that I posted above, should do just that. Don't forget to add the #include item that I mentioned at the bottom. Notes on calling the function are also described at the bottom. Once you have called that function, the string variable "szFilePath[]" will contain the complete path to the file.
Topic archived. No new replies allowed.