Windows API: Unicode variable metamorphasis - c++

I created a function that reads from a file, and fills a data structure based on that file. The function works well alone, so I decided to build an GUI around its function. I have a function that calls the Windows GUI and returns a LPOPENFILENAME structure, to which I copy the LPOPENFILENAME.lpstrFile over to a LPCWSTR.
Next, I initialize a wstring to hold the LPCWSTR value. Here's where things get a bit strange.
LPCWSTR File = FileOpenOBJ(hWnd, hInst).lpstrFile;
MessageBox(hWnd, File, L"Warning 1: File/lpstrFile assignment ", MB_ICONEXCLAMATION | MB_OK);
wstring Fileformatting(File);
Which does a runtime display of something like this:
Which is fine, since LPCWSTR File now holds the value from the LPOPENFILENAME structure, but when a wstring is initialized with the value of File, both variables seemingly go out of range or pull from a pointer location that is no longer accurate and their values appear to be something like:
The goal is to hold the file name in a string so that I can (PHP-like) escape the "\" characters to insert into my function. As such:
C:\Users\InfinityMachine\Documents\Test.obj -> C:\\Users\\InfinityMachine\\Documents\\Test.obj
In which I:
while (Fileformatting.find(L"\\") != wstring::npos){
Fileformatting.replace(Fileformatting.find(L"\\"), 2, L"\\\\");
}
How can I get the correct value of LPCWSTR File into wstring Fileformatting?

You have probably initialized lpstrFile to a variable that is local to the FileOpenOBJ function, and when that function returns you get undefined behavior when you try to use it. It appears to work the first time because the stack hasn't been exercised yet, but that changes when you make the call to MessageBox.
Moving the initialization of the wstring would mask the problem but wouldn't fix it, you're still relying on undefined behavior. The fix would be to have the structure contain a pointer that doesn't go out of scope.

In addition to what others have said about dangling pointers and such, for this kind of situation, you are better off simply having FileOpenOBJ() return a wstring instead of a copy of the original OPENFILENAME struct:
wstring FileOpenOBJ(HWND hWnd, HINSTANCE hInst)
{
WCHAR szFileName[MAX_PATH+1] = {0};
OPENFILENAME ofn = {0};
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = hWnd;
ofn.hInstance = hInst;
ofn.lpstrFile = szFileName;
ofn.nMaxFile = MAX_PATH;
...
GetOpenFileName(&ofn);
return szFileName;
}
wstring File = FileOpenOBJ(hWnd, hInst);
MessageBoxW(hWnd, File.c_str(), L"Warning 1: File/lpstrFile assignment ", MB_ICONEXCLAMATION | MB_OK);
If you need to return other values from the OPENFILENAME, you should define your own struct to hold what you need, for example:
struct FileInfo
{
wstring FullPath;
wstring FileName;
wstring Ext;
int FilterIndex;
...
};
FileInfo FileOpenOBJ(HWND hWnd, HINSTANCE hInst)
{
WCHAR szFileName[MAX_PATH+1] = {0};
OPENFILENAME ofn = {0};
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = hWnd;
ofn.hInstance = hInst;
ofn.lpstrFile = szFileName;
ofn.nMaxFile = MAX_PATH;
...
GetOpenFileName(&ofn);
FileInfo fi;
fi.FullPath = szFileName;
fi.FileName = &szFileName[ofn.nFileOffset];
if (ofn.nFileExtension != 0)
fi.Ext = &szFileName[ofn.nFileExtension];
fi.FilterIndex = ofn.nFilterIndex;
...
return fi;
}
FileInfo fi = FileOpenOBJ(hWnd, hInst);
MessageBoxW(hWnd, fi.FullPath.c_str(), L"Warning 1: File/lpstrFile assignment ", MB_ICONEXCLAMATION | MB_OK);
Don't expose direct access to the OPENFILENAME struct. It should be left as a private implementation detail of FileOpenOBJ()'s internal logic.

