Shell Extension

Win7 64bitで動作確認。
unicode, x64, vs2010
Shell Extensionはマネージだと問題あるのでアンマネージで。*1


ファイルパスをクリップボードにコピーする。

ちなみにVistaからの「シフト+右クリック」とかいうコマンドは忘却の彼方に追いやると吉。

でもこれ入れるとなんかWindows+Eがおかしくなる罠。
そのうち調べる。

AppendedMenu.hpp

#pragma once

#include <shlobj.h>
#include "ComObject.hpp"

class AppendedMenu : public ComObject, public IContextMenu, public IShellExtInit {
// インタフェース
public:

#pragma region IUnknown

	virtual HRESULT STDMETHODCALLTYPE QueryInterface(
		/* [in] */  REFIID riid,
		/* [out] */ void** ppvObject);

	virtual ULONG STDMETHODCALLTYPE AddRef();

	virtual ULONG STDMETHODCALLTYPE Release();

	virtual HRESULT STDMETHODCALLTYPE Initialize( 
		/* [in] */ PCIDLIST_ABSOLUTE pidlFolder,
		/* [in] */ IDataObject *pdtobj,
		/* [in] */ HKEY hkeyProgID
	);

#pragma endregion

#pragma region IContextMenu

	virtual HRESULT STDMETHODCALLTYPE QueryContextMenu(
		HMENU hmenu,
		UINT indexMenu,
		UINT idCmdFirst,
		UINT idCmdLast,
		UINT uFlags
	);

	virtual HRESULT STDMETHODCALLTYPE GetCommandString(
		UINT_PTR idCmd,
		UINT uFlags,
		UINT *pwReserved,
		LPSTR pszName,
		UINT cchMax
	);

	virtual HRESULT STDMETHODCALLTYPE InvokeCommand(
		LPCMINVOKECOMMANDINFO pici
	);

#pragma endregion

protected:
	TCHAR szPath[MAX_PATH];

private:
	STDMETHODIMP GetFileName( LPDATAOBJECT pDataObj);
	bool SetClipboardText(LPCTSTR lpszText);
};

AppendedMenu.cpp

#include "AppendedMenu.hpp"

#include "global.hpp"
#include <tchar.h>

#pragma region IUnknown

HRESULT AppendedMenu::QueryInterface (
	/* [in] */ REFIID riid,
	/* [out] */ void** ppvObject
) {
	if (ppvObject == NULL) return E_POINTER;

	if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IContextMenu)) {
		*ppvObject = static_cast<IContextMenu*>(this);
	} else if (IsEqualIID(riid, IID_IShellExtInit)) {
		*ppvObject = static_cast<IShellExtInit*>(this);
	} else {
		*ppvObject = NULL;
		return E_NOINTERFACE;
	}

	static_cast<IUnknown*>(*ppvObject)->AddRef();

	return S_OK;
}

ULONG STDMETHODCALLTYPE AppendedMenu::AddRef() {
	return ComObject::AddRef();
}

ULONG STDMETHODCALLTYPE AppendedMenu::Release() {
	return ComObject::Release();
}

HRESULT STDMETHODCALLTYPE AppendedMenu::Initialize( 
	PCIDLIST_ABSOLUTE pidlFolder,
	IDataObject *pdtobj,
	HKEY hkeyProgID
) {
	GetFileName(pdtobj);

	return S_OK;
}

#pragma endregion

#pragma region IContextMenu

HRESULT AppendedMenu::QueryContextMenu(
	HMENU hmenu,
	UINT indexMenu,
	UINT idCmdFirst,
	UINT idCmdLast,
	UINT uFlags
) {
	if ( uFlags & CMF_DEFAULTONLY || szPath[0] == NULL ) {
		return MAKE_SCODE(SEVERITY_SUCCESS, FACILITY_NULL, 0);
	}

	MENUITEMINFO info;

	info.cbSize = sizeof( MENUITEMINFO );
	info.fMask = MIIM_ID | MIIM_TYPE;
	info.fType = MFT_STRING;
	info.wID = idCmdFirst + 1;
	info.dwTypeData = _T("Copy Path to Clipboard(&P)");
	InsertMenuItem(hmenu, 0, true, &info);

	// 1 + 1 = info.wID - idCmdFirst + 1
	return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 1 + 1);
}

HRESULT AppendedMenu::GetCommandString(
	UINT_PTR idCmd,
	UINT uFlags,
	UINT *pwReserved,
	LPSTR pszName,
	UINT cchMax
) {
	return S_OK;
}

HRESULT AppendedMenu::InvokeCommand(
	LPCMINVOKECOMMANDINFO pici
) {
	SetClipboardText(szPath);
	return S_OK;
}

#pragma endregion

STDMETHODIMP AppendedMenu::GetFileName( LPDATAOBJECT pDataObj ) {
	FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
	STGMEDIUM stg = { TYMED_HGLOBAL };

	if ( FAILED( pDataObj->GetData ( &fmt, &stg ) )) {
		return E_INVALIDARG;
	}

	HDROP hDrop = (HDROP)GlobalLock( stg.hGlobal );

	if ( NULL == hDrop ) {
		return E_INVALIDARG;
	}

	UINT uNumFiles = DragQueryFile( hDrop, 0xFFFFFFFF, NULL, 0 );
	HRESULT hr = S_OK;

	if ( 0 == uNumFiles ) {
		GlobalUnlock ( stg.hGlobal );
		ReleaseStgMedium ( &stg );
		return E_INVALIDARG;
	}

	if ( 0 == DragQueryFile( hDrop, 0, szPath, MAX_PATH ) ) {
		hr = E_INVALIDARG;
	}
	GlobalUnlock ( stg.hGlobal );
	ReleaseStgMedium ( &stg );

	return hr;
}

bool AppendedMenu::SetClipboardText(LPCTSTR lpszText) {
	if( lpszText == NULL || _tcslen(lpszText) == 0 ) {
		return false;
	}

	size_t length = _tcslen(lpszText) * 2 + 2;

	HGLOBAL hResult = GlobalAlloc(GMEM_FIXED, length);

	LPTSTR lptstrCopy = (LPTSTR)GlobalLock(hResult);

	memcpy(lptstrCopy, lpszText, length);

	GlobalUnlock(hResult); 

	if ( !OpenClipboard(NULL) ) {
		GlobalFree(hResult);	//Clipboard 未使用時のみ開放
		return false;
	}

	EmptyClipboard();
	SetClipboardData(CF_UNICODETEXT, hResult);
	CloseClipboard();

	return true;
}


ComObject.hpp

#pragma once

#include <unknwn.h>

class ComObject : public IUnknown {

// インタフェース
public:

#pragma region IUnknown

	virtual HRESULT STDMETHODCALLTYPE QueryInterface(
			/* [in] */ REFIID riid,
			/* [out] */ void** ppvObject);

	virtual ULONG STDMETHODCALLTYPE AddRef();

	virtual ULONG STDMETHODCALLTYPE Release();

#pragma endregion

public:
	ComObject(void);

protected:
	virtual ~ComObject(void);

private:
	ComObject(const ComObject&);
	ComObject& operator=(const ComObject&);

protected:
	ULONG referenceCount;
};

ComObject.cpp

#include "global.hpp"

#include "ComObject.hpp"

#pragma region IUnknown

HRESULT ComObject::QueryInterface(
	/* [in] */ REFIID riid,
	/* [out] */ void** ppvObject
) {
	if (ppvObject == NULL) {
		return E_POINTER;
	}

	// インタフェース型へのキャスト
	if (IsEqualIID(riid, IID_IUnknown)) {
		*ppvObject = static_cast<IUnknown*>(this);
	} else {
		*ppvObject = NULL;
		return E_NOINTERFACE;
	}

	static_cast<IUnknown*>(*ppvObject)->AddRef();

	return S_OK;
}

ULONG ComObject::AddRef() {
	InterlockedIncrement(&g_serverLockCount);

	return ++referenceCount;
}

ULONG ComObject::Release() {
	ULONG ref = --referenceCount;

	if (ref == 0) {
		delete this;
	}

	InterlockedDecrement(&g_serverLockCount);

	return ref;
}

#pragma endregion

ComObject::ComObject(void) {
	referenceCount = 0;
}

ComObject::~ComObject(void) {
}

global.hpp

#pragma once

#ifndef STRICT
#define STRICT
#endif

#ifndef UNICODE
#define UNICODE
#endif

#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <ole2.h>
#include <olectl.h>

extern HMODULE g_moduleHandle;

extern volatile long g_serverLockCount;

extern const GUID CLSID_AppendedMenu;

global.cpp

#include "global.hpp"

HMODULE g_moduleHandle = NULL;

volatile long g_serverLockCount = 0;

// {3E0BEB25-3A36-4053-95C2-E13D6F17E9A1}
static const GUID CLSID_AppendedMenu = {
	0x3e0beb25, 0x3a36, 0x4053, { 0x95, 0xc2, 0xe1, 0x3d, 0x6f, 0x17, 0xe9, 0xa1 }
};

ClassObjectTemplate.hpp

#pragma once

#include <unknwn.h>

#include <new>

template <typename T>
class ClassObjectTemplate : public IClassFactory {

public:

#pragma region IUnknown

	virtual HRESULT STDMETHODCALLTYPE QueryInterface(
		/* [in] */ REFIID riid,
		/* [out] */ void** ppvObject
	) {
		if (ppvObject == NULL) {
			return E_POINTER;
		}

		if (IsEqualIID(riid, IID_IClassFactory)) {
			*ppvObject = static_cast<IClassFactory*>(this);
		} else if (IsEqualIID(riid, IID_IUnknown)) {
			*ppvObject = static_cast<IUnknown*>(this);
		} else {
			*ppvObject = NULL;
			return E_NOINTERFACE;
		}

		static_cast<IUnknown*>(*ppvObject)->AddRef();

		return S_OK;
	}

	virtual ULONG STDMETHODCALLTYPE AddRef() {
		InterlockedIncrement(&g_serverLockCount);
		return 1;
	}

	virtual ULONG STDMETHODCALLTYPE Release() {
		InterlockedDecrement(&g_serverLockCount);
		return 1;
	}

#pragma endregion

#pragma region IClassFactory

	virtual HRESULT STDMETHODCALLTYPE CreateInstance( 
		/* [in] */  IUnknown* pUnkOuter,
		/* [in] */  REFIID riid,
		/* [out] */ void** ppvObject
	) {
		if (ppvObject == NULL) {
			return E_POINTER;
		}

		if (pUnkOuter != NULL) {
			return CLASS_E_NOAGGREGATION;
		}

		*ppvObject = NULL;

		T* instance = new (std::nothrow) T();
		if (instance == NULL) {
			return E_OUTOFMEMORY;
		}

		instance->AddRef();
		HRESULT result = instance->QueryInterface(riid, ppvObject);
		instance->Release();

		return result;
	}

	virtual HRESULT STDMETHODCALLTYPE LockServer( 
		/* [in] */ BOOL fLock
	) {

		if (fLock) {
			InterlockedIncrement(&g_serverLockCount);
		} else {
			InterlockedDecrement(&g_serverLockCount);
		}

		return S_OK;
	}

#pragma endregion

public:
	ClassObjectTemplate() {}
	virtual ~ClassObjectTemplate() {}

private:
	ClassObjectTemplate(const ClassObjectTemplate&);
	ClassObjectTemplate& operator=(const ClassObjectTemplate&);
};

library.cpp

#include "global.hpp"
#include "AppendedMenu.hpp"
#include "ClassObjectTemplate.hpp"

#define APP_TITLE L"PathExtension"

