Исходники.Ру - Программирование
Исходники
Статьи
Книги и учебники
Скрипты
Новости RSS
Магазин программиста

Главная » Статьи по программированию » .NET - Все статьи »

Обсудить на форуме Обсудить на форуме

Перехват ShellExecuteEx в .NET
ShellExtension.zip (88,48 Kb)

В этой статье будет описан метод реализации интерфейса IShellExecuteHook с целью перехвата вызовов функции ShellExecuteEx используемой оболочкой Windows. Это позволит написать простейшей расширение оболочки Windows

Введение

В этой статье будет описан метод реализации интерфейса IShellExecuteHook с целью перехвата вызовов функции ShellExecuteEx используемой оболочкой Windows. Это позволит написать простейшей расширение оболочки Windows (подробнее о расширения оболочки и написании расширений с помощью Visual C++ и библиотеки ATL смотрите статьи Michael Dunn).

Создание COM компонента

Для того, чтобы создать COM компонент программисту не нужно прилагать лишних усилий и писать какой бы то ни было дополнительный код, достаточно в свойствах сборки (Build) проекта в Visual Studio .NET установить значение Register for COM Interop в True.

Интерфейс IShellExecuteHook

Интерфейс IShellExecuteHook описывает функции ShellExecute и ShellExecuteEx используемые оболочкой Windows. Например, когда пользователь вводит команду используя пункт меню Run (Выполнить) в меню Start (Пуск), либо при двойном щелчке по файлу. Таким образом, перехватив вызов функции мы можем получить имя файла, директории, либо параметры командной строки и выполнить какое-либо действие, либо заставить оболочку не выполнять никаких действий.

Интерфейс описан в заголовочном файле shlobj.h, входящим в Platform SDK.

DECLARE_INTERFACE_(IShellExecuteHookW, IUnknown) // shexhk
{
    // *** IUnknown methods ***
    STDMETHOD(QueryInterface) (THIS_ REFIID riid, void **ppv) PURE;
    STDMETHOD_(ULONG, AddRef) (THIS)  PURE;
    STDMETHOD_(ULONG, Release) (THIS) PURE;

    // *** IShellExecuteHookW methods ***
    STDMETHOD(Execute)(THIS_ LPSHELLEXECUTEINFOW pei) PURE;
};

Рассмотрим функцию Execute:

HRESULT Execute( 
    LPSHELLEXECUTEINFO pei
);

Функция возвращает S_OK, если оболочка не должна передавать объект в дальнейшую обработку (это значение можно использовать для того, чтобы информировать оболочку о том, что расширение обработало объект, либо в случае, когда дальнейшая обработка нежелательна) либо S_FALSE в противном случае.

Аргумент pei представляет собой структуру SHELLEXECUTEINFO, содержащую информацию о вызываемом обекте. Заглянув в заголовочный файл shellapi.h найдем описание этой структуры:

typedef struct _SHELLEXECUTEINFO {
    DWORD cbSize;
    ULONG fMask;
    HWND hwnd;
    LPCTSTR lpVerb;
    LPCTSTR lpFile;
    LPCTSTR lpParameters;
    LPCTSTR lpDirectory;
    int nShow;
    HINSTANCE hInstApp;
    LPVOID lpIDList;
    LPCTSTR lpClass;
    HKEY hkeyClass;
    DWORD dwHotKey;
    union {
        HANDLE hIcon;
        HANDLE hMonitor;
    } DUMMYUNIONNAME;
    HANDLE hProcess;
} SHELLEXECUTEINFO, *LPSHELLEXECUTEINFO;

Для полноты изложения приведу описание аргументов:

  • cbSize - размер структуры в байтах
  • fMask - комбинация флагов указывающая на содержание других членов структуры, описание флагов можно посмотреть в MSDN, для данной статьи оно не важно
  • hwnd - описатель окна любого сообщения, которое система может показать в результате обработки этого объекта
  • lpVerb - описывает действие, которое нужно выполнить, например: "open", "edit", "explore"
  • lpFile - указатель на имя файла или объекта
  • lpParameters - указатель на список параметров
  • lpDirectory - указатель на имя рабочей директории, если поле не заполнено, это означает, что текущая директория является рабочей
  • nShow - комбинация флагов указывающая как должно отобразиться окно приложения, обрабатывающего данный тип объектов (файлов), за списком желающие могут обратиться в MSDN
  • hInstApp - по окончании обработки, расширение может установить одно из допустимых значений, информирующее оболочку о результате обработки. Например, SE_ERR_FNF - файл не найден, SE_ERR_PNF- путь на найден и т.п.

Остальные параметры зависят от флагов, установленных в fMask. Опять же, интересующиеся могут посмотреть описание в MSDN. Данная статья лишь демонстрирует пример реализации интерфейса и не рассматривает все возможные варианты.

Реализация в C#

Надеюсь, что Вы не успели заскучать рассматривая описание структур C++. Вы-то ждали .NET. Вот и дождались. Сейчас мы "переведем" приведенные выше описания C++ в описания структур и методов .NET. Также, как это описано далее, Вы можете реализовать любой интерфейс.

Для начала нам понадобится GUID интерфейса, который мы хотим реализовать. За ним мы залезем в заголовочный файл ShlGuid.h, входящий в Platform SDK и найдем строку:

DEFINE_SHLGUID(IID_IShellExecuteHookW, 0x000214FBL, 0, 0);

В более привычном и знакомом виде: {000214FB-0000-0000-C000-000000000046}. Это значение нам понадобиться для импорта COM интерфейса в C#:

[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), 
Guid("000214FB-0000-0000-C000-000000000046")]
public interface IShellExecuteHook{
    [PreserveSig()]
    int Execute(SHELLEXECUTEINFO pei);
}

