• Advertisement
Sign in to follow this  

[.net] ShellExecuteEx API call in C#

This topic is 3530 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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;
}


Share this post


Link to post
Share on other sites
Advertisement
Why don't you use System.Diagnostics.Process.Start ?
This will return a Process object.

Cheers

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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?

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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...

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
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;

Share this post


Link to post
Share on other sites
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();

Share this post


Link to post
Share on other sites
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 function
IntPtr 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?

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
you'll need to turn on ErrorDialog for openas to work

Share this post


Link to post
Share on other sites
I'd suggest not bothering with these monitoring threads at all. Your manager knows the directory or directories containing the data it manages. So when the app loads, take a sort of picture of the directory with like an array of FileInfo classes. Then just check them all for changes in the AppActivate event or OnIdle or both.

Nobody wants to be hassled by a utility program everytime they do File/Save in photoshop. But if you must have the thing imposing itself on your users, you could use a timer as well. I wouldn't do it, though. Because if you make your app annoying, your users will try to subvert its operations by doing things like making local copies of work files that your app won't know about at all.

This way, it wont matter if the user launches the editor from your app or not. As long as they are altering files in the monitored directories, you manager will know about it.

Share this post


Link to post
Share on other sites
Quote:
Original post by Anonymous Poster
you'll need to turn on ErrorDialog for openas to work


Ano's detailed version would be:

// There is no application associated with the specified file
ProcessStartInfo psi = new ProcessStartInfo(filename);
psi.UseShellExecute = true; // u need this
psi.ErrorDialog = true; // and this which launch the OpenWith dialog
Process.Start(psi);

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement