January 26, 2023, 10:11:43 PM

News:

Own IWBasic 2.x ? -----> Get your free upgrade to 3.x now.........


Creating Context Menu Handlers

Started by sapero, March 31, 2007, 07:46:24 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

sapero

A context menu handler is a shell extension handler that adds commands to an existing context menu. Context menu handlers are associated with a particular file class and are called any time a context menu is displayed for a member of the class. While you can add items to a file class context menu with the registry, the items will be the same for all members of the class. By implementing and registering such a handler, you can dynamically add items to an object's context menu, customized for the particular object.

/* Creating Context Menu Handlers

  When a user right-clicks a Shell object, the Shell displays its context menu.
For file system objects there are a number of standard items, such as Cut and Copy,
that are on the menu by default. If the object is a file that is a member of a class,
additional items can be specified in the registry. Finally, the Shell checks the registry
to see if the file class is associated with any context menu handlers.
If it is, the Shell queries the handler for additional context menu items
*/


// this example handles only the desktop background menu, adding two items:
// 1. open shell window
// 2. run the calculator
// compile as dll, then register this dll by executing command regsvr32 dllname.dll
// to unregister, execute regsvr32 /u dllname.dll

#autodefine "OFF"
#use "shlwapi.lib"

//-------------------------------------------------------
// *** menu items ***

#define TITLE_1 "Show Shell Window"
#define TITLE_2 "Calculator"
#define EXE_1   "c:\\windows\\system32\\cmd.exe"
#define EXE_2   "c:\\windows\\system32\\calc.exe"

//-------------------------------------------------------
// *** registry ***

// 1. class GUID (call CoCreateGuid or UuidCreate to create your own)
#define REG_GUID_STRING "{09732EAE-1C09-4D66-B7F2-E670767B8F6B}"
#emit _CLSID_This dd 0x9732EAE, 0x4D661C09, 0x70E6F2B7, 0x6B8F7B76
declare _CLSID_This();
// 2. class description
#define REG_DESCRIPTION "Aurora Desktop Tools"

//-------------------------------------------------------
// *** dll server ***

export DllGetClassObject;   // required
export DllCanUnloadNow;     // required
export DllRegisterServer;   // optioal
export DllUnregisterServer; // optioal

// total number of instances; for DllCanUnloadNow
int g_instances;
// icons for menu
int g_bitmaps[2];

//-------------------------------------------------------
// *** definitions ***

// dummy typedefs
#typedef ITEMIDLIST pointer
#typedef IDataObject pointer

struct CMINVOKECOMMANDINFO {
int cbSize;
int fMask;
int hwnd;       // owner of the shortcut menu
string* lpVerb; // menu-identifier in the low-order word
string* lpParameters; // NULL for menu items
string* lpDirectory;  // NULL for menu items
int nShow;
int dwHotKey;
int hIcon;
}

extern _IID_IUnknown      as GUID;
extern _IID_IClassFactory as GUID;
extern _IID_IShellExtInit as GUID;
extern _IID_IContextMenu  as GUID;
extern _IID_IContextMenu2 as GUID;
extern _IID_IContextMenu3 as GUID;
extern _hinstance as int;

CONST E_NOINTERFACE = 0x80004002;
CONST SM_CXMENUCHECK = 71;
CONST SM_CYMENUCHECK = 72;
CONST CLASS_E_CLASSNOTAVAILABLE = 0x80040111;
CONST E_POINTER = 0x80004003;
CONST HKEY_CLASSES_ROOT = 0x80000000;
CONST SELFREG_E_CLASS = (0x80040200+1);
CONST REG_SZ = 1;
CONST CLASS_E_NOAGGREGATION = 0x80040110;
CONST MF_BYPOSITION = 0x400;
CONST COLOR_WINDOW = 5;
CONST DI_NORMAL = 0x0003;
CONST SW_SHOW = 5;
CONST MAX_PATH = 260;

class IUnknownImpl
{
declare IUnknownImpl();
declare AddInterface(GUID *riid, IUnknownImpl *pUnk);

declare virtual QueryInterface(GUID *riid,void *ppvObject),int;
declare virtual AddRef(),int;
// override this method if you have to cleanup before delete
declare virtual Release(),int;

CPointerList unknown_iid;
int m_refs; // references count
}


