I am writing a program in C++ which is going to process a large number (thousands) of PPM images all stored in the same directory. However, I first need to read in the pixel values. Is their a built in function in the standard namespace or a library I could import that would allow me to read in all the files one at a time without assuming I already know the file name using some sort of loop structure? In case it makes a difference I am writing the program on a Mac.
In order to avoid including boost::filesystem and the required dependencies, I ended up implementing this function:
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <stdexcept>
//include headers required for directory traversal
#if defined(_WIN32)
//disable useless stuff before adding windows.h
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#else
#include "dirent.h"
#endif
/**
Traverses the provided directory (non-recursively) and extracts the absolute paths to all the files in it.
Doesn't support non-ASCII file names.
#param directory the absolute path to the directory
#return a vector of file names (including extension)
*/
std::vector<std::string> Filesystem::GetFilesInDirectory(const std::string &directory)
{
std::vector<std::string> output;
#if defined(_WIN32)
//select all files
std::string tempDirectory = directory + "*";
//initialize the WIN32_FIND_DATA structure
WIN32_FIND_DATA directoryHandle = {0};
//set the directory
std::wstring wideString = std::wstring(tempDirectory.begin(), tempDirectory.end());
LPCWSTR directoryPath = wideString.c_str();
//iterate over all files
HANDLE handle = FindFirstFile(directoryPath, &directoryHandle);
while(INVALID_HANDLE_VALUE != handle)
{
//skip non-files
if (!(directoryHandle.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
{
//convert from WCHAR to std::string
size_t size = wcslen(directoryHandle.cFileName);
std::vector<char> buffer;
buffer.resize(2 * size + 2);
size_t convertedCharacters = 0;
wcstombs_s(&convertedCharacters, buffer.data(), 2 * size + 2, directoryHandle.cFileName, _TRUNCATE);
//trim the null characters (ASCII characters won't fill the vector, since they require fewer bytes)
//convertedCharacters includes the null character, which we want to discard
std::string file(buffer.begin(), buffer.begin() + convertedCharacters - 1);
//add the absolute file path
output.emplace_back(file);
}
if(false == FindNextFile(handle, &directoryHandle)) break;
}
//close the handle
FindClose(handle);
#else
DIR *directoryHandle = opendir(directory.c_str());
if (NULL != directoryHandle)
{
dirent *entry = readdir(directoryHandle);
while (NULL != entry)
{
//skip directories and select only files (hopefully)
//if ((DT_DIR != entry->d_type) && (DT_UNKNOWN == entry->d_type))
if (DT_REG == entry->d_type)
{
output.emplace_back(entry->d_name);
}
//go to next entry
entry = readdir(directoryHandle);
}
closedir(directoryHandle);
}
#endif
return output;
}
I'm not very proud of the above unflexible / mostly useless / platform dependent code and I would definitely go for BOOST if possible. By the way, it's not tested on a MAC, so please let me know if it does the trick.
Related
I want to delete all the files which begin with sub string.
CString Formatter = _T("C:\\logs\\test\\test_12-12-2018_1*.*");
DeleteFile(Formatter);
I intend to delete following files with above code
C:\logs\test\test_12-12-2018_1_G1.txt
C:\logs\test\test_12-12-2018_1_G2.txt
C:\logs\test\test_12-12-2018_1_G3.txt
C:\logs\test\test_12-12-2018_1_G4.txt
When I check error from GetLastError, I get ERROR_INVALID_NAME.
Any idea how to fix this?
DeleteFile doesn't take wildcards. It looks like what you need is a FindFirstFile/FindNextFile/FindClose loop to turn your wildcard into a list of full file names.
#include <windows.h>
#include <pathcch.h>
#pragma comment(lib, "pathcch.lib")
// (In a function now)
WIN32_FIND_DATAW wfd;
WCHAR wszPattern[MAX_PATH];
HANDLE hFind;
INT nDeleted = 0;
PathCchCombine(wszPattern, MAX_PATH, L"C:\\Logs\\Test", L"test_12-12-2018_1*.*");
SetCurrentDirectoryW(L"C:\\Logs\\Test");
hFind = FindFirstFileW(wszPattern, &wfd);
if(hFind == INVALID_HANDLE_VALUE)
{
// Handle error & exit
}
do
{
DeleteFileW(wfd.cFileName);
nDeleted++;
}
while (FindNextFileW(hFind, &wfd));
FindClose(hFind);
wprintf(L"Deleted %d files.\n", nDeleted);
Note that PathCchCombine, FindFirstFileW, and DeleteFileW can all fail, and robust code would check their return values and handle failures appropriately. Also, if FindNextFileW returns 0 and the last error code is not ERROR_NO_MORE_FILES, then it failed because of an actual error (not because there was nothing left to find), and that needs to be handled as well.
Also, if speed is a concern of yours (your example in your post about deleting four files in the same directory doesn't seem like it needs it), replace the line hFind = FindFirstFileW(...) with:
hFind = FindFirstFileExW(wszPattern, FindExInfoBasic, (LPVOID)&wfd, FindExSearchNameMatch, NULL, FIND_FIRST_EX_LARGE_FETCH);
Although you can search for the file names, and then call DeleteFile individually for each, my advice would be to use one of the Windows shell functions to do the job instead.
For example, you could use code something like this:
#define _WIN32_IE 0x500
#include <windows.h>
#include <shellapi.h>
#include <shlobj.h>
#include <iostream>
#include <string>
static char const *full_path(std::string const &p) {
static char path[MAX_PATH+2] = {0};
char *ignore;
GetFullPathName(p.c_str(), sizeof(path), path, &ignore);
return path;
}
static int shell_delete(std::string const &name) {
SHFILEOPSTRUCT op = { 0 };
op.wFunc = FO_DELETE;
op.pFrom = full_path(name);
op.fFlags = FOF_ALLOWUNDO | FOF_SILENT | FOF_WANTNUKEWARNING | FOF_NOCONFIRMATION;
return !SHFileOperation(&op);
}
int main(int argc, char **argv) {
if ( argc < 2) {
fprintf(stderr, "Usage: delete <filename> [filename ...]");
return 1;
}
for (int i=1; i<argc; i++)
shell_delete(argv[i]);
}
One obvious advantage to this is that you can pass the FOF_ALLOWUNDO flag (as I have in the code above), which moves the files to the recycle bin instead of removing it permanently. Of course, you can omit that flag if you want to the files nuked.
Depending on what you're doing, there are a few other flags that might be handy, such as FOF_FILESONLY, to delete only files, not directories that might match the wildcard you specify, and FOF_NORECURSION to have it not recurse into subdirectories at all.
Microsoft considers SHFileOperation obsolescent, and has (in Windows Vista, if memory serves) "replaced" it with IFileOperation. IFileOperation is a COM interface though, so unless you're using COM elsewhere in your code, chances are pretty good that using it will add a fair amount of extra work for (at least in this case) little or no real advantage. Especially you're already using COM, however, this might be worth considering.
I found this in another forum that is supposed to give it to you. But I think this may not be the best way, also I think it results in a memory leak due to the array not being deleted. Is this true?
Also is this the best way? Best way being a cross platform command (if it doesn't exist then use Windows) that gives the folder directory directly.
std::string ExePath()
{
using namespace std;
char buffer[MAX_PATH];
GetModuleFileName(NULL, buffer, MAX_PATH);
string::size_type pos = string(buffer).find_last_of("\\/");
if (pos == string::npos)
{
return "";
}
else
{
return string(buffer).substr(0, pos);
}
}
There is no memory leak in your code, but there are some issues with it:
it is Windows-specific,
it works with local code-page and does not support arbitrary Unicode files names.
Unfortunately, there is no standard way of accomplishing this task just with C++ library, but here is a code that will work on Windows and Linux, and support Unicode paths as well. Also it utilizes std::filesystem library from C++17:
#include <filesystem>
#ifdef _WIN32
#include <windows.h>
#elif
#include <unistd.h>
#endif
std::filesystem::path GetExeDirectory()
{
#ifdef _WIN32
// Windows specific
wchar_t szPath[MAX_PATH];
GetModuleFileNameW( NULL, szPath, MAX_PATH );
#else
// Linux specific
char szPath[PATH_MAX];
ssize_t count = readlink( "/proc/self/exe", szPath, PATH_MAX );
if( count < 0 || count >= PATH_MAX )
return {}; // some error
szPath[count] = '\0';
#endif
return std::filesystem::path{ szPath }.parent_path() / ""; // to finish the folder path with (back)slash
}
The code below demonstrates how stat and GetFileAttributes fail when the path contains some strange (but valid) ASCII characters.
As a workaround, I would use the 8.3 DOS file name. But this does not work when the drive has 8.3 names disabled.
(8.3 names are disabled with the fsutil command: fsutil behavior set disable8dot3 1).
Is it possible to get stat and/or GetFileAttributes to work in this case?
If not, is there another way of determining whether or not a path is a directory or file?
#include "stdafx.h"
#include <sys/stat.h>
#include <string>
#include <Windows.h>
#include <atlpath.h>
std::wstring s2ws(const std::string& s)
{
int len;
int slength = (int)s.length() + 1;
len = MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, 0, 0);
wchar_t* buf = new wchar_t[len];
MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, buf, len);
std::wstring r(buf);
delete[] buf;
return r;
}
// The final characters in the path below are 0xc3 (Ã) and 0x3f (?).
// Create a test directory with the name à and set TEST_DIR below to your test directory.
const char* TEST_DIR = "D:\\tmp\\VisualStudio\\TestProject\\ConsoleApplication1\\test_data\\Ã";
int main()
{
std::string testDir = TEST_DIR;
// test stat and _wstat
struct stat st;
const auto statSucceeded = stat(testDir.c_str(), &st) == 0;
if (!statSucceeded)
{
printf("stat failed\n");
}
std::wstring testDirW = s2ws(testDir);
struct _stat64i32 stW;
const auto statSucceededW = _wstat(testDirW.data(), &stW) == 0;
if (!statSucceededW)
{
printf("_wstat failed\n");
}
// test PathIsDirectory
const auto isDir = PathIsDirectory(testDirW.c_str()) != 0;
if (!isDir)
{
printf("PathIsDirectory failed\n");
}
// test GetFileAttributes
const auto fileAttributes = ::GetFileAttributes(testDirW.c_str());
const auto getFileAttributesWSucceeded = fileAttributes != INVALID_FILE_ATTRIBUTES;
if (!getFileAttributesWSucceeded)
{
printf("GetFileAttributes failed\n");
}
return 0;
}
The problem you have encountered comes from using the MultiByteToWideChar function. Using CP_ACP can default to a code page that does not support some characters. If you change the default system code page to UTF8, your code will work. Since you cannot tell your clients what code page to use, you can use a third party library such as International Components for Unicode to convert from the host code page to UTF16.
I ran your code using console code page 65001 and VS2015 and your code worked as written. I also added positive printfs to verify that it did work.
Don't start with a narrow string literal and try to convert it, start with a wide string literal - one that represents the actual filename. You can use hexadecimal escape sequences to avoid any dependency on the encoding of the source code.
If the actual code doesn't use string literals, the best resolution depends on the situation; for example, if the file name is being read from a file, you need to make sure that you know what encoding the file is in and perform the conversion accordingly.
If the actual code reads the filename from the command line arguments, you can use wmain() instead of main() to get the arguments as wide strings.
I am trying get path of the exe file at the same folder where this program will been. but i couldnt figure out how to do, i did something like this but it only gets the current programs path, and i dont know how to replace filenames between my program and the program i want to get path.
so simply can you help me about get the path of an exe (i know the name of that exe ) at the same folder where this program will been...
char fullp[MAX_PATH];
char selfp[MAX_PATH] = "..//myprogram.exe";
char otherprogram[MAX_PATH] = "//test.exe";
DWORD szPath;
szPath = GetModuleFileName(NULL, selfp, sizeof(selfp));
The Win32 API has a whole bunch of Path Handling functions available.
For instance, once you have obtained the calling process's full path from GetModuleFileName(), you can use PathRemoveFileSpec() to remove the filename leaving just the folder path:
char selfdir[MAX_PATH] = {0};
GetModuleFileNameA(NULL, selfdir, MAX_PATH);
PathRemoveFileSpecA(selfdir);
And then use either PathAppend() or PathCombine() to append a different filename to that path:
char otherprogram[MAX_PATH] = {0};
lstrcpyA(otherprogram, selfdir);
PathAppendA(otherprogram, "test.exe");
char otherprogram[MAX_PATH] = {0};
PathCombineA(otherprogram, selfdir, "test.exe");
OP is most of the way there. Here is an example of how to get the rest of the way.
To simplify the solution, I'm leaving char arrays as far behind as possible and using std::string.
#include <iostream>
#include <string>
#include <windows.h>
int main()
{
char selfp[MAX_PATH];
std::string otherprogram = "Failed to get path";
DWORD szPath;
szPath = GetModuleFileName(NULL, selfp, MAX_PATH);
if (szPath != 0) // successfully got path of current program
{
// helper string to make life much, much easier
std::string helper = selfp;
//find last backslash in current program path
size_t pos = helper.find_last_of( "\\" );
if (pos != std::string::npos) // found last backslash
{
// remove everything after last backslash. This should remove
// the current program's name.
otherprogram = helper.substr( 0, pos+1);
// append new program name
otherprogram += "test.exe";
}
}
std::cout << otherprogram << std::endl;
}
I'm using something like this:
std::string tempDirectory = "./test/*";
WIN32_FIND_DATA directoryHandle;
memset(&directoryHandle, 0, sizeof(WIN32_FIND_DATA));//perhaps redundant???
std::wstring wideString = std::wstring(tempDirectory.begin(), tempDirectory.end());
LPCWSTR directoryPath = wideString.c_str();
//iterate over all files
HANDLE handle = FindFirstFile(directoryPath, &directoryHandle);
while(INVALID_HANDLE_VALUE != handle)
{
//skip non-files
if (!(directoryHandle.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
{
//convert from WCHAR to std::string
size_t size = wcslen(directoryHandle.cFileName);
char * buffer = new char [2 * size + 2];
wcstombs(buffer, directoryHandle.cFileName, 2 * size + 2);
std::string file(buffer);
delete [] buffer;
std::cout << file;
}
if(FALSE == FindNextFile(handle, &directoryHandle)) break;
}
//close the handle
FindClose(handle);
which prints the names of each file in the relative directory ./test/*.
Is there any way to determine the absolute path of this directory, just like realpath() does on Linux without involving any 3rd party libraries like BOOST? I'd like to print the absolute path to each file.
See the GetFullPathName function.
You can try GetFullPathName
Or you can use SetCurrentDirectory and GetCurrentDirectory. You might want to save the current directory before doing this so you can go back to it afterwards.
In both cases, you only need to get the full path of your search directory. API calls are slow. Inside the loop you just combine strings.