Related

Call to GetOpenFileNameA causes common dialog error 2

I'm trying to open a dialog box using GetOpenFileNameA. However, the dialog does not open. Instead, I get a nice CommDlgError 2. Searching Google and StackOverflow for this error did not produce any helpful results.
Confusingly, this code works on a school computer that also uses Visual Studio, albeit a different version.
Note: All variables not declared in this block of code are "global" variables that are only accessible within the main code module.
void GetInputFile()
{
char szFileNameIN[MAX_PATH];
char szFileNameOUT[MAX_PATH];
// get the input file name
OPENFILENAME ofn;
ZeroMemory(&fInputPath, sizeof(fInputPath));
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = NULL;
ofn.lpstrFilter = LPWSTR("Any File\0*.*\0");
ofn.lpstrFile = LPWSTR(fInputPath);
ofn.nMaxFile = MAX_PATH;
ofn.lpstrFileTitle = LPWSTR(szFileNameIN);
ofn.nMaxFileTitle = MAX_PATH;
ofn.lpstrTitle = LPWSTR("Select an input File");
ofn.Flags = OFN_DONTADDTORECENT | OFN_FILEMUSTEXIST;
if (GetOpenFileNameA(LPOPENFILENAMEA(&ofn))) // user selected an input file
{
}
else {
// Get error
TCHAR error[MAX_LOADSTRING];
wsprintf(error,TEXT("%i"),CommDlgExtendedError());
MessageBox(NULL,error,TEXT("ERROR"),MB_OK);
}
}
Those awful (LPWSTR) casts tell me that you are compiling with UNICODE defined, so the OPENFILENAME struct you are using is actually OPENFILENAMEW; given that you are using GetOpenFileNameA, you have to use OPENFILENAMEA (or use straight GetOpenFileName with wide strings).
(and remember that, as a rule of thumb, if you have to cast pointers to/from anything different than void * and similar, you are probably doing something wrong; adding pointer casts will only silence the compiler, not make the error go away)
You are using the TCHAR version of OPENFILENAME. Since you are assigning Unicode string pointers to its fields, that means your project is being compiled with UNICODE defined, so TCHAR maps to wchar_t and OPENFILENAME maps to OPENFILENAMEW. But you are using ANSI character buffers and the ANSI version of GetOpenFileName(), and using incorrect type-casts all over the place.
So get rid of all the type-casts, and then either:
use proper TCHAR types and APIs for everything:
void GetInputFile()
{
TCHAR szFileNameIN[MAX_PATH];
TCHAR szFileNameOUT[MAX_PATH];
// get the input file name
OPENFILENAME ofn;
ZeroMemory(&fInputPath, sizeof(fInputPath));
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = NULL;
ofn.lpstrFilter = TEXT("Any File\0*.*\0");
ofn.lpstrFile = fInputPath; // must be TCHAR[]...
ofn.nMaxFile = MAX_PATH;
ofn.lpstrFileTitle = szFileNameIN;
ofn.nMaxFileTitle = MAX_PATH;
ofn.lpstrTitle = TEXT("Select an input File");
ofn.Flags = OFN_DONTADDTORECENT | OFN_FILEMUSTEXIST;
if (GetOpenFileName(&ofn)) // user selected an input file
{
}
else
{
// Get error
TCHAR error[MAX_LOADSTRING];
wsprintf(error, TEXT("%i"), CommDlgExtendedError());
MessageBox(NULL, error, TEXT("ERROR"), MB_OK);
}
}
use proper CHAR/WCHAR types and APIs for everything:
void GetInputFile()
{
WCHAR szFileNameIN[MAX_PATH];
WCHAR szFileNameOUT[MAX_PATH];
// get the input file name
OPENFILENAMEW ofn;
ZeroMemory(&fInputPath, sizeof(fInputPath));
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = NULL;
ofn.lpstrFilter = L"Any File\0*.*\0";
ofn.lpstrFile = fInputPath; // must be WCHAR[]...
ofn.nMaxFile = MAX_PATH;
ofn.lpstrFileTitle = szFileNameIN;
ofn.nMaxFileTitle = MAX_PATH;
ofn.lpstrTitle = L"Select an input File";
ofn.Flags = OFN_DONTADDTORECENT | OFN_FILEMUSTEXIST;
if (GetOpenFileNameW(&ofn)) // user selected an input file
{
}
else
{
// Get error
WCHAR error[MAX_LOADSTRING];
wsprintfW(error, L"%i", CommDlgExtendedError());
MessageBoxW(NULL, error, L"ERROR", MB_OK);
}
}
void GetInputFile()
{
CHAR szFileNameIN[MAX_PATH];
CHAR szFileNameOUT[MAX_PATH];
// get the input file name
OPENFILENAMEA ofn;
ZeroMemory(&fInputPath, sizeof(fInputPath));
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = NULL;
ofn.lpstrFilter = "Any File\0*.*\0";
ofn.lpstrFile = fInputPath; // must be CHAR[]...
ofn.nMaxFile = MAX_PATH;
ofn.lpstrFileTitle = szFileNameIN;
ofn.nMaxFileTitle = MAX_PATH;
ofn.lpstrTitle = "Select an input File";
ofn.Flags = OFN_DONTADDTORECENT | OFN_FILEMUSTEXIST;
if (GetOpenFileNameA(&ofn)) // user selected an input file
{
}
else
{
// Get error
CHAR error[MAX_LOADSTRING];
wsprintfA(error, "%i", CommDlgExtendedError());
MessageBoxA(NULL, error, "ERROR", MB_OK);
}
}