class IClassFactoryImpl : IUnknownImpl
{
declare IClassFactoryImpl();

declare virtual CreateInstance(IUnknownImpl *pUnkOuter, GUID *riid, void *ppvObject),int;
declare virtual LockServer(int fLock),int;
}


class IShellExtInitImpl : IUnknownImpl
{
declare IShellExtInitImpl();
// IUnknown - we need to create IContextMenu here
declare virtual QueryInterface(GUID *riid,void *ppvObject),int;
// IShellExtInit
declare virtual Initialize(ITEMIDLIST *pidlFolder,IDataObject *pdtobj,int hkeyProgID),int;
}


class IContextMenuImpl : IUnknownImpl
{
declare  IContextMenuImpl();

// IContextMenu
declare virtual QueryContextMenu(int hmenu,int indexMenu,int idCmdFirst,int idCmdLast,int uFlags),int;
declare virtual InvokeCommand(CMINVOKECOMMANDINFO *lpici),int;
declare virtual GetCommandString(int idCmd,int uType,int *pwReserved,string *pszName,int cchMax),int;
// IContextMenu2
declare virtual HandleMenuMsg(int uMsg, int wParam, int lParam),int;
// IContextMenu3
declare virtual HandleMenuMsg2(int uMsg, int wParam, int lParam, int* plResult),int;

int m_firstId;
}

import int GetDC(int hwnd);
import int GetSystemMetrics(int hwnd);
import int CreateCompatibleDC(int hdc);
import int CreateCompatibleBitmap(int hdc,int x,int y);
import int SelectObject(int hdc,int object);
import int DrawIconEx(int hdc,int l,int t,int icon,int w,int h,int q,int brush,int flags);
import int DestroyIcon(int object);
import int DeleteDC(int hdc);
import int ReleaseDC(int hwnd, int hdc);
import int ExtractIconExA(string path,int start,int *big,int *small,int count);
import int InsertMenuA(int hMenu,int pos,int uFlags,int id,string *txt);
import int IsBadReadPtr(void *adr,int size);
import int IsBadWritePtr(void *adr,int size);
import int IsEqualGUID(GUID *a,GUID *b);
import int GetSysColorBrush(int index);
import int SetMenuItemBitmaps(int hMenu,int pos,int uFlags,int hBitmapUnchecked,int hBitmapChecked);
import int WinExec(string *lpCmdLine,int uCmdShow);
import int cdecl strncpy(string *dest,string *src,int max);
import int GetModuleFileNameA(int inst,string *buf,int size);
import int SHSetValueA(int hkey,string *SubKey,string *value,int type,string *data,int cbData);
import int SHDeleteKeyA(int hkey,string *pszSubKey);


//-----------------------------------------------------------
// IShellExtInit class - used to initialize Shell extensions
//-----------------------------------------------------------

IShellExtInitImpl::IShellExtInitImpl()
{
// this class implements IUnknown and IShellExtInit
AddInterface(_IID_IShellExtInit, this);
}


IShellExtInitImpl::QueryInterface(GUID *riid,void *ppvObject),int
{
int hr = IUnknownImpl!!QueryInterface(riid, ppvObject);
if (hr == E_NOINTERFACE)
{
IUnknownImpl *p = new(IContextMenuImpl, 1);
if (!p)
{
return E_OUTOFMEMORY;
}
hr = p->QueryInterface(riid, ppvObject);
p->Release();
}
return hr;
}


// Initializes a property sheet extension, shortcut menu extension, or drag-and-drop handler.
// For shortcut menu extensions, pdtobj identifies the selected file objects,
// hkeyProgID identifies the file class of the object with focus,
// and pidlFolder is either NULL (for file objects) or specifies the folder
// for which the shortcut menu is being requested (for folder background shortcut menus).
IShellExtInitImpl::Initialize(ITEMIDLIST *pidlFolder,IDataObject *pdtobj,int hkeyProgID),int
{
return 0;
}