BOOL WINAPI DllMain(HINSTANCE hinstDLL,
	DWORD fdwReason, LPVOID lpvReserved
) {
	if (fdwReason == DLL_PROCESS_ATTACH) {
		g_moduleHandle = hinstDLL;
	}

	if(fdwReason == DLL_PROCESS_DETACH) {
	}

	return true;
}

STDAPI DllCanUnloadNow() {
	return (g_serverLockCount == 0) ? S_OK : S_FALSE;
}

STDAPI DllGetClassObject(
	/* [in] */  REFCLSID rclsid,
	/* [in] */  REFIID riid,
	/* [out] */ void** ppvObject
) {
	static ClassObjectTemplate<AppendedMenu> AppendedMenuClass;

	if (ppvObject == NULL) {
		return E_INVALIDARG;
	}

	IUnknown* classObject = NULL;

	if (IsEqualCLSID(rclsid, CLSID_AppendedMenu)) {
		classObject = &AppendedMenuClass;
	} else {
		*ppvObject = NULL;
		return CLASS_E_CLASSNOTAVAILABLE;
	}

	classObject->AddRef();

	HRESULT result = classObject->QueryInterface(riid, ppvObject);

	classObject->Release();

	return result;
}

static LONG WriteRegistryStringValue(
	HKEY baseKey,
	LPCTSTR path,
	LPCTSTR name,
	LPCTSTR value
) {
	HKEY key;

	LONG error = RegCreateKeyEx(baseKey, path,
			0, NULL, 0, KEY_WRITE, NULL, &key, NULL);

	if (error != ERROR_SUCCESS) {
		return error;
	}

	if (value != NULL) {
		error = RegSetValueEx(key, name, 0, REG_SZ
			, reinterpret_cast<const BYTE*>(value)
			, (lstrlen(value) + 1) * sizeof (TCHAR)
		);
	}

	RegCloseKey(key);

	return error;
}

static LONG DeleteRegistryValue(
	HKEY baseKey,
	LPCTSTR path,
	LPCTSTR name
) {
	HKEY key;

	LONG error = RegOpenKeyEx(baseKey, path, 0, KEY_WRITE, &key);
	if (error != ERROR_SUCCESS) {
		return error;
	}

	error = RegDeleteValue(key, name);
	RegCloseKey(key);

	return error;
}

void Regstry(LPCTSTR keyPath, LPCTSTR value, LPCTSTR name) {
	LONG error = WriteRegistryStringValue(
		HKEY_CLASSES_ROOT, keyPath, name, value
	);

	if (error != ERROR_SUCCESS) {
		throw;
	}
}

void Regstry(LPCTSTR keyPath, LPCTSTR value) {
	Regstry(keyPath, value, NULL);
}

static HRESULT RegisterShellExtensionHandler (
	REFCLSID classID,
	LPCTSTR description, 
	LPCTSTR threadingModel
) {
	if (description == NULL || threadingModel == NULL) {
		return E_UNEXPECTED;
	}

	WCHAR clsidString[40];
	if (!StringFromGUID2(classID, clsidString, 40)) {
		return SELFREG_E_CLASS;
	}

	WCHAR keyPath[256];

	TCHAR modulePath[MAX_PATH];
	if ( !GetModuleFileName(g_moduleHandle, modulePath, MAX_PATH) ) {
		return E_UNEXPECTED;
	}

	try {
		wsprintf(keyPath, L"CLSID\\%s", clsidString);
		Regstry( keyPath, description );

		lstrcat(keyPath, L"\\InProcServer32");
		Regstry( keyPath, modulePath );
		Regstry( keyPath, threadingModel,  L"ThreadingModel" );

		wsprintf(keyPath, L"*\\shellex\\ContextMenuHandlers\\%s", description);
		Regstry( keyPath, clsidString );
		//wsprintf(keyPath, L"Directory\\ShellEx\\ContextMenuHandlers\\%s", description);
		//Regstry( keyPath, clsidString );

		wsprintf(keyPath, L"Drive\\ShellEx\\ContextMenuHandlers\\%s", description);
		Regstry( keyPath, clsidString );
		wsprintf(keyPath, L"Folder\\ShellEx\\ContextMenuHandlers\\%s", description);
		Regstry( keyPath, clsidString );

		//wsprintf(keyPath, L"lnkfile\\ShellEx\\ContextMenuHandlers\\%s", description);
		//Regstry( keyPath, clsidString );
		//wsprintf(keyPath, L"AllFilesystemObjects\\ShellEx\\ContextMenuHandlers\\%s", description);
		//Regstry( keyPath, clsidString );

		return S_OK;
	} catch(...) {
		return SELFREG_E_CLASS;
	}
}

