After saving file with widows.h save dialog and fstream in other directory I couldn`t open file in program directory.
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = hMainWindow;
ofn.lpstrFilter = L"Text Files (*.txt)\0*.txt\0Word Files (*.rtf)\0*.rtf\0";
ofn.lpstrFile = FileName;
ofn.lpstrFile[0] = '\0';
ofn.nMaxFile = MAX_NAME_STRING;
ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
ofn.lpstrDefExt = L"txt";
ofn.nFilterIndex = 1;
if (GetSaveFileName(&ofn) == TRUE) {
std::fstream file;
file.open(FileName, std::ios::out);
//Write To File
file.clear();
file.close();
}
std::fstream code;
code.open("CodeTemplate.template", std::ios::in);
//ERROR: File Couldn't Open, But File Exists in Program Directory
code.clear();
code.close();
If I Copy That File "CodeTemplate.template" To directroy that have been selected in save dialog file opens.
GetSaveFileName() returns an absolute path to the selected file. So you are passing an absolute path to the output fstream to create the file.
But, you are passing a relative path to the input fstream to open the file. Since that is failing, it is likely that the calling process' current working directory is not pointing where you are expecting.
You even said yourself that you have to "Copy [CodeTemplate.template] To directroy that have been selected in save dialog" to make the file open correctly. Which means that file does not exist in the folder where the working directory is pointing at.
In short, you should never rely on relative paths when creating/opening files. The working directory is volatile, it can change dynamically, and so it may not always point where you are expecting. Always use absolute paths.
Related
I use this code to delete a file from my local system:
bool Delete(const ::CComPtr<IShellItem>& pShellItem, bool permanently)
{
::CComPtr<IFileOperation> pFileOperation;
// initialize the file operation
HRESULT hr = ::CoCreateInstance(CLSID_FileOperation, NULL, CLSCTX_ALL, IID_PPV_ARGS(&pFileOperation));
if (FAILED(hr))
return false;
// don't show any system error message to user, but returns an error code if delete failed. Also show elevation prompt if required
DWORD fileOpFlags = FOF_SILENT | FOF_NO_UI | FOF_NOERRORUI | FOF_NOCONFIRMATION | FOFX_EARLYFAILURE | FOFX_SHOWELEVATIONPROMPT;
// do move file on Recycle Bin instead of deleting it permanently?
if (!permanently)
if (WOSHelper_MSWindows::GetWindowsVersion() >= WOSHelper_MSWindows::IEVersion::IE_Win8)
// Windows 8 introduces the FOFX_RECYCLEONDELETE flag and deprecates the FOF_ALLOWUNDO in favor of FOFX_ADDUNDORECORD
fileOpFlags |= FOFX_ADDUNDORECORD | FOFX_RECYCLEONDELETE;
else
// for Windows 7 and Vista, RecycleOnDelete is the default behavior
fileOpFlags |= FOF_ALLOWUNDO;
// prepare the delete operation flags
hr = pFileOperation->SetOperationFlags(fileOpFlags);
if (FAILED(hr))
return false;
// mark item for deletion
hr = pFileOperation->DeleteItem(pShellItem, nullptr);
if (FAILED(hr))
return false;
// delete the item
hr = pFileOperation->PerformOperations();
if (FAILED(hr))
return false;
return true;
}
On my local computer I also mounted an alias disk using this command:
subst V: "C:\MyData"
Now, using my above alias, I need to move a file to the Recycle Bin (this is done by setting the permanently parameter to false while I call my function) from the following path:
V:\MyProject\MyFileToDel.txt
Doing that, the file is well deleted from its containing folder, but it is never moved to the Recycle Bin. On the other hand, when I try to delete my file from its original path:
C:\MyData\MyProject\MyFileToDel.txt
The file is well deleted from its containing folder AND moved to the Recycle Bin, as expected.
However this is the exact same file in the exact same location in the both cases, the only difference is that one is deleted from an alias, and the other from its complete path.
What should I change to make the function to work in the both cases?
I'm writing a small image viewer application, which use the Shell API to access to the files.
I noticed that, every time I access an image on an external device, like my connected phone, the file is copied in a local cache, named INetCache.
Below is an example of an image path on my external device:
This PC\Apple iPhone\Internal Storage\DCIM\202005__\IMG_2768.HEIC
and the same image path from the PIDL received by the Shell when I try to open it:
C:\Users\Admin\AppData\Local\Microsoft\Windows\INetCache\IE\MD7CNXNX\IMG_2768[1].HEIC
As you can see, the file was copied in the INetCache, and even duplicated. Would it be possible to open the file from my external device without copying it in the INetCache? And if yes, how may I achieve that (if possible using the Shell API)?
UPDATE on 11.08.2022
Below is the code I use to get the PIDL from an IDataObject I receive after a drag&drop operation, which contains the file to open:
std::wstring GetPIDLFromDataObject(IDataObject* pDataObject)
{
std::wstring line = L"PIDL result:\r\n";
if (!pDataObject)
return line;
CComPtr<IShellItemArray> pShellItemArray;
if (FAILED(::SHCreateShellItemArrayFromDataObject(pDataObject, IID_PPV_ARGS(&pShellItemArray))))
return line;
CComPtr<IEnumShellItems> pShellItemEnum;
pShellItemArray->EnumItems(&pShellItemEnum);
if (!pShellItemEnum)
return line;
for (CComPtr<IShellItem> pShellItem; pShellItemEnum->Next(1, &pShellItem, nullptr) == S_OK; pShellItem.Release())
{
CComHeapPtr<wchar_t> spszName;
if (SUCCEEDED(pShellItem->GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, &spszName)))
{
line += spszName;
line += L"\r\n";
}
CComHeapPtr<ITEMIDLIST_ABSOLUTE> pIDL;
if (SUCCEEDED(CComQIPtr<IPersistIDList>(pShellItem)->GetIDList(&pIDL)))
{
UINT cb = ::ILGetSize(pIDL);
BYTE* pb = reinterpret_cast<BYTE*>(static_cast<PIDLIST_ABSOLUTE>(pIDL));
for (UINT i = 0; i < cb; i++)
{
WCHAR hexArray[4];
::StringCchPrintf(hexArray, ARRAYSIZE(hexArray), L" % 02X ", pb[i]);
line += hexArray;
}
line += L"\r\n";
}
}
return line;
}
I'm writing a tool that makes use of gccxml. Basically I'm parsing the output xml file that has been created by gccxml. This works great on my windows machine in visual studio except for a couple of drawbacks. Here's the current state of my project:
cmake_gui gave me a visual studio solution that compiles perfectly (x64 Release). It's set up to create three executables in E:\cmake_builds\GCCXML\bin\Release.
My own C++ tool is located in a different VS solution file. When it's supposed to make use of gccxml the following code is used:
bool Parser::ParseFile( const std::string& _szFileName, std::string& _gccxmlPath,
const std::string& _tempFileLocation,
std::string& _errorStr)
{
bool retVal = true;
printf("Parsing file %s...\n\n", _szFileName.c_str());
/* format _gccxmlPath, adding a final forward slash to the path if required */
char lastChar = _gccxmlPath.at(_gccxmlPath.length()-1);
if(lastChar != '/' && lastChar != '\\')
_gccxmlPath += "/";
/* set up a temporary environment path variable so that the gccxml exe files may locate each other */
char envPath[500];
sprintf_s(envPath, "PATH=%s", _gccxmlPath.c_str());
const char* gccxml_env[] =
{
/* set path to gccxml directory where all exe files from gccxml are located */
envPath,
0
};
/* path & filename of gccxml.exe */
char gccxml_exe[500];
sprintf_s(gccxml_exe, "%sgccxml.exe", _gccxmlPath.c_str());
/* parameter string used to set gccxml output filename */
char fxmlParam[500];
sprintf_s(fxmlParam, "-fxml=\"%s\"", _tempFileLocation.c_str());
/* synthesize argument list for gccxml*/
/* see: http://gccxml.github.io/HTML/Running.html */
/* and: https://gcc.gnu.org/onlinedocs/gcc-4.9.0/gcc/Invoking-GCC.html */
const char* gccxml_args[GCCXML_PARAM_LEN];
unsigned int curPos = 0;
/* 1st argument: exe name */
gccxml_args[curPos++] = "gccxml.exe";
/* the source code to be compiled */
gccxml_args[curPos++] = _szFileName.c_str();
/* try to find out which msvc compiler to use */
gccxml_args[curPos++] = "--gccxml-compiler cl";
/* the output xml file */
gccxml_args[curPos++] = fxmlParam;
/* last argument: zero termination */
gccxml_args[curPos++] = 0;
/* call gccxml & compile the source code file */
if(0 != _spawnvpe(P_WAIT, gccxml_exe, gccxml_args, gccxml_env))
{
_errorStr += "GCCXML Compiler Error";
return false;
}
/* now parse the gccxml output file from tempfile ... */
...
...
return retVal;
}
as you can see I have to set up a local environment PATH variable to make sure the three executables are able to find each other.
This works great for what I want to do.
Unfortunately I can't use this method to call gccxml.exe when I move the three executables to a different directory. Of course I provide the new _gccxmlPath string but gccxml returns
"Support item Vc10/Include is not available..."
telling me that it looked in the folder into which I moved the executables. All my local copies of Vc10/Include however are located somewhere totally different and I don't understand how it had been able find one of these before I moved the executables.
It seems like this problem can be fixed by calling gccxml_vcconfig.exe using the parameters "patch_dir" and providing the directory "gccxml/Source/GCC_XML/VcInstall" from my gccxml source files. I'm, however, not able to solve my issue this way using any of the spawn* commands.
If I do the gccxml_vcconfig.exe runs just fine but after that I'm trying to call gccxml.exe and it turns out that it still looks in the same directory as before.
So gccxml_vcconfig.exe was probably not what I was looking for?
I'm trying to find a way to provide my tool to users who don't want to recompile gccxml on their machine so I'd like to distribute the thre binaries (and what else is needed).
just to let you know. I found a way of doing what I wanted to do. The trick is as follows:
right before vpe-spawning gccxml using its own location as environment (as shown above) vp-spawn the gccxml_vcconfig.exe without providing any environment path variables. This may look like this
std::string VcInstallDir = resolveRelativePath(_gccxmlPath + "../share/gccxml-0.9/VcInstall");
std::string GCCXML09Dir = resolveRelativePath(_gccxmlPath + "../share/gccxml-0.9");
std::vector<const char*> gccxml_config_args;
gccxml_config_args.push_back("gccxml_vcconfig.exe");
gccxml_config_args.push_back(VcInstallDir.c_str());
gccxml_config_args.push_back(GCCXML09Dir.c_str());
gccxml_config_args.push_back(0);
if(0 != _spawnvp(_P_WAIT, gccxml_vcconfig_exe.c_str(), gccxml_config_args.data()))
{
_errorStr += "GCCXML Configuration Error";
return false;
}
note that resolveRelativePath is a self written function for string manipulation that produces a valid absolute path; gccxml_vcconfig_exe contains the absolute path to my exe file
and I somewhat changed my coding style from arrays to std::vectors as you can see
I have this function:
void PickupFileAndSave(std::vector<unsigned char> file_data, int *error_code, char *file_mask = "All files (*.*)\0*.*\0\0")
{
OPENFILENAMEA ofn; // common dialog box structure
char szFile[MAX_PATH]; // buffer for file name
char initial_dir[MAX_PATH] = { 0 };
GetStartupPath(initial_dir);
// Initialize OPENFILENAME
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = GetActiveWindow();
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 = file_mask;
ofn.nFilterIndex = 1;
ofn.lpstrFileTitle = NULL;
ofn.nMaxFileTitle = 0;
ofn.lpstrInitialDir = initial_dir;
ofn.Flags = OFN_PATHMUSTEXIST | OFN_EXPLORER;
if (!GetSaveFileNameA(&ofn))
{
*error_code = GetLastError();
return;
}
char err_msg[1024] = { 0 };
std::string file_name = ofn.lpstrFile; //this stores path to file without extension
file_name.append(".");
file_name.append(ofn.lpstrDefExt); //this is NULL and fails to copy too
WriteAllBytes(file_name.c_str(), &file_data[0], file_data.size(), &err_msg[0]);
if (strlen(err_msg) > 0)
{
*error_code = GetLastError();
return;
}
}
I call it that way:
int write_error = 0;
PickupFileAndSave(compressed, &write_error, "RLE compressed files (*.rle)\0*.rle\0\0");
When I choose file it shows in the filter needed extension, but do not add it to lpstrFile.
Any ideas why and how to fix it?
You did not assign lpstrDefExt so the system will not add the extension in case you omit it. So you simply need to initialise the field before you show the dialog:
lpstrDefExt = "rle";
The documentation explains this:
lpstrDefExt
The default extension. GetOpenFileName and GetSaveFileName append this extension to the file name if the user fails to type an extension. This string can be any length, but only the first three characters are appended. The string should not contain a period (.). If this member is NULL and the user fails to type an extension, no extension is appended.
It's not clear from the code in the question but you want to handle the case where there are multiple filters and you wish to append the extension of the selected filter.
The system won't do that for you so you will have to. Read nFilterIndex after you have shown the dialog. That tells you which filter the user selected. Then parse the filter string to obtain the chosen extension, and append it to the filename if it has no extension.
What are the Win32 APIs to use to programically delete files and folders?
Edit
DeleteFile and RemoveDirectory are what I was looking for.
However, for this project I ended up using SHFileOperation.
I found the sample code at CodeGuru helpful.
There are two ways to approach this. One is through the File Services (using commands such as DeleteFile and RemoveDirectory) and the other is through the Windows Shell (using SHFileOperation). The latter is recommended if you want to delete non-empty directories or if you want explorer style feedback (progress dialogs with flying files, for example). The quickest way of doing this is to create a SHFILEOPSTRUCT, initialise it and call SHFileOperation, thus:
void silently_remove_directory(LPCTSTR dir) // Fully qualified name of the directory being deleted, without trailing backslash
{
SHFILEOPSTRUCT file_op = {
NULL,
FO_DELETE,
dir,
"",
FOF_NOCONFIRMATION |
FOF_NOERRORUI |
FOF_SILENT,
false,
0,
"" };
SHFileOperation(&file_op);
}
This silently deletes the entire directory. You can add feedback and prompts by varying the SHFILEOPSTRUCT initialisation - do read up on it.
I think you want DeleteFile and RemoveDirectory
See uvgroovy's comment above. You need 2 nulls at the end of the 'dir' field.
int silently_remove_directory(LPCTSTR dir) // Fully qualified name of the directory being deleted, without trailing backslash
{
int len = strlen(dir) + 2; // required to set 2 nulls at end of argument to SHFileOperation.
char* tempdir = (char*) malloc(len);
memset(tempdir,0,len);
strcpy(tempdir,dir);
SHFILEOPSTRUCT file_op = {
NULL,
FO_DELETE,
tempdir,
NULL,
FOF_NOCONFIRMATION |
FOF_NOERRORUI |
FOF_SILENT,
false,
0,
"" };
int ret = SHFileOperation(&file_op);
free(tempdir);
return ret; // returns 0 on success, non zero on failure.
}
I believe DeleteFile does not send the file to the Recycle Bin. Also, RemoveDirectory removes only empty dirs. SHFileOperation would give you the most control over what and how to delete and would show the standard Windows UI dialog boxes (e.g. "Preparing to delete etc.) if needed.
/* function used to send files and folder to recycle bin in win32 */
int fn_Send_Item_To_RecycleBin(TCHAR newpath[])
{
_tcscat_s(newpath, MAX_PATH,_T("|"));
TCHAR* Lastptr = _tcsrchr(newpath, _T('|'));
*Lastptr = _T('\0'); // Replace last pointer with Null for double null termination
SHFILEOPSTRUCT shFileStruct;
ZeroMemory(&shFileStruct,sizeof(shFileStruct));
shFileStruct.hwnd=NULL;
shFileStruct.wFunc= FO_DELETE;
shFileStruct.pFrom= newpath;
shFileStruct.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT;
return SHFileOperation(&shFileStruct);
}
For C++ programming, if you're willing to work with third-party libraries,
boost::filesystem::remove_all(yourPath)
is much simpler than SHFileOperation.