//-----------------------------------------------------------
// IContextMenu - called by the Shell to either create or
// merge a shortcut menu associated with a Shell object
//-----------------------------------------------------------

IContextMenuImpl::IContextMenuImpl()
{
AddInterface(_IID_IContextMenu, this);
AddInterface(_IID_IContextMenu2, this);
// AddInterface(_IID_IContextMenu3, this);
}


// Adds commands to a shortcut menu
IContextMenuImpl::QueryContextMenu(int hmenu,int indexMenu,int idCmdFirst,int idCmdLast,int uFlags),int
{
// Adds commands to a shortcut menu
m_firstId = idCmdFirst;
InsertMenuA(hMenu, indexMenu+0, MF_BYPOSITION, idCmdFirst+0, TITLE_1);
InsertMenuA(hMenu, indexMenu+1, MF_BYPOSITION, idCmdFirst+1, TITLE_2);

// icons loaded ?
int hIcon;
int hdc, cdc;
int hbm;

if (!g_bitmaps[0])
{

hdc = GetDC(0);
cdc = CreateCompatibleDC(hdc);
int cx = GetSystemMetrics(SM_CXMENUCHECK);
int cy = GetSystemMetrics(SM_CYMENUCHECK);

if (ExtractIconExA(EXE_1, 0, 0, &hIcon, 1))
{
g_bitmaps[0] = CreateCompatibleBitmap(hdc, cx,cy);
hbm = SelectObject(cdc, g_bitmaps[0]);
DrawIconEx(cdc, 0,0,hIcon,cx,cy,0,GetSysColorBrush(COLOR_WINDOW), DI_NORMAL);
SelectObject(cdc, hbm);
DestroyIcon(hIcon);
}
if (!g_bitmaps[1] && ExtractIconExA(EXE_2, 0, 0, &hIcon, 1))
{
g_bitmaps[1] = CreateCompatibleBitmap(hdc, cx,cy);
hbm = SelectObject(cdc, g_bitmaps[1]);
DrawIconEx(cdc, 0,0,hIcon,cx,cy,0,GetSysColorBrush(COLOR_WINDOW), DI_NORMAL);
SelectObject(cdc, hbm);
DestroyIcon(hIcon);
}
DeleteDC(cdc);
ReleaseDC(0, hdc);
}

if (g_bitmaps[0]) SetMenuItemBitmaps(hmenu, indexMenu+0, MF_BYPOSITION, g_bitmaps[0], 0);
if (g_bitmaps[1]) SetMenuItemBitmaps(hmenu, indexMenu+1, MF_BYPOSITION, g_bitmaps[1], 0);
// the bitmaps are destroyed while shutting down

return 2; // number of added commands
}

// Carries out the command associated with a shortcut menu item
IContextMenuImpl::InvokeCommand(CMINVOKECOMMANDINFO *lpici),int
{
switch (lpici->lpVerb)
{
case 0: WinExec(EXE_1, SW_SHOW);
case 1: WinExec(EXE_2, SW_SHOW);
}
return 0;
}

// Retrieves information about a shortcut menu command,
// including the help string and the language-independent,
// or canonical, name for the command
IContextMenuImpl::GetCommandString(int idCmd,int uType,int *pwReserved,string *pszName,int cchMax),int
{
strncpy(pszName, str$(rand(0,3333)), cchMax);
}

// Allows client objects of the IContextMenu interface to handle
// messages associated with owner-drawn menu items
IContextMenuImpl::HandleMenuMsg(int uMsg, int wParam, int lParam),int
{

return 0;
}

// Allows client objects of the IContextMenu3 interface to handle
// messages associated with owner-drawn menu items
IContextMenuImpl::HandleMenuMsg2(int uMsg, int wParam, int lParam, int* plResult),int
{

return 0;
}

//-----------------------------------------------------------
// universal IUnknownImpl implementation
//-----------------------------------------------------------

struct NODE
{
GUID     *iid;
IUnknownImpl *object;
}

IUnknownImpl::IUnknownImpl()
{
m_refs = 1;
g_instances++;
unknown_iid.Create();
AddInterface(_IID_IUnknown, this);
}

