In a game I'm making, folders with text files inside represent world saves, In the load menu of this game I want to have an option to delete a save. I'm using currently this code to try to delete the saves:
hFind = FindFirstFile((dir+"/*").c_str(), &FindFileData);
if (hFind){
do{
string s = FindFileData.cFileName;
if(s.find('.')){//prevents prossesing of "." and ".."
DeleteFile((dir+"/"+s).c_str());
}
}while(FindNextFile(hFind,&FindFileData));
CloseHandle(hFind);
}
rmdir(dir.c_str());
The only things in these folders are 3 text files so this code should be sufficient, however it isn't. What happens is all the file in the directory are deleted but not the folder, and if I try to delete this folder manually, or edit it in any way while the program is running, windows denies me access. But as soon as I close the game the folder is deleted.
I know the files inside are deleted because I tried the above code with out "rmdir(dir.c_str());" and opened the folder and all the files were gone, also with the above code if I "Delete" the save and then try to load it I there is no world and no inventory, indicating the files have been deleted.
I've tried it with removeDirectory and the same thing happens, It also says it was deleted successfully without any errors.
Why does this happen? How can I avoid this, and get it to work properly?
Any help would be greatly appreciated.
The problem was fixxed with the following code:
hFind = FindFirstFile((dir+"/*").c_str(), &FindFileData);
if (hFind){
do{
string s = FindFileData.cFileName;
if(s.find('.')){//prevents prossesing of "." and ".."
DeleteFile((dir+"/"+s).c_str());
}
}while(FindNextFile(hFind,&FindFileData));
CloseHandle(hFind);
}
findClose(hFind);
rmdir(dir.c_str());
According to the RemoveDirectory documentation:
RemoveDirectory function marks a directory for deletion on close. Therefore, the directory is not removed until the last handle to the directory is closed.
Probably your program has the directory as its current working directory, or perhaps otherwise still has a handle to it open.
In windows, rmdir is a comparability function that calls the native windows functions, so it will behave the same.
The root problem is that the code called CloseHandle instead of FindClose on the handle returned by FindFirstFile.
But the code has several more bugs. In the interest of helping future visitors here, this is the corrected code.
HANDLE hFind = FindFirstFile((dir + "\\*").c_str(), &FindFileData); // See 1 below
if (hFind != INVALID_HANDLE_VALUE) { // 2
do {
if ((FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) { // 3
const std::string s = dir + "\\" + FindFileData.cFileName;
DeleteFile(s.c_str());
}
} while (FindNextFile(hFind, &FindFileData));
// 4
FindClose(hFind); // 5
}
RemoveDirectory(dir.c_str()); // 6
Windows paths use \ rather than / as separators. Many of the APIs will accept either, but eventually you'll encounter one that doesn't, so it's best to use the correct one consistently.
FindFirstFile returns INVALID_HANDLE_VALUE (not NULL) upon failure. Since INVALID_HANDLE_VALUE is non-zero, you cannot simply test if (hFile) { ... }.
The API enumerates files and directories. The old code was trying to filter out the . and .. directories incorrectly, which could have caused it to skip some files and to attempt to use DeleteFile on other directories. It's simpler (and easier-to-understand) to skip all directories.
Don't call CloseHandle on the handle returned by FindFirstFile.
Do call FindClose on the handle returned by FindFirstFile, but do so only in the case you got a valid handle from FindFirstFile.
As long as you're using Windows-specific APIs, you may as well use them consistently and not mix with library wrappers like rmdir. The library wrappers sometimes introduce surprising limitations or behavior, though I think rmdir would work correctly in this instance.
This still leaves a significant problem: It doesn't handle Unicode in the paths (and it requires you to compile for ANSI which limits Unicode handling in other parts of the project).
Related
If I do
LoadLibrary("MyTest.dll")
Windows will locate and load it from "C:\TestFolder\Test\MyTest.dll", because "C:\TestFolder\Test\" is in %PATH% folder.
How can I emulate same function? I need to locate C:\TestFolder\Test\MyTest.dll (C:\TestFolder\Test\ is in %PATH%) by passing MyTest.dll as an argument to a function. Is there such an API? or a function?
P.S. I can't do LoadLibrary and then GetModuleHandle and finding Path, sometimes this DLL could be malicious DLL and I can't load it. So I need to find PATH without having to load it.
To load the DLL without running any malicious code inside, use LoadLibraryEx with the DONT_RESOLVE_DLL_REFERENCES and LOAD_LIBRARY_AS_DATAFILE flags.
Then you can use GetModuleFileName.
You should also read about all the other flags, which allow you to perform all the various searches Windows is capable of.
The accepted answer to this question will not work in all scenarios. More specifically, using GetModuleFileName together with LOAD_LIBRARY_AS_DATAFILE will only work if the library was already loaded prior without this flag. For example, it will work for a library like KERNEL32.DLL which is already loaded by the process, but it will not work with your own library being loaded into the process for the first time.
This is because, to quote The Old New Thing, a library loaded via LOAD_LIBRARY_AS_DATAFILE (or similar flags) doesn't get to play in any reindeer module games.
If you load a library with the LOAD_LIBRARY_AS_DATAFILE flag, then it
isn’t really loaded in any normal sense. In fact, it’s kept completely
off the books. If you load a library with the
LOAD_LIBRARY_AS_DATAFILE, LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE, or
LOAD_LIBRARY_AS_IMAGE_RESOURCE flag (or any similar flag added in the
future), then the library gets mapped into the process address space,
but it is not a true module. Functions like GetModuleHandle,
GetModuleFileName, EnumProcessModules and
CreateToolhelp32Snapshot will not see the library, because it was
never entered into the database of loaded modules.
At that point, you might as well just use GetModuleHandle, since it'll only work with previously loaded libraries. Obviously not ideal, and doesn't actually answer the question of getting the path without executing DllMain.
What about the other flag, DONT_RESOLVE_DLL_REFERENCES? Well, technically yes it will work. However, you'll notice in the Microsoft documentation the following note.
Do not use this value; it is provided only for backward compatibility.
If you are planning to access only data or resources in the DLL, use
LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE or LOAD_LIBRARY_AS_IMAGE_RESOURCE
or both.
The flag is only provided for backwards compatibility, and that is for good reason. To quote The Old New Thing a second time, DONT_RESOLVE_DLL_REFERENCES is a time bomb.
It is common for somebody to call GetModuleHandle to see if a DLL is
loaded, and if so, use GetProcAddress to get a procedure address and
call it. If the DLL had been loaded with DONT_RESOLVE_DLL_REFERENCES,
both the GetModuleHandle will succeed, but the resulting function will
crash when called. The code doing this has no idea that the DLL was
loaded with DONT_RESOLVE_DLL_REFERENCES; it has no way of protecting
itself.
Other threads will see the library is loaded. If they attempt to use the loaded library, as would be entirely normal to do, they will crash the program because it has not actually been initialized. So this flag, while it does work with GetModuleFileName, will cause instability in the program. Still not ideal.
So then, if we can't use DONT_RESOLVE_DLL_REFERENCES or LOAD_LIBRARY_AS_DATAFILE with GetModuleFileName, then what is the solution? Well, the solution is to not use GetModuleFileName - and instead use GetMappedFileName.
At this point, if you know what GetMappedFileName does, you may be confused. Normally, GetMappedFileName is used to get a filename from a File Mapping, created with the File Mapping API. Well, the secret is that under the hood, image loading is accomplished with MapViewOfFile. This is subtly hinted at by the Dbghelp documentation - for example, the ImageNtHeader documentation which states an image base must be...
The base address of an image that is mapped into memory by a call to
the MapViewOfFile function.
This means that not only is a module handle a pointer to a module, but also a mapped file pointer. Unlike GetModuleFileName however, GetMappedFileName has no concept of "the reindeer module games," so it works even with the LOAD_LIBARY_AS_DATAFILE flag of LoadLibraryEx. Not only that, but GetMappedFileName has an additional benefit over GetModuleFileName.
Something you might not know is that simply loading a library with LoadLibrary does not exclusively lock the DLL file. Try it yourself: write a simple program which loads your own library with LoadLibrary, and then while the program is running, cut and paste the DLL file to a different location. This will work (and yes, has always worked regardless of Windows version) so long as no other application has a lock on the DLL file. The File Mapping API just keeps on chugging, regardless of the DLL file's new location.
However, when you call GetModuleFileName, it will always return the path of the DLL file as of whenever the library was loaded with LoadLibrary. This has security ramifications. It would be possible to cut and paste the DLL file to a new location, and put a different one at the old location. If the path returned by GetModuleFileName is used to load the library again, it could actually result in loading a different DLL file altogether. As such, GetModuleFileName is only useful for the purpose of displaying the name or getting the DLL file name passed to LoadLibrary, and can't be depended upon for the current file path.
GetMappedFileName has no such issue, because it has no concept of when LoadLibrary was called. It returns an up to date path to the file, even if it has been moved while it's loaded.
There is one minor downside though: GetMappedFileName returns a device path, in the format of \Device\HarddiskVolume1\Example.DLL. This appears to be due to the File Mapping API originally being from DOS - why a "GetMappedFileNameEx" that returns a drive path doesn't exist is beyond me.
Thankfully, it's a resolvable issue. We can use QueryDosDevice to turn the device path into a drive path.
bool getFilePathNameFromMappedView(HANDLE process, LPVOID mappedView, std::string &filePathName) {
if (!process) {
return false;
}
if (!mappedView) {
return false;
}
CHAR mappedFileName[MAX_PATH] = "";
if (!GetMappedFileName(process, mappedView, mappedFileName, MAX_PATH - 1)) {
return false;
}
// the mapped file name is a device path, we need a drive path
// https://learn.microsoft.com/en-us/windows/win32/fileio/defining-an-ms-dos-device-name
const SIZE_T DEVICE_NAME_SIZE = 3;
CHAR deviceName[DEVICE_NAME_SIZE] = "A:";
// the additional character is for the trailing slash we add
size_t targetPathLength = 0;
CHAR targetPath[MAX_PATH + 1] = "";
// find the MS-DOS Device Name
DWORD logicalDrives = GetLogicalDrives();
do {
if (logicalDrives & 1) {
if (!QueryDosDevice(deviceName, targetPath, MAX_PATH - 1)) {
return false;
}
// add a trailing slash
targetPathLength = strnlen_s(targetPath, MAX_PATH);
targetPath[targetPathLength++] = '\\';
// compare the Target Path to the Device Object Name in the Mapped File Name
// case insensitive
// https://flylib.com/books/en/4.168.1.23/1/
if (!_strnicmp(targetPath, mappedFileName, targetPathLength)) {
break;
}
}
deviceName[0]++;
} while (logicalDrives >>= 1);
if (!logicalDrives) {
return false;
}
// get the drive path
filePathName = std::string(deviceName) + "\\" + (mappedFileName + targetPathLength);
return true;
}
GetLogicalDrives just gets a list of which drives are available (like C:, D:, etc.) in the form of a bitmask (where the first bit corresponds to A:, the second bit corresponds to B:, etc.) We then loop through the available drives, getting their paths, and comparing them against the one in the Mapped File Name. The result of this function is a path that could be passed to the CreateFile function.
The only source I could find for whether these device paths are case-insensitive or not was this book claiming that they used to be case-sensitive, but are case-insensitive as of Windows XP. I'm going to assume you are not targeting Windows 9x anymore, so I just compare them case-insensitively.
Hold on a second though: this still may not be enough. If your intention is, as mine was, to try and get a file handle to the DLL file, but using the DLL search path, then just getting the path and passing it to CreateFile opens us up to a filesystem race condition, like the kind explained in this LiveOverflow video. A technique like this could be abused by a hacker so the handle doesn't actually point to the file we want. There isn't any GetMappedFileHandle function, so what can we do?
I thought about this for a while, and here is the workaround I came up with. The idea is that we call our own getFilePathNameFromMappedView function once just to get the path to pass to CreateFile, and exclusively lock the file in place with the FILE_SHARE_READ flag. However, we then confirm, with a second call to getFilePathNameFromMappedView, that the file is still actually there. If the paths match, knowing that the file at that path is now locked, we can know for sure the handle we got is to the library that was actually loaded. If the file was moved before the call to CreateFile finished, however, the paths will not match, because GetMappedFileName returns the up to date path to the file. At that point, we can try again. I decided to do this as a recursive function, but you could decide to throw an error and have the caller handle it.
inline bool stringsCaseInsensitiveEqual(const char* leftHandSide, const char* rightHandSide) {
return !_stricmp(leftHandSide, rightHandSide);
}
bool getHandleFromModuleHandle(HMODULE moduleHandle, HANDLE &file) {
if (!moduleHandle) {
return false;
}
HANDLE currentProcess = GetCurrentProcess();
// pass the Module Handle as a Mapped View
// to get its current path
std::string filePathName = "";
if (!getFilePathNameFromMappedView(currentProcess, moduleHandle, filePathName)) {
return false;
}
// prevent the Example File from being written to, moved, renamed, or deleted
// by acquiring it and effectively locking it from other processes
file = CreateFile(filePathName.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (!file || file == INVALID_HANDLE_VALUE) {
return false;
}
bool recursive = false;
{
// we now know this path is now protected against race conditions
// but the path may have changed before we acquired it
// so ensure the File Path Name is the same as before
// so that we know the path we protected is for the Mapped View
std::string _filePathName = "";
if (!getFilePathNameFromMappedView(currentProcess, moduleHandle, _filePathName)) {
goto error;
}
if (!stringsCaseInsensitiveEqual(filePathName.c_str(), _filePathName.c_str())) {
// race condition occured
recursive = true;
goto error;
}
}
return true;
error:
if (file && file != INVALID_HANDLE_VALUE) {
if (!CloseHandle(file)) {
return false;
}
file = NULL;
}
if (recursive) {
return getHandleFromModuleHandle(moduleHandle, file);
}
return false;
}
Then we can call it like this...
HMODULE exampleModuleHandle = LoadLibraryEx("Example.DLL", NULL, LOAD_LIBRARY_AS_DATAFILE);
if (!exampleModuleHandle) {
return false;
}
// we want this to be a handle to the Example File
HANDLE exampleFile = NULL;
if (!getHandleFromModuleHandle(exampleModuleHandle, exampleFile)) {
return false;
}
This is just something I thought of, so let me know in the responses if there are issues with it.
Once you have a handle to the file, it can then be passed to GetFileInformationByHandle to confirm it is the same library as is loaded in another process, and subsequently closed with CloseHandle.
I had the following code which got executed every two
minutes all day long:
int sucessfully_deleted = DeleteFile(dest_filename);
if (!sucessfully_deleted)
{
// this never happens
}
rename(source_filename,dest_filename);
Once every several hours the rename() would fail with errno=13 (EACCES). The files involved were all sitting on a DropBox directory and I had a hunch that DropBox could be the cause. I figured that it might just be possible that the DeleteFile() function may return with a non-zero successfully_deleted but actually DropBox could still be busy doing some stuff in relation to the deletion that prevented rename() from succeeding. What I did next was to change rename() to my_rename() which would attempt a rename() and upon any failure would Sleep() for one second and try a second time. Sure enough that has worked perfectly ever since. What's more, I get a diagnostic message displaying first-attempt-failures every several hours. It has never failed on the second attempt.
So you could say that the problem is entirely solved... but I would like to understand what might be going on so as to better defend myself against any related DropBox issues in the future...
Really I would like to have a new super_delete() function which does not return until the file is properly deleted and finished with in all respects.
under windows request to delete file really never delete file just. it mark it FCB (File Control Block) with special flag (FCB_STATE_DELETE_ON_CLOSE). real deletion will be only when the last file handle will be closed.
The DeleteFile function marks a file for deletion on close. Therefore,
the file deletion does not occur until the last handle to the file is
closed. Subsequent calls to CreateFile to open the file fail with
ERROR_ACCESS_DENIED.
also if exist section ( memory-mapped file ) open on file - file even can not be marked for delete. api call fail with STATUS_CANNOT_DELETE. so in general impossible always delete file.
in case exist another open handles for file (but not section !) begin from windows 10 rs1 exist new functional for delete - FileDispositionInformationEx with FILE_DISPOSITION_POSIX_SEMANTICS. in this case:
Normally a file marked for deletion is not actually deleted until all
open handles for the file have been closed and the link count for the
file is zero. When marking a file for deletion using
FILE_DISPOSITION_POSIX_SEMANTICS, the link gets removed from the visible namespace as soon as the POSIX delete handle has been closed,
but the file’s data streams remain accessible by other existing
handles until the last handle has been closed.
ULONG DeletePosix(PCWSTR lpFileName)
{
HANDLE hFile = CreateFileW(lpFileName, DELETE, FILE_SHARE_VALID_FLAGS, 0, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_OPEN_REPARSE_POINT, 0);
if (hFile == INVALID_HANDLE_VALUE)
{
return GetLastError();
}
static FILE_DISPOSITION_INFO_EX fdi = { FILE_DISPOSITION_DELETE| FILE_DISPOSITION_POSIX_SEMANTICS };
ULONG dwError = SetFileInformationByHandle(hFile, FileDispositionInfoEx, &fdi, sizeof(fdi))
? NOERROR : GetLastError();
// win10 rs1: file removed from parent folder here
CloseHandle(hFile);
return dwError;
}
Update
Sorry i didn't get the question correctly the first time. I thought DeleteFile returned error 13.
Now I understand that DeleteFile succeeds but rename fails immediatlely after.
It could be just a sync issue with the filesystem. After calling DeleteFile the file will be deleted when the OS commits the changes to the filesystem. That may not appen immediately.
If you need to perform multiple operations to the same path, you should have a look at transactions https://learn.microsoft.com/it-it/windows/desktop/api/winbase/nf-winbase-deletefiletransacteda.
-- OLD ANSWER --
That is correct. If the another application handles to that file, DeleteFile will fail.
Citing MSDN docs https://learn.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-deletefile :
The DeleteFile function fails if an application attempts to delete a file that has other handles open for normal I/O or as a memory-mapped file (FILE_SHARE_DELETE must have been specified when other handles were opened).
This applies to dropbox, the antivirus, or in general, any other application that may open those files.
Dropbox may open the file to compute its hash (to look for changes) at any moment. Same goes with the antivirus.
I am trying to move all files in a folder (Desktop) to another but only HALF of the files end up being moved, howcome?
Here is my code:
WCHAR DesktopPath[MAX_PATH];
pExpandEnvironmentStringsW(L"%userprofile%\\Desktop\\", DesktopPath, MAX_PATH);
WCHAR DesktopFiles[MAX_PATH];
lstrcpyW(DesktopFiles, DesktopPath);
lstrcatW(DesktopFiles, L"DesktopFiles\\");
CreateDirectory(DesktopFiles, NULL);
WCHAR search_path[200];
wsprintf(search_path, L"%s/*.*", DesktopPath);
WIN32_FIND_DATA fd;
HANDLE hFind = ::FindFirstFile(search_path, &fd);
if (hFind != INVALID_HANDLE_VALUE)
{
do {
WCHAR filepath[MAX_PATH];
lstrcpyW(filepath, DesktopPath);
lstrcatW(filepath, fd.cFileName);
WCHAR newpath[MAX_PATH];
lstrcpyW(newpath, DesktopFiles);
lstrcatW(newpath, fd.cFileName);
MoveFile(filepath, newpath);
} while (::FindNextFile(hFind, &fd));
::FindClose(hFind);
}
There's a warning in the Windows API about use of FindFirstFile/FindNextFile loop about changing the directory in the loop. Make a list, then move the files in another loop. [Incidentally delete is explicitly defined to work so we should be surprised move isn't. And as we see below, that's not what's really going on.]
Having now read the intermediate edit, he has fallen into an interesting mistake. The source directory is the desktop. It moved all of his files on the desktop, and didn't look at the all users desktop, which is another directory. (In fact enough information is present in the original question to see this, but not enough to see that he was expecting it to apply to the all users desktop.) This isn't going to work so well because the target directory doesn't exist on the all users desktop. While he could create it, this just ends up with two of them visible, and merging arbitrary directories as folder views is too advanced a topic.
If I do
LoadLibrary("MyTest.dll")
Windows will locate and load it from "C:\TestFolder\Test\MyTest.dll", because "C:\TestFolder\Test\" is in %PATH% folder.
How can I emulate same function? I need to locate C:\TestFolder\Test\MyTest.dll (C:\TestFolder\Test\ is in %PATH%) by passing MyTest.dll as an argument to a function. Is there such an API? or a function?
P.S. I can't do LoadLibrary and then GetModuleHandle and finding Path, sometimes this DLL could be malicious DLL and I can't load it. So I need to find PATH without having to load it.
To load the DLL without running any malicious code inside, use LoadLibraryEx with the DONT_RESOLVE_DLL_REFERENCES and LOAD_LIBRARY_AS_DATAFILE flags.
Then you can use GetModuleFileName.
You should also read about all the other flags, which allow you to perform all the various searches Windows is capable of.
The accepted answer to this question will not work in all scenarios. More specifically, using GetModuleFileName together with LOAD_LIBRARY_AS_DATAFILE will only work if the library was already loaded prior without this flag. For example, it will work for a library like KERNEL32.DLL which is already loaded by the process, but it will not work with your own library being loaded into the process for the first time.
This is because, to quote The Old New Thing, a library loaded via LOAD_LIBRARY_AS_DATAFILE (or similar flags) doesn't get to play in any reindeer module games.
If you load a library with the LOAD_LIBRARY_AS_DATAFILE flag, then it
isn’t really loaded in any normal sense. In fact, it’s kept completely
off the books. If you load a library with the
LOAD_LIBRARY_AS_DATAFILE, LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE, or
LOAD_LIBRARY_AS_IMAGE_RESOURCE flag (or any similar flag added in the
future), then the library gets mapped into the process address space,
but it is not a true module. Functions like GetModuleHandle,
GetModuleFileName, EnumProcessModules and
CreateToolhelp32Snapshot will not see the library, because it was
never entered into the database of loaded modules.
At that point, you might as well just use GetModuleHandle, since it'll only work with previously loaded libraries. Obviously not ideal, and doesn't actually answer the question of getting the path without executing DllMain.
What about the other flag, DONT_RESOLVE_DLL_REFERENCES? Well, technically yes it will work. However, you'll notice in the Microsoft documentation the following note.
Do not use this value; it is provided only for backward compatibility.
If you are planning to access only data or resources in the DLL, use
LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE or LOAD_LIBRARY_AS_IMAGE_RESOURCE
or both.
The flag is only provided for backwards compatibility, and that is for good reason. To quote The Old New Thing a second time, DONT_RESOLVE_DLL_REFERENCES is a time bomb.
It is common for somebody to call GetModuleHandle to see if a DLL is
loaded, and if so, use GetProcAddress to get a procedure address and
call it. If the DLL had been loaded with DONT_RESOLVE_DLL_REFERENCES,
both the GetModuleHandle will succeed, but the resulting function will
crash when called. The code doing this has no idea that the DLL was
loaded with DONT_RESOLVE_DLL_REFERENCES; it has no way of protecting
itself.
Other threads will see the library is loaded. If they attempt to use the loaded library, as would be entirely normal to do, they will crash the program because it has not actually been initialized. So this flag, while it does work with GetModuleFileName, will cause instability in the program. Still not ideal.
So then, if we can't use DONT_RESOLVE_DLL_REFERENCES or LOAD_LIBRARY_AS_DATAFILE with GetModuleFileName, then what is the solution? Well, the solution is to not use GetModuleFileName - and instead use GetMappedFileName.
At this point, if you know what GetMappedFileName does, you may be confused. Normally, GetMappedFileName is used to get a filename from a File Mapping, created with the File Mapping API. Well, the secret is that under the hood, image loading is accomplished with MapViewOfFile. This is subtly hinted at by the Dbghelp documentation - for example, the ImageNtHeader documentation which states an image base must be...
The base address of an image that is mapped into memory by a call to
the MapViewOfFile function.
This means that not only is a module handle a pointer to a module, but also a mapped file pointer. Unlike GetModuleFileName however, GetMappedFileName has no concept of "the reindeer module games," so it works even with the LOAD_LIBARY_AS_DATAFILE flag of LoadLibraryEx. Not only that, but GetMappedFileName has an additional benefit over GetModuleFileName.
Something you might not know is that simply loading a library with LoadLibrary does not exclusively lock the DLL file. Try it yourself: write a simple program which loads your own library with LoadLibrary, and then while the program is running, cut and paste the DLL file to a different location. This will work (and yes, has always worked regardless of Windows version) so long as no other application has a lock on the DLL file. The File Mapping API just keeps on chugging, regardless of the DLL file's new location.
However, when you call GetModuleFileName, it will always return the path of the DLL file as of whenever the library was loaded with LoadLibrary. This has security ramifications. It would be possible to cut and paste the DLL file to a new location, and put a different one at the old location. If the path returned by GetModuleFileName is used to load the library again, it could actually result in loading a different DLL file altogether. As such, GetModuleFileName is only useful for the purpose of displaying the name or getting the DLL file name passed to LoadLibrary, and can't be depended upon for the current file path.
GetMappedFileName has no such issue, because it has no concept of when LoadLibrary was called. It returns an up to date path to the file, even if it has been moved while it's loaded.
There is one minor downside though: GetMappedFileName returns a device path, in the format of \Device\HarddiskVolume1\Example.DLL. This appears to be due to the File Mapping API originally being from DOS - why a "GetMappedFileNameEx" that returns a drive path doesn't exist is beyond me.
Thankfully, it's a resolvable issue. We can use QueryDosDevice to turn the device path into a drive path.
bool getFilePathNameFromMappedView(HANDLE process, LPVOID mappedView, std::string &filePathName) {
if (!process) {
return false;
}
if (!mappedView) {
return false;
}
CHAR mappedFileName[MAX_PATH] = "";
if (!GetMappedFileName(process, mappedView, mappedFileName, MAX_PATH - 1)) {
return false;
}
// the mapped file name is a device path, we need a drive path
// https://learn.microsoft.com/en-us/windows/win32/fileio/defining-an-ms-dos-device-name
const SIZE_T DEVICE_NAME_SIZE = 3;
CHAR deviceName[DEVICE_NAME_SIZE] = "A:";
// the additional character is for the trailing slash we add
size_t targetPathLength = 0;
CHAR targetPath[MAX_PATH + 1] = "";
// find the MS-DOS Device Name
DWORD logicalDrives = GetLogicalDrives();
do {
if (logicalDrives & 1) {
if (!QueryDosDevice(deviceName, targetPath, MAX_PATH - 1)) {
return false;
}
// add a trailing slash
targetPathLength = strnlen_s(targetPath, MAX_PATH);
targetPath[targetPathLength++] = '\\';
// compare the Target Path to the Device Object Name in the Mapped File Name
// case insensitive
// https://flylib.com/books/en/4.168.1.23/1/
if (!_strnicmp(targetPath, mappedFileName, targetPathLength)) {
break;
}
}
deviceName[0]++;
} while (logicalDrives >>= 1);
if (!logicalDrives) {
return false;
}
// get the drive path
filePathName = std::string(deviceName) + "\\" + (mappedFileName + targetPathLength);
return true;
}
GetLogicalDrives just gets a list of which drives are available (like C:, D:, etc.) in the form of a bitmask (where the first bit corresponds to A:, the second bit corresponds to B:, etc.) We then loop through the available drives, getting their paths, and comparing them against the one in the Mapped File Name. The result of this function is a path that could be passed to the CreateFile function.
The only source I could find for whether these device paths are case-insensitive or not was this book claiming that they used to be case-sensitive, but are case-insensitive as of Windows XP. I'm going to assume you are not targeting Windows 9x anymore, so I just compare them case-insensitively.
Hold on a second though: this still may not be enough. If your intention is, as mine was, to try and get a file handle to the DLL file, but using the DLL search path, then just getting the path and passing it to CreateFile opens us up to a filesystem race condition, like the kind explained in this LiveOverflow video. A technique like this could be abused by a hacker so the handle doesn't actually point to the file we want. There isn't any GetMappedFileHandle function, so what can we do?
I thought about this for a while, and here is the workaround I came up with. The idea is that we call our own getFilePathNameFromMappedView function once just to get the path to pass to CreateFile, and exclusively lock the file in place with the FILE_SHARE_READ flag. However, we then confirm, with a second call to getFilePathNameFromMappedView, that the file is still actually there. If the paths match, knowing that the file at that path is now locked, we can know for sure the handle we got is to the library that was actually loaded. If the file was moved before the call to CreateFile finished, however, the paths will not match, because GetMappedFileName returns the up to date path to the file. At that point, we can try again. I decided to do this as a recursive function, but you could decide to throw an error and have the caller handle it.
inline bool stringsCaseInsensitiveEqual(const char* leftHandSide, const char* rightHandSide) {
return !_stricmp(leftHandSide, rightHandSide);
}
bool getHandleFromModuleHandle(HMODULE moduleHandle, HANDLE &file) {
if (!moduleHandle) {
return false;
}
HANDLE currentProcess = GetCurrentProcess();
// pass the Module Handle as a Mapped View
// to get its current path
std::string filePathName = "";
if (!getFilePathNameFromMappedView(currentProcess, moduleHandle, filePathName)) {
return false;
}
// prevent the Example File from being written to, moved, renamed, or deleted
// by acquiring it and effectively locking it from other processes
file = CreateFile(filePathName.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (!file || file == INVALID_HANDLE_VALUE) {
return false;
}
bool recursive = false;
{
// we now know this path is now protected against race conditions
// but the path may have changed before we acquired it
// so ensure the File Path Name is the same as before
// so that we know the path we protected is for the Mapped View
std::string _filePathName = "";
if (!getFilePathNameFromMappedView(currentProcess, moduleHandle, _filePathName)) {
goto error;
}
if (!stringsCaseInsensitiveEqual(filePathName.c_str(), _filePathName.c_str())) {
// race condition occured
recursive = true;
goto error;
}
}
return true;
error:
if (file && file != INVALID_HANDLE_VALUE) {
if (!CloseHandle(file)) {
return false;
}
file = NULL;
}
if (recursive) {
return getHandleFromModuleHandle(moduleHandle, file);
}
return false;
}
Then we can call it like this...
HMODULE exampleModuleHandle = LoadLibraryEx("Example.DLL", NULL, LOAD_LIBRARY_AS_DATAFILE);
if (!exampleModuleHandle) {
return false;
}
// we want this to be a handle to the Example File
HANDLE exampleFile = NULL;
if (!getHandleFromModuleHandle(exampleModuleHandle, exampleFile)) {
return false;
}
This is just something I thought of, so let me know in the responses if there are issues with it.
Once you have a handle to the file, it can then be passed to GetFileInformationByHandle to confirm it is the same library as is loaded in another process, and subsequently closed with CloseHandle.
I am using Windows 7 and I have to run one program in that windows but that program working in Windows XP. This is a Visual C++ program and I am using Visual Studio 2008 for this. When I am running my application, it does not throw any errors, but it does not create a directory in "c:\program files\". So can anyone help me to create directory and exe file?
This is the code I am using:
char szAppPath[MAX_PATH];
char szFileName[MAX_PATH];
DWORD dwResult;
WIN32_FIND_DATA FindFileData;
HANDLE hFind;
dwResult = ExpandEnvironmentStrings( NULL, szAppPath, MAX_PATH); // "%ProgramFiles%"
// do same for NSim directory
strcat(szAppPath,"\\NSim");
hFind = FindFirstFile(szAppPath, &FindFileData);
if (hFind == INVALID_HANDLE_VALUE)
{
//Directory Does't Exists create New
if(!CreateDirectory(szAppPath,NULL)) //Throw Error
{
MessageBox("Unable to Create N-SIM directory","NSim Installer");
return ;
}
}
else
{
//check if is directory or not
if(!(FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
{
MessageBox("Can't Create N-SIM directory\n Another file with same name exists","NSim Installer");
return ;
}
FindClose(hFind);
}
//***************************************N-SIM Application****************************
strcpy(szFileName, szAppPath);
HRSRC hRes;
if( bRegister == FALSE)
{
strcat(szFileName,"\\NSim.exe"); //make same name of the Client & Server in program file
hRes = FindResource(NULL, MAKEINTRESOURCE(IDR_LANSIMSERVER),RT_RCDATA);
if(flagUpgrade ==0)
{
CString trial = installationDate(); //----- Detemine Expiry Date -----
setRegistry(trial);
}
}
It's a file permissions issue, plain and simple. Programs can't just go rooting around system directories in Windows 7. That's why it works "properly" in Windows XP, but not in newer versions.
I can't tell for sure, but it looks like you're trying to write an installer. If so, why are you reinventing the wheel? There are tons of great setup utilities available—Visual Studio provides a setup project that you can customize to your needs, or look into Inno Setup, my personal favorite. A Google search will turn up plenty of other options that have already solved this problem for you, and innumerable others.
If this isn't an installer, and you're just trying to store application and/or user data in the Program Files folder, I highly recommend that you look elsewhere. You weren't supposed to shove data into the app folder under earlier versions of Windows, and Windows 7 just cuts you off at the knees if you do this. Your best bet is to follow the recommendations that existed from the beginning: Investigate the user and common Application Data folders carefully. Use the SHGetKnownFolderPath function to retrieve the full path to a known folder using its KNOWNFOLDERID. A couple of suggestions:
FOLDERID_ProgramData (a shared program data directory for all users)
FOLDERID_LocalAppData (a per-user program data directory, non-roaming)
FOLDERID_RoamingAppData (a per-user program data directory, roaming)
Alternatively, you can try running the application as an Administrator. You might want to look into creating a manifest that indicates the application requires administrator-level permissions to execute.
[edit] I edited the code in the question for readability and removed the commented out code (to see the wood for the trees). It is now obvious that nothing initialises szAppPath before calling strcat(), and calling ExpandEnvironmentStrings with NULL as the first argument is undefined (and certainly useless). Calling strcat() on an unitialised string is not likely to have the desired result. This may be an artefact of not posting the real code, or even of other peoples edits (including mine).
CreateDirectory sets the system error code on error; if you want to know what went wrong, check it! Any answer you get here will be an educated guess.
if(!CreateDirectory(szAppPath,NULL)) //Throw Error
{
DWORD errorcode = GetLastError();
LPVOID lpMsgBuf;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, errorcode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, NULL );
MessageBox(NULL, (LPCTSTR)lpMsgBuf, TEXT("Error"), MB_OK);
return ;
}
If you just want to get the error code and look it up manually, then a complete directory of codes is available on MSDN, here, I would guess that ERROR_ACCESS_DENIED
(5) is most probable. A more elaborate example of error code display is given here.
windows7?
Ok, the problem is not with your program. Its with the file system permissions in Windows 7. User programs cannot create files there.
I think the problem is lack of privileges. You can debug your project to see whether the CreateDirectory function sets an error as ERROR_ACCESS_DENIED, if it does, you should make your program run with an administrator privilege. Add manifest in your project to do so.
It is intended to protect your computer against attack. Well maybe. Or Microsoft deciding to tell you what you are and not allowed to do on your own computer.
In any case you can change your UAC settings if you really have to write there in that way although that obviously exposes you to risk.
Otherwise play nice and do things the Microsoft way, using a proper installer.