Thread: Threads, synchronisation and temporary files

  1. #1
    Confused Magos's Avatar
    Join Date
    Sep 2001
    Location
    Sweden
    Posts
    3,145

    Threads, synchronisation and temporary files

    I'm having this odd error. In my program you can open files that rely on temporary files, thus I need a way to automatically remove them when the (external) program closes. By this I mean that my program A opens a file B somewhere on the disc and the file B is opened with its associated program C. When C is closed, the file B is automatically removed.

    So far this works fine by using WaitForSingleObject() on the process handle you get from the call to ShellExecuteEx().
    It works almost perfectly fine, however in certain cases when running a file it tends to open in the same program as an existing file instead of creating a new. One example is mp3's, if you have an mp3 loaded in winamp and try to open another it's loaded in the existing winamp.

    When this happens the last opened file jumps out of WaitForSingleObject() almost immediately instead of waiting until winamp closes. Here lies my problem, why does it exit immediately? Is it impossible to have 2 separate threads waiting for the same program to close or am I just making some mistake?

    It's a lot of source code, but I guess some of the relevant parts are here:

    (Just a structure passed to the thread function)
    Code:
    struct FILE_PROCESS
    {
    	FILE_PROCESS(HANDLE NewHandle, CONST std::string& NewFileName)
    	{
    		Handle = NewHandle;
    		FileName = NewFileName;
    	}
    	
    	HANDLE Handle;
    	std::string FileName;
    };
    (Thread that removes the file when closed)
    Code:
    DWORD CALLBACK DestroyFileWhenNeccessary(DWORD Parameter)
    {
    	std::string FileName;
    	HANDLE ProcessHandle;
    	FILE_PROCESS* FileProcessPointer;
    
    	FileProcessPointer = reinterpret_cast<FILE_PROCESS*>(Parameter);
    	if(FileProcessPointer == NULL)
    	{
    		ExitThread(0);
    		return 0;
    	}
    
    	ProcessHandle = FileProcessPointer->Handle;
    	FileName = FileProcessPointer->FileName;
    
    	delete FileProcessPointer;
    
    	if(WaitForSingleObject(ProcessHandle, INFINITE) == WAIT_FAILED)
    	{
    		ExitThread(0);
    		return 0;
    	}
    
    	MpqWindow.RemoveTemporaryFile(FileName);
    
    	ExitThread(0);
    	return 0;
    }
    (code that loads the file and creates the thread)
    Code:
    	if(!ExportMpqFile(FileName, TemporaryFileName))
    	{
    		return FALSE;
    	}
    
    	AddTemporaryFile(TemporaryFileName);
    
    	ZeroMemory(&Info, sizeof(SHELLEXECUTEINFO));
    	Info.cbSize = sizeof(SHELLEXECUTEINFO);
    	Info.fMask = SEE_MASK_NOCLOSEPROCESS;
    	Info.hwnd = Window;
    	Info.lpVerb = "open";
    	Info.lpFile = TemporaryFileName.c_str();
    	Info.hInstApp = GetModuleHandle(NULL);
    	Info.nShow = SW_SHOW;
    
    	if(ScriptExtention(FileName))
    	{
    		Info.lpFile = "Notepad";
    		Info.lpParameters = TemporaryFileName.c_str();
    	}
    
    	if(!ShellExecuteEx(&Info))
    	{
    		Error.SetMessage("Unable to open \"" + TemporaryFileName + "\"!\nProbably an unassociated extention!");
    		RemoveTemporaryFile(TemporaryFileName);
    		return FALSE;
    	}
    
    	ZeroMemory(&Attributes, sizeof(SECURITY_ATTRIBUTES));
    	Attributes.nLength = sizeof(SECURITY_ATTRIBUTES);
    
    	ThreadHandle = CreateThread(&Attributes, 0, reinterpret_cast<LPTHREAD_START_ROUTINE>(DestroyFileWhenNeccessary),
    								new FILE_PROCESS(Info.hProcess, TemporaryFileName), 0, &ThreadId);
    	if(ThreadHandle == NULL)
    	{
    		Error.SetMessage("Unable to create thread!");
    		RemoveTemporaryFile(TemporaryFileName);
    		return FALSE;
    	}
    Last edited by Magos; 03-03-2005 at 09:49 AM.
    MagosX.com

    Give a man a fish and you feed him for a day.
    Teach a man to fish and you feed him for a lifetime.

  2. #2
    Yes, my avatar is stolen anonytmouse's Avatar
    Join Date
    Dec 2002
    Posts
    2,544
    Quote Originally Posted by MSDN
    SEE_MASK_NOCLOSEPROCESS
    Use to indicate that the hProcess member receives the process handle. This handle is typically used to allow an application to find out when a process created with ShellExecuteEx terminates. In some cases, such as when execution is satisfied through a DDE conversation, no handle will be returned. The calling application is responsible for closing the handle when it is no longer needed.

    ...

    hProcess
    Handle to the newly started application. This member is set on return and is always NULL unless fMask is set to SEE_MASK_NOCLOSEPROCESS. Even if fMask is set to SEE_MASK_NOCLOSEPROCESS, hProcess will be NULL if no process was launched. For example, if a document to be launched is a URL and an instance of Microsoft Internet Explorer is already running, it will display the document. No new process is launched, and hProcess will be NULL.
    Off the top of my head I can't think of a nice solution for this problem. I know Firefox just leaves the temporary files until I go in and manually delete them.

    On another note spinning up a thread to wait on a single handle is not the most efficient operation. It probably doesn't matter in this case as there is not going to be many handles, but for future cases it may be worth checking out RegisterWaitForSingleObject (Windows 2000+, see Thread Pooling Article Scenario 3: Calling Functions when Single Kernel Objects Become Signaled) or WaitForMultipleObjects.

  3. #3
    Confused Magos's Avatar
    Join Date
    Sep 2001
    Location
    Sweden
    Posts
    3,145
    I checked if hProcess was NULL and then stored the filenames in a list so I could remove all temporary files when the program closes. This worked fine for html files that were opened in the same browser, however it did not work for mp3's (hProcess != NULL).

    Thanks anyway!
    MagosX.com

    Give a man a fish and you feed him for a day.
    Teach a man to fish and you feed him for a lifetime.

  4. #4
    Registered User Codeplug's Avatar
    Join Date
    Mar 2003
    Posts
    4,981
    My version of WinAmp does not use DDE - so the shell will spawn a new instance of winamp.exe - at which point the second instance is probably notifying the primary instance to take care of it.

    gg

Popular pages Recent additions subscribe to a feed