IUnknownImpl::AddInterface(GUID *riid, IUnknownImpl *pUnk)
{
if (riid && pUnk)
{
NODE *node = new(NODE, 1);
if (node)
{
node->iid = riid;
node->object = pUnk;
unknown_iid.Add(node);
}
}
}

// Returns a pointer to a specified interface on an object to which
// a client currently holds an interface pointer. This function must
// call IUnknown::AddRef on the pointer it returns
IUnknownImpl::QueryInterface(GUID *riid,void *ppvObject),int
{
if (IsBadReadPtr(riid, sizeof(GUID) || IsBadWritePtr(ppvObject, 4)))
{
return E_POINTER;
}
*(pointer)ppvObject = NULL;

int hr = E_NOINTERFACE;

pointer pos = unknown_iid.GetFirst();
while (pos)
{
NODE *node = unknown_iid.GetData(pos);
if (IsEqualGUID(node->iid, riid))
{
IUnknownImpl *p = node->object;
*(pointer)ppvObject = p;
p->AddRef();
return 0;
}
pos = unknown_iid.GetNext(pos);
}
return hr;
}

// Increments the reference count for an interface on an object.
// It should be called for every new copy of a pointer to an interface on a given object
IUnknownImpl::AddRef(),int
{
m_refs++;
return m_refs;
}

// Decrements the reference count for the calling interface on a object.
// If the reference count on the object falls to 0, the object is freed from memory
IUnknownImpl::Release(),int
{
m_refs--;
if (m_refs) return m_refs;
g_instances--;
unknown_iid.RemoveAll();
delete this;
return 0;
}

//-----------------------------------------------------------
// IClassFactory - contains two methods intended to deal
// with an entire class of objects
//-----------------------------------------------------------

IClassFactoryImpl::IClassFactoryImpl
{
AddInterface(_IID_IClassFactory, this);
}

// Creates an uninitialized object
IClassFactoryImpl::CreateInstance(IUnknownImpl *pUnkOuter, GUID *riid, void *ppvObject),int
{
if (IsBadReadPtr(riid, sizeof(GUID)) || IsBadWritePtr(ppvObject, 4))
{
return E_POINTER;
}
*(pointer)ppvObject = NULL;
if (pUnkOuter)
{
return CLASS_E_NOAGGREGATION;
}
IUnknownImpl *p = new(IShellExtInitImpl, 1);
if (!p)
{
return E_OUTOFMEMORY;
}
int hr = p->QueryInterface(riid, ppvObject);
*(pointer)ppvObject = p;
p->Release();

return hr;
}

// Called by the client of a class object
// to keep a server open in memory, allowing instances to be created more quickly
IClassFactoryImpl::LockServer(int fLock),int
{
if (fLock)
g_instances++
else
if (g_instances) g_instances--;

return 0;
}


//-----------------------------------------------------------
// dll server part
//-----------------------------------------------------------

// regsvr32.exe thisdll.dll
// Instructs an in-process server to create its registry entries
// for all classes supported in this server module. If this function fails,
// the state of the registry for all its classes is indeterminate
global sub DllRegisterServer(),int
{
int hr = 0;
dstring szModule[MAX_PATH];
GetModuleFileNameA(_hinstance, szModule, MAX_PATH);

// the main server key : description
hr  = SHSetValueA(HKEY_CLASSES_ROOT,
"CLSID\\"+REG_GUID_STRING,
NULL, REG_SZ,
REG_DESCRIPTION+" Context Menu Handler", len(REG_DESCRIPTION+" Context Menu Handler")+1);

// the main server key : server path
hr |= SHSetValueA(HKEY_CLASSES_ROOT,
"CLSID\\"+REG_GUID_STRING+"\\InprocServer32",
NULL, REG_SZ,
szModule, len(szModule)+1);

// explorer background extension
hr |= SHSetValueA(HKEY_CLASSES_ROOT,
"Directory\\Background\\shellex\\ContextMenuHandlers\\"+REG_DESCRIPTION,
NULL, REG_SZ,
REG_GUID_STRING, len(REG_GUID_STRING)+1);

return (hr != 0) * SELFREG_E_CLASS;
}


