[.net] ShellExecuteEx API call in C#

Started by
11 comments, last by alvirtuoso 15 years, 11 months ago
I have successfully made a call to the Win32 API ShellExecuteEx function from my C# app (a game resource manager). I did this so that I can open resources from my app via a "Open With..." dialog (after extraction of the file to the TEMP folder). This also allows users to specify what application they want to use to open a file with an unknown extension (you know, the "Windows cannot open this file:" dialog). When a resouce file is opened, the resource editor creates a thread that monitors the file for changes. When changes are detected, the app asks the user if they want to update the archive with the changes (like WinZip). My next problem is getting a handle to the application process generated after the "Windows cannot open this file:" dialog. I want to be able to grab the process handle of Photoshop, Notepad, or whatever, so I know when the application closes and I can close the thread that monitors the file. The API documentation says to set the SHELLEXECUTEINFO structure's 'mask' property to SEE_MASK_NOCLOSEPROCESS. As you can see in the code below, I am doing this, but the hProcess property always returns a 0. I also overrode the form's WndProc() method so I can see if any related messages are received from the API (none are). A possible problem could be that ShellExecuteEx() returns immediately, and does not wait for the application-selection dialogs to be completed by the user. Obviously, it wouldn't know about any process if the property is getting set before the user picks an application. So, how can I get ShellExecuteEx() to return the process of the executed application? Also, if anyone knows of a better/easier way to accomplish all this (especially w/o calling the API directly), feel free to let me know! Here is the code I used to call ShellExecute:

class FileForm
{
	/* tons of other stuff */

	[DllImport("Shell32.dll")]
	public static extern int ShellExecuteEx(SHELLEXECUTEINFO lpExecInfo);

	protected override void WndProc(ref Message m)
	{
		base.WndProc(ref m);
	}

	private void OpenFile(string filepath)
	{
		if(File.Exists(filepath))
		{
			try
			{
				System.Diagnostics.Process.Start(filepath);
			}
			catch(System.ComponentModel.Win32Exception ex)
			{
				if( ex.NativeErrorCode == 1155 )	// 1155 = ERROR_NO_ASSOCIATION
				{
					const int SEE_MASK_NOCLOSEPROCESS = 64,		// 0x00000040
						SW_SHOWNORMAL = 1;

					SHELLEXECUTEINFO ei = new SHELLEXECUTEINFO();
					ei.cbSize = 60;			// sizeof(SHELLEXECUTEINFO);
					ei.fMask = SEE_MASK_NOCLOSEPROCESS;
					ei.lpVerb = "openas";
					ei.lpFile = filepath;
					ei.nShow = SW_SHOWNORMAL;			// 1 = SW_SHOWNORMAL

					int result = ShellExecuteEx(ei);
					if(result == 1)
						success = true;
				}
			}
		}
		else
			MessageBox.Show("File \"" + filepath + "\" not found!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
	}
}

[StructLayout(LayoutKind.Sequential)]
public class SHELLEXECUTEINFO 
{
	public int cbSize = 60;
	public int fMask = 0;
	public int hwnd = 0;
	public string lpVerb = null;
	public string lpFile = null;
	public string lpParameters = null;
	public string lpDirectory = null;
	public int nShow = 0;
	public int hInstApp = 0;
	public int lpIDList = 0;
	public string lpClass = null;
	public int hkeyClass = 0;
	public int dwHotKey = 0;
	public int hIcon = 0;
	public int hProcess = 0;
}


Advertisement
Why don't you use System.Diagnostics.Process.Start ?
This will return a Process object.

Cheers
As far as I can tell, System.Diagnostics.Process.Start() won't display the "Open With..." dialog, it merely throws a System.ComponentModel.Win32Exception. I was originally hoping that the "OpenWith" dialog would be one of the common windows dialogs, like the Open, Save, Font, Color and Print dialogs.
Well can you not just make your own Open With... Dialog or use an open file dialog window to prompt the user to open a file?
I've thought about implementing my own dialog, but that would be extra work that I'd rather not do. I won't have a choice if I can't figure this out, though :( Also, just as an FYI, the dialog I want is not used to select a file, but is used to select an application to open a previously-selected file. For example, if the ABC file extension has no assigned application in Windows, you would see this dialog when you double-click an ABC file. You would then use the Open With... dialog to specify what application (like Notepad) that you want to use to open the file with.

-Mike
Any ideas? I can't believe that this is not possible with the main .NET library, let alone calls to the Win32 API.

Any [additional] thoughts would be apprecited.
I found some information about using the "openas" verb when starting the process, but it hasn't worked in my tests. You could always add the needed information to the registry if opening the file fails...
You can get the "Open with..." dialog to appear using the Process managed class.

Process proc = new Process();
ProcessStartInfo info = new ProcessStartInfo("someFile.hello");
info.UseShellExecute = true; // here's the trick

proc.StartInfo = info;
That doesn't seem to work. I assumed that I need to Start() the process after initializing it using the AP's code. In addition, I tried using the "openas" verb. In any case, a Win32Exception is thrown on the call to Start():

"An unhandled exception of type 'System.ComponentModel.Win32Exception' occurred in system.dll

Additional information: No application is associated with the specified file for this operation"

Here is the exact code I used:

Process proc = new Process();
ProcessStartInfo psi = new ProcessStartInfo(filepath);
psi.UseShellExecute = true;
psi.Verb = "openas";
proc.StartInfo = psi;
proc.Start();
The approach I have been playing with recently has been to call a function in a seperate, unmanaged C++ DLL. I know this is overkill, but it is the only method that seems to work. Here is the code for the function in my ShellExecuter.DLL:

#define WIN32_LEAN_AND_MEAN#include <windows.h>#include <shellapi.h>#include <stdio.h>extern "C" int __declspec(dllexport) OpenFileWith(LPCTSTR filepath, HWND parent, HANDLE* process){	SHELLEXECUTEINFO sei;	ZeroMemory(&sei, sizeof(SHELLEXECUTEINFO));	sei.cbSize = sizeof(SHELLEXECUTEINFO);	sei.fMask = SEE_MASK_NOCLOSEPROCESS;	sei.lpVerb = "openas";	sei.lpFile = filepath;	sei.nShow = SW_SHOWNORMAL;	sei.hwnd = parent;	int result = ShellExecuteEx(&sei);	*process = sei.hProcess;	return result;}


Here is the .NET code that uses it:
// declare the DLLImport function[DllImport("ShellExecuter.dll")]public static extern int OpenFileWith(string filepath, IntPtr parent, ref IntPtr process);// calling the functionIntPtr process = IntPtr.Zero;int result = OpenFileWith(filepath, Handle, ref process);


This runs great - it opens the "Open With" dialog and returns the process handle to my app. My only problem now is that I'm not sure what to do with the process handle. Can I simply cast it to a System.Diagnostics.Process object?

This topic is closed to new replies.

Advertisement