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;
}
Debug
Để 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
No comments:
Post a Comment