// regsvr32.exe /u thisdll.dll
// Instructs an in-process server to remove only those entries
// created through DllRegisterServer
global sub DllUnregisterServer(),int
{
int hr;

hr  = SHDeleteKeyA(HKEY_CLASSES_ROOT, "Directory\\Background\\shellex\\ContextMenuHandlers\\"+ REG_DESCRIPTION);
hr |= SHDeleteKeyA(HKEY_CLASSES_ROOT, "CLSID\\"+REG_GUID_STRING+"\\InprocServer32");
hr |= SHDeleteKeyA(HKEY_CLASSES_ROOT, "CLSID\\"+REG_GUID_STRING);

return (hr != 0) * SELFREG_E_CLASS;
}

// Retrieves the class object from a DLL object handler or object application.
// DllGetClassObject is called from within the CoGetClassObject function
// when the class context is a DLL
global sub DllGetClassObject(GUID *rclsid, GUID *riid, void *ppv),int
{
// validate pointers
if (IsBadReadPtr(rclsid, sizeof(GUID))
||  IsBadReadPtr(riid, sizeof(GUID))
||  IsBadWritePtr(ppv, 4))
{
return E_POINTER;
}
*(pointer)ppv = NULL;

// validate class GUID
if (!IsEqualGUID(rclsid, &_CLSID_This))
{
return CLASS_E_CLASSNOTAVAILABLE;
}

// create main class IClassFactory and validate interface GUID
IClassFactoryImpl *pFactory = new(IClassFactoryImpl, 1);
if (!pFactory)
{
return E_OUTOFMEMORY;
}
int hr = pFactory->QueryInterface(riid, ppv);
pFactory->Release();
if (!hr)
{
*(pointer)ppv = pFactory;
}
return hr;;
}

// Determines whether the DLL that implements this function is in use.
// If not, the caller can unload the DLL from memory
global sub DllCanUnloadNow(),int
{
return (g_instances != 0); // S_OK or S_FALSE
}

pistol350

hello Sapero and thank you for sharing. ;)
I don't know why but when i compile (as console) i got an "unresolved extern main" + an "Unresolved extern _hinstance" error  ???
Any help please ?

cheers!
_____________________________________________________________________________________________________
Peter
Regards,

Peter B.

sapero

April 01, 2007, 06:25:48 PM #2 Last Edit: April 01, 2007, 06:31:20 PM by sapero
You have to update your compiler :)
the main() is not here, you should compile this as dll, not as exe. The _hinstance should be exported from dllstartup.o, if not, you can replace it with api call to GetModuleHandleA("your dll name here.dll")

pistol350

LOL  ;D
It seems that i did not read your indications well enough ,fortunately, i indicated that i compiled it as console.
Anyway,It compiles well as DLL!

Besides my compiler version is RC1 version 1.0.0.404  and i may be wrong, but I think that it is updated ! and i also have the "dllstartup.o" in my lib folder.

Thanks for replying  ;)

cheers!
Regards,

Peter B.

ExMember001


sapero

Version 2:
The IShellExtInitImpl::Initialize method has been extended to get the source directory, where the context menu was created. This folder path is saved in class string and copied to IContextMenuImpl, so the handler can determine the source and create different menu items for different folders.
In this example a "Delete Aurora temporary files" menu item is created, but only if the folder has asm, o, map, exp or dbgs files. After selecting this menu item, the files are moved to recycle bin, so you can restore it.
It works now for folder background, folder (as item in another folder) and drive.

This code is only for reference, the full source you find in attachment.

