CreateFile中的dwFlagsAndAttributes使用FILE_FLAG_DELETE_ON_CLOSE

解决方案 »

  1.   

    能,在程序结束之前用WinExec()调用另一个程序,通过另一个程序来删除他
      

  2.   

    REDFIRE:删除文件不行的
    PLAYPCGAME:如此做法你试过吗?不会报错?
    ZYOUJIE:与其如此,不如手工删除!或者使用批处理文件!我是说“自杀”!
    CELXTA:如何实现?
      

  3.   

    看看这篇文章:
    As I'm sure you are aware, the following code does not work correctly:
    TCHAR szEXEPathname[_MAX_PATH];
    GetModuleFileName(NULL, szEXEPathname, _MAX_PATH);
    DeleteFile(szEXEPathname);
    The above lines of code get the full pathname of the currently running executable and then attempt to delete this file by calling DeleteFile.  Because Windows 95 and Windows NT load EXE files into memory as memory-mapped files, the system opens the EXE file when the process is started and automatically closes the file when the process terminates.  When DeleteFile is called, the system sees that the EXE file is still opened, DeleteFile returns FALSE, and a subsequent call to GetLastError returns 5 which identifies an access violation error (ERROR_ACCESS_DENIED).
    Well, now we know that the straight-forward technique isn抰 going to work.  At this point I started to think of many different ways to solve this problem.  Each solution has its advantages and disadvantages.  For the remainder of this article, won抰 you please join me as I take you down the path that I took in order to solve this problem.
    The first thing that popped into my mind when solving this problem is the little-known MoveFileEx function:
    BOOL MoveFileEx(LPCTSTR lpExistingFileName, LPCTSTR lpNewFileName, DWORD dwFlags); 
    This function allows you to move a file from one directory to another directory.  However, if you pass NULL for the lpNewFileName parameter, you are telling the system to move the file to nowhere.  This is the same thing as telling the system to delete the file and is basically the same thing as calling DeleteFile. Based on what I抳e said so far, there is no reason to think that MoveFileEx would be any better of a solution to calling DeleteFile.  However, MoveFileEx抯 third parameter, dwFlags, allows us to specify flags that alter the behavior slightly.  I will not discuss all of the flags here but I will mention the MOVEFILE_DELAY_UNTIL_REBOOT flag.  This flag tells the system that the file is not to be moved (deleted) until the system is rebooted.  When the system is rebooted, the UNSETUP.EXE file will not be in use, the system will delete the file, and we have solved the problem proposed to us.
    Unfortunately, there are 3 problems with the MoveFileEx technique.  First, it doesn抰 remove the subdirectory that contains the file. The second problem with the MoveFileEx solution is that the file is not deleted immediately. I know several people that have had Windows NT running continuously over a full year without ever rebooting!  If these people install and un-install several applications, their hard drives would soon start to be populated with a whole bunch of UNSETUP.EXE files.  The third and final problem with MoveFileEx is the biggest problem of all: the function is not implemented on Windows 95.
    However, Windows 95 does offer a technique that does allow you move/delete a file when the user reboots.  When Windows 95 boots, it run an application called WININIT.EXE.  This application looks for a file called WININIT.INI.  If this file exists, WININIT.EXE then looks for a section within the file called [Rename].  A sample WININIT.INI appears below:
    [Rename]
    NUL=C:\Program Files\Win95ADG\UNSETUP.EXE
    NUL=C:\Program Files\SomeApp\UNSETUP.EXE
    C:\Calc.exe=C:\Windows\Calc.exe
    Every line in the [Rename] section, indicates a file that needs to be moved/deleted.  The name on the left of the equal sign indicates the new pathname of the file and the name on the right of the equal sign indicates the current pathname of the file.  If the name on the left of the equal sign is NUL, WININIT.EXE deletes the file.  Because WININIT.INI抯 contents look very much like any old-style INI file, you might be tempted to use the WritePrivateProfileString function in order to add entries to the [Rename] section.  However, you should not do this because the WritePrivateProfileString function ensures that no more than one entry in a section can have NUL to the left of the equal sign.  If every UNSETUP.EXE program used WritePrivateProfileString to add its entry to the WININIT.INI file the following scenario could occur:  The user runs your application抯 UNSETUP.EXE and you insert an entry into the WININIT.INI file.  Then, before rebooting, the user decides to un-install another application.  This other application抯 UNSETUP.EXE program also uses the WritePrivateProfileString function to add its entry to the WININIT.INI file.  But because WritePrivateProfileString doesn抰 allow more than one entry to begin with NUL=, your entry is deleted from the file and your application抯 UNSETUP.EXE file will never be deleted from the user抯 hard drive.
    The ReplaceFileOnReboot function shown in RFoR.C (Listing 1) and prototyped in RFoR.H (Listing 2) abstracts the implementation problems of moving/deleting a file on reboot between Windows 95 and Windows NT. The function first tries to call MoveFileEx.  If this function fails, it then opens (or creates) a WININIT.INI file and properly adds entries to the [Rename] section.  Of course, in a future version of Windows 9x, Microsoft will fully support the MoveFileEx function.  I have written the ReplaceFileOnReboot function so that it always calls MoveFileEx so that it will work correctly on future versions of Windows 9x.
    However, since I didn抰 like the fact that the ReplaceFileOnReboot function still forces the user to reboot in order to delete the UNSETUP.EXE file, I needed to try something else.  Here is the code I used for my next attempt:
    int WINAPI WinMain (HINSTANCE hinstExe, HINSTANCE hinstExePrev,
       LPSTR lpszCmdLine, int nCmdShow) {   TCHAR szEXEPathname[_MAX_PATH];
       HANDLE hfile;   GetModuleFileName(NULL, szEXEPathname, _MAX_PATH);
       hfile = CreateFile(szEXEPathname, GENERIC_READ, FILE_SHARE_READ,
          NULL, OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL);
       CloseHandle(hfile);
       return(0);
    }
    For this technique, my thinking was that the EXE file is already open due to the running process.  I will go and open the file a second time by calling CreateFile using the FILE_FLAG_DELETE_ON_CLOSE flag. This flag tells the system that the file should be deleted when it is no longer in use. After CreateFile returns, I immediately call CloseHandle assuming that the system would remember that I wanted the file deleted when it was no longer in use at all.  So, when the process terminated, I expected the system to automatically delete the file.
    After writing the small test application and experimenting with it, I soon discovered that this technique doesn抰 work at all. When I debugged the program under Windows 95, I saw that the call to CreateFile was succeeding but the system was just ignoring my request to open the file with delete-on-close access.  When I closed the file and terminated the process, the executable file remained on my hard disk.
    I then went and tested my application under Windows NT.  On Windows NT, the call to CreateFile fails returning INVALID_HANDLE_VALUE and GetLastError returns ERROR_ACCESS_DENIED.  This is because Windows NT supports a file access called FILE_SHARE_DELETE which is very similar to the FILE_SHARE_READ and FILE_SHARE_WRITE accesses.  But, FILE_SHARE_DELETE is not documented in the Win32 SDK documentation; however, it can be found in the Windows NT DDK抯 NTDDK.H header file as follows:
    #define FILE_SHARE_DELETE               0x00000004
    So, using this flag, I changed the call to CreateFile so that it read as follows:
    hfile = CreateFile(szEXEPathname, GENERIC_READ, FILE_SHARE_DELETE | FILE_SHARE_READ,
          NULL, OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL); 
    This did not help me at all because Windows NT maps the executable file image into memory without specifying the FILE_SHARE_DELETE access.  So, when I attempt to open the file with this access, CreateFile again returns INVALID_HANDLE_VALUE and GetLastError returns ERROR_ACCESS_DENIED.
    Taking the failure of my previous technique in stride, I started working on my next idea.  Somehow, I need to execute code in my process抯 address space without have my EXE file loaded anymore.  After thinking this to myself, I drew a comparison with DLLs.  DLLs can be dynamically loaded and unloaded as desired simply by calling LoadLibrary and FreeLibrary respectively.  All I need to do is to dynamically unload my EXE file from my address space just like I would unload a DLL.  You may recall that the FreeLibrary function,
    BOOL FreeLibrary(HINSTANCE hinstDll);
    takes a single parameter: the HINSTANCE (or HMODULE) of a loaded DLL.  In Win32 programs, the HINSTANCE of a DLL is the virtual memory address of where the DLL got loaded into a process抯 address space.  This is true of EXE files as well: the hinstExe parameter passed to WinMain identifies the virtual address of where the EXE file got loaded.  So, it stands to reason that if I call FreeLibrary and pass in the EXE抯 hinstExe value, the system should unload the EXE file from my process抯 address space, right?!
    So, my code to test this theory looks like this:
    int WINAPI WinMain (HINSTANCE hinstExe, HINSTANCE hinstExePrev,
       LPSTR lpszCmdLine, int nCmdShow) {   TCHAR szEXEPathname[_MAX_PATH];
       GetModuleFileName(NULL, szEXEPathname, _MAX_PATH);
       FreeLibrary(hinstExe);
       DeleteFile(szEXEPathname);
       return(0);
    }
    The code seems simple enough but there is one really big problem with this.  If you build this sample code and run it on Windows 95, you抣l consistently get access violations and, of course, the UNSETUP.EXE file will not be deleted from the user抯 hard drive.  This was a fun sample to code and execute inside a debugger.  When you debug this application, you抣l want to keep the EXE file visible in the debugger抯 memory window.  Then, watch what happens when you execute the FreeLibrary call.  FreeLibrary actually does unload the EXE file from the process抯 address space.  You can easily see this because the contents of the debugger抯 memory window changes to all questions s. But then, FreeLibrary returns and the code representing the call to DeleteFile is gone and this is what causes the access violation.
    This is a relatively easy problem to solve especially if you remember my Inject Library article form the May 1994 issue of MSJ [Editor抯 note: Jeff抯 Advanced Windows book has the most recent version of this code and some bug fixes have been made since the article appeared].  In this article, I describe a technique that allows you to dynamically inject code into another process抯 address space and execute this injected code. This injected code in the remote process did not come from a DLL or from an EXE file but instead comes from copying bytes from the local process抯 address space into the remote process抯 address space.  For our problem, we just need a stripped down version of the Inject Library code.
    You can find this code in the DelExe.C file Listing 3) and the prototypes for this function in the DelExe.H (Listing 4).  For a full understanding of what this code does, please refer to my Inject Library article.  However, I will briefly describe what this module does.  Basically, the DeleteExecutable function first calls HeapAlloc to allocate a block of memory in our process抯 address space.  Then, a function is copied from our EXE file into the dynamically allocated block of memory.  The rules that this function must follow are spelled out in the Inject Library article.  Next, a DELEXEINFO data structure is initialized with the full pathname of the process抯 EXE file, a flag indicating whether the containing subdirectory should also be deleted, and the address of the functions (in KERNEL32.DLL) that are going to be called from the code in the dynamically allocated memory block.
    After the data structure is initialized, the code in the memory block is called and is passed the address of the DELEXEINFO structure (which is on the thread抯 stack).  The code in the memory block uses the information in the DELEXEINFO structure to call FreeLibrary to unload the EXE file, then DeleteFile to delete the EXE file (which will succeed now), and then call ExitProcess so that the process terminates.  It would be a bad mistake to allow the code in the memory block to return because the calling code no longer exists after FreeLibrary unloads the EXE file.
    In order to use my function, all you have to do is include the DelExe.H header file and add the DELEXE.C source file to your project.  Then in your code, just place a call to my DeleteExecutable function whose prototype looks like this:
    void WINAPI DeleteExecutable(DWORD dwExitCode, BOOL fRemoveDir);
    The first parameter is your process抯 exit code and the second parameter tells the function whether it should also attempt to remove the subdirectory that contains the UNSETUP.EXE file.  The function is prototyped as returning void because the function will never return.
    This is my favorite solution because it deletes the EXE file immediately.  But now I have some bad news: this method doesn抰 work on Windows NT.  The reason why it doesn抰 work is because Windows NT doesn抰 allow FreeLibrary to unload the process抯 EXE file. At first, I thought that Windows NT just sets a really big usage count on the EXE file module.  So, I can call FreeLibrary passing the hinstExe value continuously in a loop.  Each call to FreeLibrary decrements the EXE file抯 usage count until the EXE file is unloaded.  I can detect this because FreeLibrary will return FALSE if I pass an  address that does not represent a module.  The pseudo-code would be something like this:
    while (FreeLibrary(hinstExe))
    ;
    DeleteFile(szEXEPathname);
    I coded this up and tested in on Windows NT.  After several hours, the while loop still had not terminated.  So, either Windows NT just doesn抰 allow FreeLibrary to affect an EXE or this loop will take too long to execute and the performance of the UNSETUP.EXE would be too horrendous to ship this CPU-intensive solution anyway.  By the way, I also tried this (again, in pseudo-code):
    TCHAR szEXEPathname[_MAX_PATH];
    GetModuleFileName(NULL, szEXEPathname, _MAX_PATH);
    LoadLibrary(szEXEPathname);
    LoadLibrary(szEXEPathname);
    FreeLibrary(histExe);
    DeleteFile(szEXEPathname);
    thinking that Windows NT uses an unsigned value for the EXE file抯 usage count and initializes this count to 0xFFFFFFFF.  So, if I call LoadLibrary twice, the count would now become 1 and then the call to FreeLibrary decrements the count back to zero hopefully unloading the EXE file. This didn抰 work either.  I could not find anyway to get this technique to work under Windows NT.
    After all of this, I decided on a brute-force method that consistently works and works on both Windows 95 and Windows NT: batch files.  Batch files have the unique ability to delete themselves.  If you create the following batch file
    del %0.bat
    and run it at the command prompt, the batch file will delete itself, and then the following error message appears:
    The batch file cannot be found.
    This error message is harmless.  The DeleteExecutableBF function is in the DelExeBF.C file (Listing 5) and is prototyped in the DelExeBF.H file (Listing 6).  This function creates a batch file that kills the running executable program. If the executable is called UNSETUP.EXE and resides in the C:\Win95ADG subdirectory, the contents of the batch file looks like this:
    :Repeat
    del "C:\Win95ADG\UNSETUP.EXE"
    if exist "UNSETUP.EXE" goto Repeat
    rmdir "C:\Win95ADG"
    del "\DelUS.bat"
    This batch file attempts to delete the UNSETUP.EXE file.  If the file is not deleted, because it is still executing, the batch file attempts to delete the file again.  When the executable file no longer exists, the batch file removes the subdirectory that contained the file and then, the batch file deletes itself.
    You抣l notice that the DeleteExecutableBF function does a lot of work in order to run the batch file.  The system always executes batch files inside a console window.  But, there is no need for the user to know that we are running batch file and therefore I set the STARTUPINFO抯 wShowWindow member to SW_HIDE.
    Another thing to be aware of is that the batch file polls for the existence of the executable file.  In a pre-emptive, multi-threaded environment, polling is an awful thing to do because you are causing the system to waste precious CPU cycles. In an application, you should always go out of your way to call the WaitForSingleObject or WaitForMultipleObjects functions so that the system does not waste CPU time on you until you are able to do whatever you need to.  In batch files, there is no way to call these functions and therefore I am must use a polling technique. 
    However, I can adjust the priority class and relative thread priorities of the processes and threads involved with my solution.  When the DeleteExecutableBF function calls CreateProcess to spawn the batch file, I specify both the CREATE_SUSPENDED and IDLE_PRIORITY_CLASS flags.  The CREATE_SUSPENDED flag tells the system to create the console window (which will be invisible) to run the batch file but the system is not allowed to schedule it any CPU time yet.  The IDLE_PRIORITY_CLASS flag tells the system that this process should not be schedule CPU time frequently. This way fewer CPU cycles will be wasted by the batch file while it polls for the executable file to terminate.
    After CreateProcess returns, I explicitly set its primary thread抯 relative thread priority to THREAD_PRIORITY_IDLE.  This further reduces the amount of CPU time that will be wasted by the batch file.  Then, set the executable thread抯 relative priority to time critical and the executable抯 priority class to high.  This causes the system to schedule CPU time to the executable抯 thread very frequently so that it can terminate as soon as possible.  Note that I do not set the priority class to real-time because this would interfere with threads that are responsible for processing various hardware events.
    Now, after all of the thread priorities have been adjusted, I close the handle to the batch file抯 process and then, I resume the batch file抯 thread.  This allows the system to schedule CPU time to execute the batch file; but, the system will not schedule a lot of time to the batch file because its priority is so low.  Finally, we close the handle to the batch file抯 thread and return from the DeleteExecutableBF function.  At this point, we want our executable process to terminate as soon as possible so that the batch file will terminate, stop polling, and stop wasting CPU cycles.
    The TDelExe.C file (Listing 7) is a small application that simply tests all of the techniques presented throughout this article.  When you invoke it, it prompts the user with a message box (Figure 1) as to how the user would like to delete the executable file.  Of course, you can only run the program once since the EXE file is destroyed when the process terminates.  If you can抰 easily rebuild the code, I suggest you make a backup of the original EXE file before running it each time
      

  4.   

    zhou007:
        My God!你真伟大!!!!!!!!!!!!!!!愿主保佑你!!!!!!!
        
      

  5.   

    bensilver:这篇文章是成功的。但我觉得重要的事,这篇文章道出了我们程序员再写程序的过程中遇到问题是怎样解决的。我觉得深有同感。
    注:文章出处:msdn技术文档
      

  6.   

    在msj上Jeffrey Richter对这个问题做过专门的讨论。
      

  7.   

    msj Q&A win32, January 1996
    msj Q&A win32, January 1998
      

  8.   

    用.bat的方法好象在Windows Me下不行吧,Me里没有DOS了.