OpenFileDialog class not working properly in c++

I want to show the file name that is opened by OpenFileDialog but it sends the wrong text into title bar. I hace changed the character set of the project but it did not help. here is my code :
OpenFileDialog .h :
class OpenFileDialog
{
public:
OpenFileDialog(){};
void CreateOpenFileDialog(HWND hWnd, LPCWSTR Title, LPCWSTR InitialDirectory, LPCWSTR Filter, int FilterIndex);
~OpenFileDialog(){};
LPCWSTR result=L"";
};
OpenFileDialog .cpp :
void OpenFileDialog::CreateOpenFileDialog(HWND hWnd, LPCWSTR Title, LPCWSTR InitialDirectory, LPCWSTR Filter, int FilterIndex)
{
OPENFILENAME ofn;
TCHAR szFile[MAX_PATH];
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.lpstrFile = szFile;
ofn.lpstrFile[0] = '\0';
ofn.hwndOwner = hWnd;
ofn.nMaxFile = sizeof(szFile);
ofn.lpstrFilter = Filter;
ofn.nFilterIndex = FilterIndex;
ofn.lpstrTitle = Title;
ofn.lpstrInitialDir = InitialDirectory;
ofn.lpstrFileTitle = NULL;
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
if (GetOpenFileName(&ofn))
{
result = ofn.lpstrFile;
}
else
{
result = L"Empty";
}
}
and in windows procedure in WM_COMMAND :
case WM_COMMAND:
{
if (LOWORD(wParam) == ID_FILE_OPEN)
{
OpenFileDialog ofd;
ofd.CreateOpenFileDialog(hwnd, L"Test", L"C:\\", L"All files(*.*)\0*.*\0TextFiles(*.txt)\0*.txt\0", 2);
SetWindowText(hwnd, ofd.result);
}
break;
}
thanks alot.
In your function CreateOpenFileDialog(), the buffer for storing the file name is a local array szFile[MAX_PATH]. You initialize the lpstrFile = szFile in the ofn structure, making sure that the GetOpenFileName() puts the result of user entry at the right place.
The problem is that as soon as you return from CreateOpenFileDialog(), its local variables are destroyed, including the buffer containing the file name. Hence, the resultpointer that you've set with result = ofn.lpstrFile; then points to an invalid memory location.
You can solve this by allocating the buffer directly in result in the OpenFileDialog constructor (or making it an array), and using this pointer directly with ofn.lpstrFile = buffer;