the new Initialize method:IShellExtInitImpl::Initialize(ITEMIDLIST *pidlFolder,IDataObject *pdtobj,int hkeyProgID),int
{
if (pdtobj)
{
STGMEDIUM   medium;
UINT        uCount;
FORMATETC   fe;
fe.cfFormat = CF_HDROP;
fe.ptd      = NULL;
fe.dwAspect = DVASPECT_CONTENT;
fe.lindex   = -1;
fe.tymed    = TYMED_HGLOBAL;

if (!pdtobj->GetData(&fe, &medium))
{
// Get the count of files dropped.
uCount = DragQueryFile(medium.hGlobal, -1, NULL, 0);

// Get the first file name from the CF_HDROP - this is our target folder path
if(uCount)
{
DragQueryFile(medium.hGlobal, 0, &m_szFile, MAX_PATH); // m_szFile = string
}

ReleaseStgMedium(&medium);
}
}
else if (pidlFolder)
{
// from explorer
SHGetPathFromIDListA(pidlFolder, &m_szFile);
}
return 0;
}


ExMember001

April 07, 2007, 02:45:41 PM #6 Last Edit: April 07, 2007, 02:50:09 PM by KrYpT
Hi Sapero !
To use this dll inside a program.
do i only call  this ?
DllRegisterServer;   // optinoal
DllUnregisterServer; // optional

sapero

April 07, 2007, 03:14:55 PM #7 Last Edit: April 07, 2007, 04:27:50 PM by sapero
No, you use this dll transparently, once it gets registered, you can forget that this dll exists.
Create a register.bat file in same folder as your dll, then paste this, save and run
regsvr32 CleanupAuroraTemps.dll
Now open (this) project folder in explorer and right click on folder background, search for "delete aurora temporary files" menu item.


I personally (un)register dll's from context menu:
HKEY_CLASSES_ROOT\dllfile\shell\Register\command
@ = regsvr32 "%1"

HKEY_CLASSES_ROOT\dllfile\shell\Unregister\command
@ = regsvr32 /u "%1"

The "@" is the default value that always  exists in all registry keys
You can manually call all methods implemented in this server if your creating custom shell :)

ExMember001

ok, thanx :D
I want to use this method to add shell extension to my Ampkc player
but i want to register it inside the program... ill need to dig your code a bit more ;)

a class to register contextmenu shell extension could be great !

sapero

April 07, 2007, 04:26:14 PM #9 Last Edit: April 07, 2007, 04:48:03 PM by sapero
Here is a universal function to do it:declare import, LoadLibraryA(string path),int;
declare import, GetProcAddress(int hdll, string api),int;
declare import, FreeLibrary(int hdll),int;


// returns 0 on success, or one of erros: 1,2,SELFREG_E_CLASS
sub register(string szDllPath,opt int unregister=false),int
{
int result = 1; // dll failed to load
declare *dllregister(),int;
int hdll = LoadLibraryA(szDllPath);
if (hdll)
{
result = 2; // function not found
if (unregister)
dllregister = GetProcAddress(hdll, "DllUnregisterServer")
else
dllregister = GetProcAddress(hdll, "DllRegisterServer");

if (dllregister)
result = dllregister();

FreeLibrary(hdll);
}
return result;
}

sub main()
{
if (!register("drive:\\folder1\\folder2\\server.dll"))
MessageBox(0, "registered!", "")
else
MessageBox(0, "registering failed", "");
}


And remember to create your own CLSID for every new extension:sub main()
{
// compile for console
#use "ole32.lib"
import int CoCreateGuid(pointer guid);
import int StringFromGUID2(pointer guid,wstring wsz,int cch);
import int printf(...);
import int system(...);

unsigned int x[4];
CoCreateGuid(&x);
printf("#emit CLSID_This      dd 0x%x, 0x%x, 0x%x, 0x%x\n", x[0], x[1], x[2], x[3]);

wstring wsz[44];
StringFromGUID2(&x, wsz, 44);
printf("#emit REG_GUID_STRING db \"%S\",0\n", wsz);
return system("pause");
}

CLSID_This is a dummy name, it's ok if your implementing only one class in your dll. A single dll can implement more classes, usually one class for folders, one for all files (*.*), one for always supported (wav, mp3, wma).
Your ClassFactory should return different objects for different clsids (of type IShellExtInitImpl, like IShellExtInit_mp3, IShellExtInit_folder, IShellExtInit_filegroup...)

The last very important think: do not delay inside IContextMenu longer than 0.5 second, you will be blocked by the user :) (like ati desktop menu item, or SendTo)

ExMember001