Lâu lắm mới viết lại shell extension ...
Để viết Shell extensions thì có nhiều tutorials ko cần phải nói nữa, search 1 vòng trên CodeProject có thể list ra như series
http://www.codeproject.com/Articles/441/The-Complete-Idiot-s-Guide-to-Writing-Shell-Extens hoặc các ví dụ trên CodePlex
http://1code.codeplex.com/wikipage?title=WinShell
Hầu hết các ví dụ này dùng ATL, tất nhiên vậy cũng OK. Nhưng có 1 cách tiếp cận hay khá linh động viết COM bình thường không dùng ATL cần note lại là của TortoiseSVN. Đơn giản có thể tóm tắt như sau: ví dụ cần wrap 1 số shell extensions vào 1 chỉ DLL hoặc sử dụng trong trường hợp cần nhiều icon overlays mỗi cho 1 trạng thái của file như syncing, synced, do handler chỉ xử lý bằng IsMemberOf có nghĩa là chỉ xác định có/không hiển thị icon nên phải có 2 CLSID cho 2 trạng thái file.
Giả sử cần 2 icon overlay handlers cho syncing, synced và 1 context menu. Class xử lý icon overlay là IconOverlayExt và class xử lý context menu là ContextMenuExt. IconOverlayExt xử lý hiển thị hay không 2 trạng thái syncing và synced. Ta có các CLSID tương ứng CLSID_Synced, CLSID_Syncing, CLSID_ContextMenu.
Khai báo một enum xác định extension cần tạo:
enum ExtensionState
{
Invalid,
IconSynced,
IconSyncing,
ContextMenu
};
ShellExtClassFactory chịu trách nhiệm tại ext tùy theo loại ext cần tạo, nhận 1 ExtensionState là member
// This class factory object creates
the main handlers -
// its constructor says which OLE class
it has to make.
class ShellExtClassFactory : public
IClassFactory
{
protected:
ULONG
_refCount;
// Variable to contain class of object (i.e. syncing, synced, context menu)
ExtensionState _stateToMake;
public:
ShellExtClassFactory(ExtensionState stateToMake);
virtual ~ShellExtClassFactory();
// IUnknown members
STDMETHODIMP
QueryInterface(REFIID, LPVOID FAR *);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();
// IClassFactory members
STDMETHODIMP CreateInstance(LPUNKNOWN,
REFIID, LPVOID FAR *);
STDMETHODIMP LockServer(BOOL);
};
Như vậy code DllGetClassObject như sau:
STDAPI DllGetClassObject(const
CLSID& rclsid, const IID& riid, void** ppv)
{
if(ppv == 0)
{
return E_POINTER;
}
*ppv =
NULL;
// State to get factory
ExtensionState
stateToMake = ExtensionState::Invalid;
if (IsEqualIID(rclsid, CLSID_Synced))
{
stateToMake
= ExtensionState::IconSynced;
}
else if
(IsEqualIID(rclsid, CLSID_Syncing))
{
stateToMake
= ExtensionState::IconSyncing;
}
else if
(IsEqualIID(rclsid, CLSID_ContextMenu))
{
stateToMake
= ExtensionState::ContextMenu;
}
if (stateToMake != ExtensionState::Invalid)
{
ShellExtClassFactory
*classFactory = new
(std::nothrow)ShellExtClassFactory(stateToMake);
if (classFactory == NULL)
{
return E_OUTOFMEMORY;
}
// IMPORTANT NOTE: ref count set to 0 at this moment ->
do not call Release
// If your ClassFactory object's reference count initialize
with 0, DO NOT call the release method,
// it seems the COM library would call the release method
automatically.
// Otherwise, you'd get an access exception.
//
// If ClassFactory initialize with ref count = 1 -> must
call Release
// See
http://msdn.microsoft.com/en-us/library/windows/desktop/ms680760(v=vs.85).aspx
const HRESULT hr =
classFactory->QueryInterface(riid, ppv);
if (FAILED(hr))
{
delete classFactory;
}
return hr;
}
return CLASS_E_CLASSNOTAVAILABLE;
}
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID
riid, void **ppv)
{
HRESULT
hr = CLASS_E_CLASSNOTAVAILABLE;
if (IsEqualCLSID(CLSID_FileContextMenuExt, rclsid))
{
hr =
E_OUTOFMEMORY;
ClassFactory *pClassFactory = new
ClassFactory();
if (pClassFactory)
{
hr = pClassFactory->QueryInterface(riid, ppv);
pClassFactory->Release();
}
}
return hr;
}
Như vậy là OK đã có ClassFactory và ext cần tạo, trong CreateInstance
STDMETHODIMP ShellExtClassFactory::CreateInstance(LPUNKNOWN
unkOuter,
REFIID
riid, LPVOID *out)
{
if(out == 0)
{
return E_POINTER;
}
*out =
NULL;
// Shell extensions typically don't support aggregation
(inheritance)
if (unkOuter)
{
return CLASS_E_NOAGGREGATION;
}
HRESULT
hr;
if(_stateToMake == ExtensionState::ContextMenu)
{
// The shell will then call QueryInterface with
IID_IShellExtInit--this
// is how shell extensions are initialized.
ContextMenuExt*
contextMenu = new
(std::nothrow)ContextMenuExt(); // create the ContextMenuExt object
if (contextMenu == nullptr)
{
return E_OUTOFMEMORY;
}
hr
= contextMenu->QueryInterface(riid, out);
if(FAILED(hr))
{
delete contextMenu;
}
}
else
{
// Create the main shell extension object.
IconOverlayExt*
iconOverlay = new
(std::nothrow)IconOverlayExt(_stateToMake);
// create the IconOverlayExt object
if (iconOverlay == nullptr)
{
return E_OUTOFMEMORY;
}
hr
= iconOverlay->QueryInterface(riid, out);
if(FAILED(hr))
{
delete iconOverlay;
}
}
return hr;
}
Thêm std::nothrow để kiểm tra kết quả trả về thay vì nhận exception. Tại đây dựa vào ext cần tạo sẽ xử lý.
Còn 1 việc cần giải quyết là IPC giữa shell và app. Có thể dùng RPC, DLL shared segment cũng là 1 khả năng tuy nhiên phải cùng x86 hay 64-bit (app x86 chẳng hạn không thể load shell ext 64-bit), và named pipe có lẽ là hợp lý nhất.
Debug
Về debug chỉ có 1 lưu ý, nếu trong máy có install nhiều app có ext thì nên disable hay tạm thời uninstall. Giả sử máy bạn là developer hay cài TortoiseSVN thì tạm thời bỏ nó đi vì không thể nào debug được, lý do TortoiseSVN sẽ block ngay explorer khi reach break point đâu đó, khi đó chỉ có nước nhấn reset máy (cry).
Để tránh các app khác load ext: 1 số thằng rất hay load như Google Chrome, IDM và Skype trong DLL Attach code 1 đoạn như sau nếu có debugger hay là app test-shell của mình hoặc Explorer hay regsvr32 thì OK:
BOOL APIENTRY DllMain(HMODULE module, DWORD reason,
LPVOID reserved)
{
#ifdef _DEBUG
// If no debugger is present, then don't load the dll.
// this prevents other apps from loading the dll and
locking
// it.
bool isInShellTest = false;
TCHAR
fullPath[MAX_PATH + 1]; // MAX_PATH ok, the test really is for debugging anyway.
DWORD
len = GetModuleFileName(NULL, fullPath, MAX_PATH);
TCHAR
fileNameWithoutExt[MAX_PATH + 1];
TCHAR
ext[MAX_PATH + 1];
_tsplitpath_s(fullPath,
NULL, 0, NULL, 0, fileNameWithoutExt, MAX_PATH, ext, MAX_PATH);
TCHAR
fileName[MAX_PATH + 1];
_tmakepath_s(fileName,
MAX_PATH, NULL, NULL, fileNameWithoutExt, ext);
if ((_tcsicmp(fileName, _T("test-shell.exe")))
== 0 || (_tcsicmp(fileName, _T("explorer.exe")))
== 0 ||
(_tcsicmp(fileName,
_T("verclsid.exe"))) == 0 ||
(_tcsicmp(fileName, _T("regsvr32.exe")))
== 0)
{
isInShellTest
= true;
}
if (!::IsDebuggerPresent() && !isInShellTest)
{
return FALSE;
}
#endif
switch (reason)
{
...
}
return TRUE;
}
Để có thể debug thực hiện:
Thêm Properties -> Build Events -> Post-Build Event:
Command Line: echo "Registering Icon Overlay Library" regsvr32 "$(TargetPath)"
Thêm Debugging
Command: explorer.exe
Để restart Explorer unload ext DLL không tắt bằng Taskbar Manager mà thực hiện
Ctrl + Shift + Right Click vào khoảng trống như hình bên dưới