Поскольку IShellExecuteHook наследует IUnknown, мы указываем тип COM интерфейса InterfaceIsIUnknown. Аттрибут PreserveSig() указывается для того, чтобы возвращаемый параметр рассматривался именно как параметр стандартного типа HRESULT и не претерпевал каких-либо преобразований.

Затем опишем наш COM класс, для этого нам понадобиться уникальный GUID, который можно получить воспользовавшись стандартной утилитой, входящей в поставку Visual Studio .NET - Create GUID.

Получив GUID опишем класс, который будет доступен как COM:

[Guid("706CAD5F-0DD4-4477-B8DB-1FE4AF1C44B2"), ComVisible(true)]
public class SimpleExt : IShellExecuteHook 
{
}

Данный класс должен содержать только один метод Execute, в реализации которого есть только одна сложность - необходимо перевести структуру SHELLEXECUTEINFO из C++ в C# и привести неуправляемые типы к управляемым. Для того, чтобы члены структуры размещались в памяти в указанном порядке, необходимо указать атрибут [StructLayout(LayoutKind.Sequential)]. Приведем типы и опишем структуру:

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

Учтите, что для соответствия управляемого варианта структуры неуправляемому необходимо явно указать, что строки должны передаваться как неуправляемые, для чего используется атрибут MarshalAs(UnmanagedType.LPWStr).

Остается самая малость - определить типы возвращаемых значений, чтобы код реализации функции еще больше напоминал стандартный код COM класса. Типов возвращаемых значений в нашем случае всего два S_OK и S_FALSE, опишем их непосредственно в классе:

	private int S_OK= 0;
	private int S_FALSE= 1;

Напишем код для функции Execute:

public int Execute(SHELLEXECUTEINFO pei) 
{
	try 
	{
		switch(pei.lpFile)
		{
			case "msgbox":
				MessageBox.Show(pei.lpParameters);
				return S_OK;
				case "time":
				MessageBox.Show(DateTime.Now.ToString());
				return S_OK;
				default:
				if(pei.lpFile.IndexOf (".txt") != -1)
				{
					MessageBox.Show(
					"You can not open text files! Acces to file: "
					 + pei.lpFile + " denied!");
					return S_OK;
				}
		}
		
	}
	catch(FormatException) 
	{
		return S_FALSE;
	} 
	catch(Exception ex) 
	{
		MessageBox.Show(ex.Message);
			
	}
	return S_FALSE;
	}

Главное, при реализации данной функции, не забыть возвращать значение S_FALSE в случаях, когда обработка не нужна. В качестве небольшого эксперимента можете попробовать в начале функции возвращать S_OK. После этого Вы не сможете заставить оболочку запустить какое-либо приложение или какую-либо команду без удаления обработчика. Будьте осторожны!

Регистрация "перехватчика"

Для того, чтобы установить наш COM компонент, перехватывающий функцию ShellExecuteEx, необходимо зарегистрировать сам компонент (как обычно), а также указать его GUID в ветке реестра, предназначенной для регистрации компонентов:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\ShellExecuteHooks

Для этого создадим простейшую утилиту регистрации, которая будет регистрировать и удалять регистрационные данные из реестра. Вот два используемых метода, реализующих эту функциональность:

private void btnReg_Click(object sender, System.EventArgs e)
{	
	try 
	{
		Assembly mAsm = Assembly.LoadFrom (txtPath.Text); 
		RegistrationServices mReg = new RegistrationServices();
		mReg.RegisterAssembly(mAsm, 0);
		RegistryKey key;
		key = Registry.LocalMachine.OpenSubKey(
		@"Software\Microsoft\Windows\CurrentVersion\Explorer\ShellExecuteHooks", true);
		key.SetValue(clsid, "SimpleExtension");
		key.Close();
	}
	catch(Exception ex) 
	{
		MessageBox.Show (ex.Message);
	}
	}
private void btnUnreg_Click(object sender, System.EventArgs e)
{
	try 
	{
		Assembly mAsm = Assembly.LoadFile (txtPath.Text); 
		RegistrationServices mReg = new RegistrationServices();
		mReg.UnregisterAssembly(mAsm);
		RegistryKey key;
		key = Registry.LocalMachine.OpenSubKey(
		@"Software\Microsoft\Windows\CurrentVersion\Explorer\ShellExecuteHooks", true);
		key.DeleteValue (clsid);
		key.Close();
	}
	catch(Exception ex) 
	{
		MessageBox.Show (ex.Message);
	}
}

Для начала откройте исходный код прилагаемый к статье, затем соберите решение и используя утилиту регистрации зарегистрируйте наше простейшее расширения оболочки. Наслаждайтесь! Вот как выглядит утилита:

А вот так работает наше расширение:

Попробуйте просто ввести команду time до установки расширения и после установке расширения. Разница видна сама собой.

Заключение

В данной статье был рассмотрен способ перехвата функции ShellExecuteEx, а также попутно рассказано о том, как перевести описание методов и структур из С++ в C#, а также реализовать стандартный интерфейс в собственном COM компоненте.

Если у Вас возникнут какие-то вопросы, Вы можете написать мне письмо gaidar@vbstreets.ru или задать вопрос в форуме bbs.vbstreets.ru.


Может пригодится:


Автор: gaidar
Прочитано: 6843
Рейтинг:
Оценить: 1 2 3 4 5

Комментарии: (0)

Добавить комментарий
Ваше имя*:
Ваш email:
URL Вашего сайта:
Ваш комментарий*:
Код безопастности*:

Рассылка новостей
Рейтинги
© 2007, Программирование Исходники.Ру