LONG DeleteRegstry(LPCTSTR keyPath) {
	LONG error = DeleteRegistryValue(HKEY_CLASSES_ROOT, keyPath, NULL);
	error |= RegDeleteKey(HKEY_CLASSES_ROOT, keyPath);
	return error;
}

LONG DeleteRegstry(LPCTSTR keyPath, LPCTSTR name) {
	LONG error = DeleteRegistryValue(HKEY_CLASSES_ROOT, keyPath, name);
	error |= DeleteRegstry(keyPath);
	return error;
}

static HRESULT UnregisterShellExtensionHandler(
	REFCLSID classID,
	LPCTSTR description
) {
	if (description == NULL) {
		return E_UNEXPECTED;
	}

	WCHAR clsidString[40];
	if ( !StringFromGUID2(classID, clsidString, 40) ) {
		return SELFREG_E_CLASS;
	}

	WCHAR keyPath[256];

	LONG error = ERROR_SUCCESS;

	//wsprintf(keyPath, L"AllFilesystemObjects\\ShellEx\\ContextMenuHandlers\\%s", description);
	//error |= DeleteRegstry(keyPath);
	//wsprintf(keyPath, L"lnkfile\\ShellEx\\ContextMenuHandlers\\%s", description);
	//error |= DeleteRegstry(keyPath);

	wsprintf(keyPath, L"Folder\\ShellEx\\ContextMenuHandlers\\%s", description);
	error |= DeleteRegstry(keyPath);
	wsprintf(keyPath, L"Drive\\ShellEx\\ContextMenuHandlers\\%s", description);
	error |= DeleteRegstry(keyPath);

	//wsprintf(keyPath, L"Directory\\ShellEx\\ContextMenuHandlers\\%s", description);
	//error |= DeleteRegstry(keyPath);
	wsprintf(keyPath, L"*\\shellex\\ContextMenuHandlers\\%s", description);
	error |= DeleteRegstry(keyPath);

	wsprintf(keyPath, L"*\\shellex\\ContextMenuHandlers\\%s", description);
	error |= DeleteRegstry(keyPath);

	wsprintf(keyPath, L"CLSID\\%s\\InProcServer32", clsidString);
	error |= DeleteRegstry(keyPath, L"ThreadingModel");

	wsprintf(keyPath, L"CLSID\\%s", clsidString);
	error |= DeleteRegstry(keyPath);

	return error ? S_FALSE : S_OK;
}

STDAPI DllRegisterServer() {
	HRESULT result;

	result = RegisterShellExtensionHandler(
		CLSID_AppendedMenu, APP_TITLE, L"Apartment"
	);

	if ( FAILED( result ) ) {
		return result;
	}

	return S_OK;
}

STDAPI DllUnregisterServer() {
	UnregisterShellExtensionHandler(
		CLSID_AppendedMenu, APP_TITLE
	);

	return S_OK;
}

PathExtension.def

LIBRARY      "PathExtension.DLL"

EXPORTS
	DllCanUnloadNow PRIVATE
	DllGetClassObject PRIVATE
	DllRegisterServer PRIVATE
	DllUnregisterServer PRIVATE

セットアップファイルは
プライマリ出力のregisterをvsdrpCOMSelfRegにしておく。

*1:というのは過去の話だった → CLR 徹底解剖 - インプロセス サイドバイサイド