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 徹底解剖 - インプロセス サイドバイサイド