Handle multiple file selection

I have the following code:
void OpenJpgFile(HWND hWnd)
{
OPENFILENAME ofn;
wchar_t szFileName[17*MAX_PATH] = L"";
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = hWnd;
ofn.lpstrTitle = L"Selecione as fotos que deseja adicionar...";
ofn.lpstrFilter = L"Arquivos JPEG (*.jpg)\0*.jpg\0Todos os Arquivos (*.*)\0*.*\0";
ofn.lpstrFile = szFileName;
ofn.nMaxFile = MAX_PATH;
ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_ALLOWMULTISELECT;
ofn.lpstrDefExt = L"jpg";
if(GetOpenFileName(&ofn))
{
//Test
MessageBox(hWnd,ofn.lpstrFileTitle,L"TESTE",NULL);
if(gpBitmap)
{
delete gpBitmap;
}
gpBitmap = new Bitmap(szFileName);
InvalidateRect(hWnd, NULL, TRUE);
UpdateWindow(hWnd);
}
}
What I wanted to know is how can I get the name of all files the user has selected...
All I can get is the path to the folder... is there an array that keeps the file names?
Per the documentation of OFN_ALLOWMULTISELECT when OFN_EXPLORER is also specified, lpstrFile will contain the directory followed by the names of the files, separated by null characters, and terminated with two adjacent null characters. When you call new Bitmap(szFileName) you are treating szFileName incorrectly - that is you pass it to a constructor that expects a standard single-null terminated string. Instead you have to process it more carefully to read past the initial null, and probably recreate your full file paths.
This string format happens to be the same format as used for REG_MULTI_SZ so, aside from not having to worry about missing final terminators, advice related to handling values of type REG_MULTI_SZ can help you here.
Note that to be fully general, you may also have to handle cases that require a larger buffer. See GetOpenFileName() with OFN_ALLOWMULTISELECT flag set for another angle on that

Save/Open Common Dialog boxes in win32 without MFC

How do you create the deafault Save/Open dialog boxes using pure unmanaged Win32 API ?
Following the guide here, the following code is executed when WM_CREATE message is handled in the message loop of the main window:
Ive included <Commdlg.h> also.
OPENFILENAMEA ofn;
char Buffer[300];
fill(Buffer, Buffer + 300, '\0');
ofn.lStructSize = sizeof(OPENFILENAMEA);
ofn.hwndOwner = hWnd;
ofn.lpstrFile = Buffer;
ofn.nMaxFile = 300;
ofn.Flags = OFN_EXPLORER;
ofn.lpstrFilter = NULL;
ofn.lpstrCustomFilter = NULL;
ofn.nFilterIndex = 0;
ofn.lpstrFileTitle = NULL;
ofn.lpstrInitialDir = NULL;
ofn.lpstrTitle = NULL;
out << GetOpenFileNameA(&ofn) << endl;
out << Buffer << (int)CommDlgExtendedError();
However, this code gives NO output whatsoever. Help?!
the following code is executed when WM_CREATE message is handled
Look in the Output window and observe the first-chance exception notification for 0xc0000005, an AccessViolation exception. There's a backstop in the Wow64 emulator that swallows exceptions while WM_CREATE is being dispatched.
The exception is caused by not fully initializing the OPENFILENAMEA structure. Quick fix:
OPENFILENAMEA ofn = {0};
And favor displaying the dialog before calling ShowWindow() instead of the WM_CREATE message handler.
The overall idea is right, but if you are passing the handle of the window you are creating as the owner, then it is not going to be initialized yet.
For diagnostics, consider creating variables to store the API function return values and examining them in the debugger.
It is also more convenient and less error-prone to initialize the structure to zero, instead of explictely zeroing out unneeded members, like this:
OPENFILENAME ofn = { 0 };
GetOpenFileName blocks (for a while), and then returns either TRUE if the dialog was closed by 'OK', or FALSE if it was cancelled.
The actual result (the directory/file path) can be read from the OPENFILENAME structure.
from https://learn.microsoft.com/en-us/windows/win32/dlgbox/using-common-dialog-boxes#opening-a-file we get a utf-16 version of this, with some small changes of mine:
OPENFILENAME ofn = { 0 }; // common dialog box structure
WCHAR szFile[260]; // buffer for file name
HWND hwnd; // owner window
HANDLE hf; // file handle
// Initialize OPENFILENAME
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = hwnd;
ofn.lpstrFile = szFile;
// Set lpstrFile[0] to '\0' so that GetOpenFileName does not
// use the contents of szFile to initialize itself.
ofn.lpstrFile[0] = '\0';
ofn.nMaxFile = sizeof(szFile);
ofn.lpstrFilter = "All\0*.*\0Text\0*.TXT\0";
ofn.nFilterIndex = 1;
ofn.lpstrFileTitle = NULL;
ofn.nMaxFileTitle = 0;
ofn.lpstrInitialDir = NULL;
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
// Display the Open dialog box.
if (GetOpenFileName(&ofn)==TRUE)
hf = CreateFile(ofn.lpstrFile,
GENERIC_READ,
0,
(LPSECURITY_ATTRIBUTES) NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
(HANDLE) NULL);

Very strange GetOpenFileName problem

I seem to be having a very strange problem with GetOpenFileName.
It errors for no apparent reason, however, if I call CommDlgExtendedError() in the error check, the error never happens in the first place.
Here is my code:
#include <windows.h>
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
OPENFILENAME fm;
char flnm[MAX_PATH];
ZeroMemory(&fm, sizeof(fm));
fm.lStructSize = sizeof(OPENFILENAME);
fm.hwndOwner = NULL;
fm.lpstrFilter = "Text Files (*.txt)\0*.txt\0";
fm.lpstrFile = flnm;
fm.nMaxFile = MAX_PATH;
fm.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
fm.lpstrDefExt = "";
if(!GetOpenFileNameA(&fm))
{
MessageBoxA(NULL, "failed! :(", NULL, NULL);
}
return 0;
}
What's shown? "failed! :("
If I remove this check, I do see a file dialog. However, it doesn't work, and the filename box is pre-filled with random junk.
If I change to:
if(!GetOpenFileNameA(&fm))
{
DWORD dwErr = CommDlgExtendedError();
MessageBoxA(NULL, "failed! :(", NULL, NULL);
}
"failed! :(" is NOT shown. The file dialog shows and performs without issue.
What is going on!?!?
OPENFILENAME fm;
char flnm[MAX_PATH]; // nobody initialized me ...
ZeroMemory(&fm, sizeof(fm));
fm.lStructSize = sizeof(OPENFILENAME);
fm.hwndOwner = NULL;
fm.lpstrFilter = "Text Files (*.txt)\0*.txt\0";
fm.lpstrFile = flnm; // ... who knows what I am?
fm.nMaxFile = MAX_PATH;
fm.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
fm.lpstrDefExt = "";
if(!GetOpenFileNameA(&fm))
{
MessageBoxA(NULL, "failed! :(", NULL, NULL);
}
The documentation for lpstrFile states:
The file name used to initialize the File Name edit control. The first character of this buffer must be NULL if initialization is not necessary. When the GetOpenFileName or GetSaveFileName function returns successfully, this buffer contains the drive designator, path, file name, and extension of the selected file.
You are not initializing flnm and therein lies the problem. You can solve the problem by writing flnm[0] = '\0' before you call GetOpenFileName.